first commit
This commit is contained in:
commit
d5b9ba872c
1649
Cargo.lock
generated
Executable file
1649
Cargo.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
Executable file
38
Cargo.toml
Executable file
@ -0,0 +1,38 @@
|
||||
# Cargo.toml
|
||||
[package]
|
||||
name = "futriix"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
rmp-serde = "1.1"
|
||||
rmp = "0.8"
|
||||
toml = "0.8"
|
||||
rlua = "0.20.1"
|
||||
crossbeam = "0.8"
|
||||
dashmap = "5.0"
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
hyper-rustls = "0.24"
|
||||
rustls = "0.21"
|
||||
rustls-pemfile = "1.0"
|
||||
tokio-rustls = "0.24"
|
||||
siphasher = "1.0.1"
|
||||
csv = "1.3"
|
||||
futures = "0.3" # Добавлена зависимость futures
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0", features = ["full", "rt-multi-thread", "time"] }
|
||||
|
||||
[[test]]
|
||||
name = "integration_tests"
|
||||
path = "tests/integration_tests.rs"
|
||||
harness = true
|
||||
82
config.toml
Executable file
82
config.toml
Executable file
@ -0,0 +1,82 @@
|
||||
# config.toml
|
||||
# Конфигурация Futriix Server с wait-free архитектурой
|
||||
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
http_port = 9090
|
||||
https_port = 8443
|
||||
max_connections = 10000
|
||||
connection_timeout = 30
|
||||
http2_enabled = true
|
||||
http = true # Новая директива: включение HTTP сервера
|
||||
https = false # Новая директива: включение HTTPS сервера
|
||||
|
||||
[tls]
|
||||
enabled = false
|
||||
cert_path = "/futriix/certs/server.crt" # Изменено с /falcot/certs
|
||||
key_path = "/futriix/certs/server.key" # Изменено с /falcot/certs
|
||||
|
||||
[replication]
|
||||
enabled = true
|
||||
master_nodes = [
|
||||
"node1.futriix:9090", # Изменено с falcot на futriix
|
||||
"node2.futriix:9090",
|
||||
"node3.futriix:9090"
|
||||
]
|
||||
sync_interval = 1000 # ms
|
||||
replication_factor = 3
|
||||
|
||||
[sharding]
|
||||
enabled = true
|
||||
shards = 3
|
||||
replication_factor = 2
|
||||
auto_balance = true
|
||||
|
||||
[cluster] # Новая секция для кластера
|
||||
enabled = true
|
||||
name = "futriix-main-cluster"
|
||||
|
||||
[acl]
|
||||
enabled = false
|
||||
allowed_ips = ["127.0.0.1", "192.168.1.0/24"]
|
||||
denied_ips = ["10.0.0.5"]
|
||||
|
||||
[logging]
|
||||
level = "info"
|
||||
file_path = "/futriix/logs/futriix.log" # Изменено с falcot.log
|
||||
max_file_size = 10485760 # 10MB
|
||||
backup_count = 5
|
||||
|
||||
[backup]
|
||||
enabled = true
|
||||
interval = 3600 # 1 hour
|
||||
retention = 7 # days
|
||||
path = "/futriix/backups" # Изменено с /falcot/backups
|
||||
|
||||
[csv] # Новая секция для CSV
|
||||
import_dir = "/futriix/csv/import" # Директория для импорта
|
||||
export_dir = "/futriix/csv/export" # Директория для экспорта
|
||||
max_file_size = 104857600 # 100MB
|
||||
|
||||
[security]
|
||||
require_authentication = false
|
||||
jwt_secret = "your-secret-key-here"
|
||||
password_hashing_rounds = 12
|
||||
|
||||
[performance]
|
||||
max_memory_mb = 1024
|
||||
cache_size_mb = 512
|
||||
worker_threads = 4
|
||||
io_threads = 2
|
||||
|
||||
[monitoring]
|
||||
enabled = false
|
||||
prometheus_port = 9090
|
||||
health_check_interval = 30
|
||||
|
||||
[limits]
|
||||
max_documents_per_collection = 1000000
|
||||
max_collections = 1000
|
||||
max_indexes_per_collection = 16
|
||||
request_timeout_ms = 5000
|
||||
max_request_size_mb = 10
|
||||
389
futriix.log
Executable file
389
futriix.log
Executable file
@ -0,0 +1,389 @@
|
||||
[2025-11-22 00:09:24] Starting Futriix server
|
||||
[2025-11-22 00:09:24] Loading configuration from: config.toml
|
||||
[2025-11-22 00:09:24] Database initialized with system collections
|
||||
[2025-11-22 00:09:24] Server created successfully
|
||||
[2025-11-22 00:09:24] Futriix Database Server started
|
||||
[2025-11-22 00:09:24] Version: 1.0.0
|
||||
[2025-11-22 00:09:24] Starting Lua interpreter...
|
||||
[2025-11-22 00:09:24] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-22 00:09:24] Futriix server stopped
|
||||
[2025-11-22 00:11:46] Starting Futriix server
|
||||
[2025-11-22 00:11:46] Loading configuration from: config.toml
|
||||
[2025-11-22 00:11:46] Database initialized with system collections
|
||||
[2025-11-22 00:11:46] Server created successfully
|
||||
[2025-11-22 00:11:46] Futriix Database Server started
|
||||
[2025-11-22 00:11:46] Version: 1.0.0
|
||||
[2025-11-22 00:11:46] Starting Lua interpreter...
|
||||
[2025-11-22 00:11:46] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-22 00:12:24] Futriix server stopped
|
||||
[2025-11-22 00:13:20] Starting Futriix server
|
||||
[2025-11-22 00:13:20] Loading configuration from: config.toml
|
||||
[2025-11-22 00:13:20] Database initialized with system collections
|
||||
[2025-11-22 00:13:20] Server created successfully
|
||||
[2025-11-22 00:13:20] Futriix Database Server started
|
||||
[2025-11-22 00:13:20] Version: 1.0.0
|
||||
[2025-11-22 00:13:20] Starting Lua interpreter...
|
||||
[2025-11-22 00:13:20] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-22 00:13:40] Futriix server stopped
|
||||
[2025-11-22 00:19:36] Starting Futriix server
|
||||
[2025-11-22 00:19:36] Loading configuration from: config.toml
|
||||
[2025-11-22 00:19:36] Database initialized with system collections
|
||||
[2025-11-22 00:19:36] Server created successfully
|
||||
[2025-11-22 00:19:36] Futriix Database Server started
|
||||
[2025-11-22 00:19:36] Version: 1.0.0
|
||||
[2025-11-22 00:19:36] Starting Lua interpreter...
|
||||
[2025-11-22 00:19:36] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-22 00:19:58] Futriix server stopped
|
||||
[2025-11-22 00:25:01] Starting Futriix server
|
||||
[2025-11-22 00:25:01] Loading configuration from: config.toml
|
||||
[2025-11-22 00:25:01] Database initialized with system collections
|
||||
[2025-11-22 00:25:01] Server created successfully
|
||||
[2025-11-22 00:25:01] Futriix Database Server started
|
||||
[2025-11-22 00:25:01] Version: 1.0.0
|
||||
[2025-11-22 00:25:01] Starting Lua interpreter...
|
||||
[2025-11-22 00:25:01] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-22 00:33:05] Starting Futriix server
|
||||
[2025-11-22 00:33:05] Loading configuration from: config.toml
|
||||
[2025-11-22 00:33:05] Database initialized with system collections
|
||||
[2025-11-22 00:33:05] Server created successfully
|
||||
[2025-11-22 00:33:05] Futriix Database Server started
|
||||
[2025-11-22 00:33:05] Version: 1.0.0
|
||||
[2025-11-22 00:33:05] Starting Lua interpreter...
|
||||
[2025-11-22 00:33:05] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-22 01:01:31] Starting Futriix server
|
||||
[2025-11-22 01:01:31] Loading configuration from: config.toml
|
||||
[2025-11-22 01:01:31] Database initialized with system collections
|
||||
[2025-11-22 01:01:31] Server created successfully
|
||||
[2025-11-22 01:01:31] Futriix Database Server started
|
||||
[2025-11-22 01:01:31] Version: 1.0.0
|
||||
[2025-11-22 01:01:31] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-22 01:01:31] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-22 01:01:31] Starting Lua interpreter...
|
||||
[2025-11-24 01:05:09] Starting Futriix server
|
||||
[2025-11-24 01:05:09] Loading configuration from: config.toml
|
||||
[2025-11-24 01:05:09] Database initialized with system collections
|
||||
[2025-11-24 01:05:09] Server created successfully
|
||||
[2025-11-24 01:05:09] Futriix Database Server started
|
||||
[2025-11-24 01:05:09] Version: 1.0.0
|
||||
[2025-11-24 01:05:09] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-24 01:05:09] Starting Lua interpreter...
|
||||
[2025-11-24 01:05:09] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-24 01:05:29] Starting Futriix server
|
||||
[2025-11-24 01:05:29] Loading configuration from: config.toml
|
||||
[2025-11-24 01:05:29] Database initialized with system collections
|
||||
[2025-11-24 01:05:29] Server created successfully
|
||||
[2025-11-24 01:05:29] Futriix Database Server started
|
||||
[2025-11-24 01:05:29] Version: 1.0.0
|
||||
[2025-11-24 01:05:29] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-24 01:05:29] Starting Lua interpreter...
|
||||
[2025-11-24 01:05:29] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-24 01:06:41] Futriix server stopped
|
||||
[2025-11-24 01:06:43] Starting Futriix server
|
||||
[2025-11-24 01:06:43] Loading configuration from: config.toml
|
||||
[2025-11-24 01:06:43] Database initialized with system collections
|
||||
[2025-11-24 01:06:43] Server created successfully
|
||||
[2025-11-24 01:06:43] Futriix Database Server started
|
||||
[2025-11-24 01:06:43] Version: 1.0.0
|
||||
[2025-11-24 01:06:43] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-24 01:06:43] Starting Lua interpreter...
|
||||
[2025-11-24 01:06:43] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-24 01:27:24] Starting Futriix server
|
||||
[2025-11-24 01:27:24] Loading configuration from: config.toml
|
||||
[2025-11-24 01:27:24] Database initialized with system collections
|
||||
[2025-11-24 01:27:24] Server created successfully
|
||||
[2025-11-24 01:27:24] Futriix Database Server started
|
||||
[2025-11-24 01:27:24] Version: 1.0.0
|
||||
[2025-11-24 01:27:24] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-24 01:27:24] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-24 01:28:06] Futriix server stopped
|
||||
[2025-11-25 01:13:37] Starting Futriix server
|
||||
[2025-11-25 01:13:37] Loading configuration from: config.toml
|
||||
[2025-11-25 01:13:37] Database initialized with system collections
|
||||
[2025-11-25 01:13:37] Server created successfully
|
||||
[2025-11-25 01:13:37] Futriix Database Server started
|
||||
[2025-11-25 01:13:37] Version: 1.0.0
|
||||
[2025-11-25 01:13:37] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 01:13:37] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-25 01:13:57] Futriix server stopped
|
||||
[2025-11-25 01:25:00] Starting Futriix server
|
||||
[2025-11-25 01:25:00] Loading configuration from: config.toml
|
||||
[2025-11-25 01:25:00] Database initialized with system collections
|
||||
[2025-11-25 01:25:00] Server created successfully
|
||||
[2025-11-25 01:25:00] Futriix Database Server started
|
||||
[2025-11-25 01:25:00] Version: 1.0.0
|
||||
[2025-11-25 01:25:00] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 01:25:00] Failed to start HTTPS server: HTTP error: Failed to open certificate: No such file or directory (os error 2)
|
||||
[2025-11-25 01:33:11] Futriix server stopped
|
||||
[2025-11-25 01:35:55] Starting Futriix server
|
||||
[2025-11-25 01:35:55] Loading configuration from: config.toml
|
||||
[2025-11-25 01:35:55] Database initialized with system collections
|
||||
[2025-11-25 01:35:55] Server created successfully
|
||||
[2025-11-25 01:35:55] Futriix Database Server started
|
||||
[2025-11-25 01:35:55] Version: 1.0.0
|
||||
[2025-11-25 01:35:55] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 01:35:55] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 21:31:08] Starting Futriix server
|
||||
[2025-11-25 21:31:08] Loading configuration from: config.toml
|
||||
[2025-11-25 21:31:08] Database initialized with system collections
|
||||
[2025-11-25 21:31:08] Server created successfully
|
||||
[2025-11-25 21:31:08] Futriix Database Server started
|
||||
[2025-11-25 21:31:08] Version: 1.0.0
|
||||
[2025-11-25 21:31:08] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 21:31:08] ACL: disabled
|
||||
[2025-11-25 21:31:08] Replication: enabled
|
||||
[2025-11-25 21:31:08] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 21:31:52] Futriix server stopped
|
||||
[2025-11-25 21:40:34] Starting Futriix server
|
||||
[2025-11-25 21:40:34] Loading configuration from: config.toml
|
||||
[2025-11-25 21:40:34] Database initialized with system collections
|
||||
[2025-11-25 21:40:34] Server created successfully
|
||||
[2025-11-25 21:40:34] Futriix Database Server started
|
||||
[2025-11-25 21:40:34] Version: 1.0.0
|
||||
[2025-11-25 21:40:34] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 21:40:35] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 21:41:16] Futriix server stopped
|
||||
[2025-11-25 21:51:01] Starting Futriix server
|
||||
[2025-11-25 21:51:01] Loading configuration from: config.toml
|
||||
[2025-11-25 21:51:01] Database initialized with system collections
|
||||
[2025-11-25 21:51:01] Server created successfully
|
||||
[2025-11-25 21:51:01] Futriix Database Server started
|
||||
[2025-11-25 21:51:01] Version: 1.0.0
|
||||
[2025-11-25 21:51:01] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 21:51:02] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 21:51:27] Futriix server stopped
|
||||
[2025-11-25 22:15:32] Starting Futriix server
|
||||
[2025-11-25 22:15:32] Loading configuration from: config.toml
|
||||
[2025-11-25 22:15:32] Database initialized with system collections
|
||||
[2025-11-25 22:15:32] Server created successfully
|
||||
[2025-11-25 22:15:32] Futriix Database Server started
|
||||
[2025-11-25 22:15:32] Version: 1.0.0
|
||||
[2025-11-25 22:15:32] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 22:15:32] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 22:15:43] Futriix server stopped
|
||||
[2025-11-25 22:24:30] Starting Futriix server
|
||||
[2025-11-25 22:24:30] Loading configuration from: config.toml
|
||||
[2025-11-25 22:24:30] Database initialized with system collections
|
||||
[2025-11-25 22:24:30] Server created successfully
|
||||
[2025-11-25 22:24:30] Futriix Database Server started
|
||||
[2025-11-25 22:24:30] Version: 1.0.0
|
||||
[2025-11-25 22:24:30] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 22:24:30] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 22:41:25] Starting Futriix server
|
||||
[2025-11-25 22:41:25] Loading configuration from: config.toml
|
||||
[2025-11-25 22:41:25] Database initialized with system collections
|
||||
[2025-11-25 22:41:25] Server created successfully
|
||||
[2025-11-25 22:41:25] Futriix Database Server started
|
||||
[2025-11-25 22:41:25] Version: 1.0.0
|
||||
[2025-11-25 22:41:25] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 22:41:25] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 22:41:57] Futriix server stopped
|
||||
[2025-11-25 22:43:55] Starting Futriix server
|
||||
[2025-11-25 22:43:55] Loading configuration from: config.toml
|
||||
[2025-11-25 22:43:55] Database initialized with system collections
|
||||
[2025-11-25 22:43:55] Server created successfully
|
||||
[2025-11-25 22:43:55] Futriix Database Server started
|
||||
[2025-11-25 22:43:55] Version: 1.0.0
|
||||
[2025-11-25 22:43:55] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 22:43:55] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 22:44:09] Futriix server stopped
|
||||
[2025-11-25 23:24:51] Starting Futriix server
|
||||
[2025-11-25 23:24:51] Loading configuration from: config.toml
|
||||
[2025-11-25 23:24:51] Database initialized with system collections
|
||||
[2025-11-25 23:24:51] Server created successfully
|
||||
[2025-11-25 23:24:51] Futriix Database Server started
|
||||
[2025-11-25 23:24:51] Version: 1.0.0
|
||||
[2025-11-25 23:24:51] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-25 23:24:51] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-25 23:24:55] Futriix server stopped
|
||||
[2025-11-26 00:44:05] Starting Futriix server
|
||||
[2025-11-26 00:44:05] Loading configuration from: config.toml
|
||||
[2025-11-26 00:44:05] Database initialized with system collections
|
||||
[2025-11-26 00:44:05] Server created successfully
|
||||
[2025-11-26 00:44:05] Futriix Database Server started
|
||||
[2025-11-26 00:44:05] Version: 1.0.0
|
||||
[2025-11-26 00:44:05] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 00:44:05] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-26 00:44:52] Futriix server stopped
|
||||
[2025-11-26 21:49:55] Starting Futriix server
|
||||
[2025-11-26 21:49:55] Loading configuration from: config.toml
|
||||
[2025-11-26 21:49:55] Database initialized with system collections
|
||||
[2025-11-26 21:49:55] Server created successfully
|
||||
[2025-11-26 21:49:55] Futriix Database Server started
|
||||
[2025-11-26 21:49:55] Version: 1.0.0
|
||||
[2025-11-26 21:49:55] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 21:49:55] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-26 21:50:34] Futriix server stopped
|
||||
[2025-11-26 21:51:56] Starting Futriix server
|
||||
[2025-11-26 21:51:56] Loading configuration from: config.toml
|
||||
[2025-11-26 21:51:56] Database initialized with system collections
|
||||
[2025-11-26 21:51:56] Server created successfully
|
||||
[2025-11-26 21:51:56] Futriix Database Server started
|
||||
[2025-11-26 21:51:56] Version: 1.0.0
|
||||
[2025-11-26 21:51:56] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 21:51:57] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-26 21:52:50] Futriix server stopped
|
||||
[2025-11-26 21:55:44] Starting Futriix server
|
||||
[2025-11-26 21:55:44] Loading configuration from: config.toml
|
||||
[2025-11-26 21:55:44] Database initialized with system collections
|
||||
[2025-11-26 21:55:44] Server created successfully
|
||||
[2025-11-26 21:55:44] Futriix Database Server started
|
||||
[2025-11-26 21:55:44] Version: 1.0.0
|
||||
[2025-11-26 21:55:44] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 21:55:44] HTTPS server started on 127.0.0.1:8443
|
||||
[2025-11-26 21:56:18] Futriix server stopped
|
||||
[2025-11-26 22:25:42] Starting Futriix server
|
||||
[2025-11-26 22:25:42] Loading configuration from: config.toml
|
||||
[2025-11-26 22:25:42] Database initialized with system collections
|
||||
[2025-11-26 22:25:42] Server created successfully
|
||||
[2025-11-26 22:25:42] Futriix Database Server started
|
||||
[2025-11-26 22:25:42] Version: 1.0.0
|
||||
[2025-11-26 22:25:42] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 22:25:42] HTTP server started on 127.0.0.1:8080
|
||||
[2025-11-26 22:26:21] Futriix server stopped
|
||||
[2025-11-26 22:27:51] Starting Futriix server
|
||||
[2025-11-26 22:27:51] Loading configuration from: config.toml
|
||||
[2025-11-26 22:27:51] Database initialized with system collections
|
||||
[2025-11-26 22:27:51] Server created successfully
|
||||
[2025-11-26 22:27:51] Futriix Database Server started
|
||||
[2025-11-26 22:27:51] Version: 1.0.0
|
||||
[2025-11-26 22:27:51] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 22:27:51] HTTP server started on 127.0.0.1:8080
|
||||
[2025-11-26 22:28:36] Futriix server stopped
|
||||
[2025-11-26 22:29:32] Starting Futriix server
|
||||
[2025-11-26 22:29:32] Loading configuration from: config.toml
|
||||
[2025-11-26 22:29:32] Database initialized with system collections
|
||||
[2025-11-26 22:29:32] Server created successfully
|
||||
[2025-11-26 22:29:32] Futriix Database Server started
|
||||
[2025-11-26 22:29:32] Version: 1.0.0
|
||||
[2025-11-26 22:29:32] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 22:29:32] HTTP server started on 127.0.0.1:9090
|
||||
[2025-11-26 22:44:04] Starting Futriix server
|
||||
[2025-11-26 22:44:04] Loading configuration from: config.toml
|
||||
[2025-11-26 22:44:04] Database initialized with system collections
|
||||
[2025-11-26 22:44:04] Server created successfully
|
||||
[2025-11-26 22:44:04] Futriix Database Server started
|
||||
[2025-11-26 22:44:04] Version: 1.0.0
|
||||
[2025-11-26 22:44:04] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 22:56:20] Starting Futriix server
|
||||
[2025-11-26 22:56:20] Loading configuration from: config.toml
|
||||
[2025-11-26 22:56:20] Database initialized with system collections
|
||||
[2025-11-26 22:56:20] Server created successfully
|
||||
[2025-11-26 22:56:20] Futriix Database Server started
|
||||
[2025-11-26 22:56:20] Version: 1.0.0
|
||||
[2025-11-26 22:56:20] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 22:57:12] Futriix server stopped
|
||||
[2025-11-26 23:05:43] Starting Futriix server
|
||||
[2025-11-26 23:05:43] Loading configuration from: config.toml
|
||||
[2025-11-26 23:05:43] Database initialized with system collections
|
||||
[2025-11-26 23:05:43] Server created successfully
|
||||
[2025-11-26 23:05:43] Futriix Database Server started
|
||||
[2025-11-26 23:05:43] Version: 1.0.0
|
||||
[2025-11-26 23:05:43] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 23:07:11] Futriix server stopped
|
||||
[2025-11-26 23:07:19] Starting Futriix server
|
||||
[2025-11-26 23:07:19] Loading configuration from: config.toml
|
||||
[2025-11-26 23:07:19] Database initialized with system collections
|
||||
[2025-11-26 23:07:19] Server created successfully
|
||||
[2025-11-26 23:07:19] Futriix Database Server started
|
||||
[2025-11-26 23:07:19] Version: 1.0.0
|
||||
[2025-11-26 23:07:19] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-26 23:07:31] Futriix server stopped
|
||||
[2025-11-27 20:29:48] Starting Futriix server
|
||||
[2025-11-27 20:29:48] Loading configuration from: config.toml
|
||||
[2025-11-27 20:29:48] Database initialized with system collections
|
||||
[2025-11-27 20:29:48] Server created successfully
|
||||
[2025-11-27 20:29:48] Futriix Database Server started
|
||||
[2025-11-27 20:29:48] Version: 1.0.0
|
||||
[2025-11-27 20:29:48] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-27 21:00:06] Starting Futriix server
|
||||
[2025-11-27 21:00:06] Loading configuration from: config.toml
|
||||
[2025-11-27 21:00:06] Database initialized with system collections
|
||||
[2025-11-27 21:00:06] Server created successfully
|
||||
[2025-11-27 21:00:06] Futriix Database Server started
|
||||
[2025-11-27 21:00:06] Version: 1.0.0
|
||||
[2025-11-27 21:00:06] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-27 21:00:06] HTTP server started on 127.0.0.1:9090
|
||||
[2025-11-27 21:03:23] Futriix server stopped
|
||||
[2025-11-27 21:05:05] Starting Futriix server
|
||||
[2025-11-27 21:05:05] Loading configuration from: config.toml
|
||||
[2025-11-27 21:05:05] Database initialized with system collections
|
||||
[2025-11-27 21:05:05] Server created successfully
|
||||
[2025-11-27 21:05:05] Futriix Database Server started
|
||||
[2025-11-27 21:05:05] Version: 1.0.0
|
||||
[2025-11-27 21:05:05] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-27 21:05:05] HTTP server started on 127.0.0.1:9090
|
||||
[2025-11-27 21:05:16] Starting Futriix server
|
||||
[2025-11-27 21:05:16] Loading configuration from: config.toml
|
||||
[2025-11-27 21:05:16] Database initialized with system collections
|
||||
[2025-11-27 21:05:16] Server created successfully
|
||||
[2025-11-27 21:05:16] Futriix Database Server started
|
||||
[2025-11-27 21:05:16] Version: 1.0.0
|
||||
[2025-11-27 21:05:16] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-27 21:05:16] HTTP server started on 127.0.0.1:9090
|
||||
[2025-11-27 21:35:10] Starting Futriix server
|
||||
[2025-11-27 21:35:10] Loading configuration from: config.toml
|
||||
[2025-11-27 21:35:10] Database initialized with system collections
|
||||
[2025-11-27 21:35:10] Server created successfully
|
||||
[2025-11-27 21:35:10] Futriix Database Server started
|
||||
[2025-11-27 21:35:10] Version: 1.0.0
|
||||
[2025-11-27 21:35:10] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-27 21:36:33] Starting Futriix server
|
||||
[2025-11-27 21:36:33] Loading configuration from: config.toml
|
||||
[2025-11-27 21:36:33] Database initialized with system collections
|
||||
[2025-11-27 21:36:33] Server created successfully
|
||||
[2025-11-27 21:36:33] Futriix Database Server started
|
||||
[2025-11-27 21:36:33] Version: 1.0.0
|
||||
[2025-11-27 21:36:33] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-27 21:38:49] Starting Futriix server
|
||||
[2025-11-27 21:38:49] Loading configuration from: config.toml
|
||||
[2025-11-27 21:38:49] Database initialized with system collections
|
||||
[2025-11-27 21:38:49] Server created successfully
|
||||
[2025-11-27 21:38:49] Futriix Database Server started
|
||||
[2025-11-27 21:38:49] Version: 1.0.0
|
||||
[2025-11-27 21:38:49] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-27 21:56:48] Starting Futriix server
|
||||
[2025-11-27 21:56:48] Loading configuration from: config.toml
|
||||
[2025-11-27 21:56:48] Database initialized with system collections
|
||||
[2025-11-27 21:56:48] Server created successfully
|
||||
[2025-11-27 21:56:48] Futriix Database Server started
|
||||
[2025-11-27 21:56:48] Version: 1.0.0
|
||||
[2025-11-27 21:56:48] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-27 22:14:09] Starting Futriix server
|
||||
[2025-11-27 22:14:09] Loading configuration from: config.toml
|
||||
[2025-11-27 22:14:09] Database initialized with system collections
|
||||
[2025-11-27 22:14:09] Server created successfully
|
||||
[2025-11-27 22:14:09] Futriix Database Server started
|
||||
[2025-11-27 22:14:09] Version: 1.0.0
|
||||
[2025-11-27 22:14:09] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-28 00:13:27] Starting Futriix server
|
||||
[2025-11-28 00:13:27] Loading configuration from: config.toml
|
||||
[2025-11-28 00:13:27] Database initialized with system collections
|
||||
[2025-11-28 00:13:27] Server created successfully
|
||||
[2025-11-28 00:13:27] Futriix Database Server started
|
||||
[2025-11-28 00:13:27] Version: 1.0.0
|
||||
[2025-11-28 00:13:27] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-28 00:37:01.851] Starting Futriix server
|
||||
[2025-11-28 00:37:01.851] Loading configuration from: config.toml
|
||||
[2025-11-28 00:37:01.858] Database initialized with system collections
|
||||
[2025-11-28 00:37:01.858] Server created successfully
|
||||
[2025-11-28 00:37:01.858] Futriix Database Server started
|
||||
[2025-11-28 00:37:01.858] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-28 00:46:20.204] Starting Futriix server
|
||||
[2025-11-28 00:46:20.204] Loading configuration from: config.toml
|
||||
[2025-11-28 00:46:20.210] Database initialized with system collections
|
||||
[2025-11-28 00:46:20.210] Server created successfully
|
||||
[2025-11-28 00:46:20.210] Futriix Database Server started
|
||||
[2025-11-28 00:46:20.210] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-28 00:57:07.501] Starting Futriix server
|
||||
[2025-11-28 00:57:07.501] Loading configuration from: config.toml
|
||||
[2025-11-28 00:57:07.504] Database initialized with system collections
|
||||
[2025-11-28 00:57:07.504] Server created successfully
|
||||
[2025-11-28 00:57:07.504] Futriix Database Server started
|
||||
[2025-11-28 00:57:07.505] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
[2025-11-28 00:57:42.988] Futriix server stopped
|
||||
[2025-11-28 00:59:01.667] Starting Futriix server
|
||||
[2025-11-28 00:59:01.667] Loading configuration from: config.toml
|
||||
[2025-11-28 00:59:01.671] Database initialized with system collections
|
||||
[2025-11-28 00:59:01.671] Server created successfully
|
||||
[2025-11-28 00:59:01.671] Futriix Database Server started
|
||||
[2025-11-28 00:59:01.671] Mode: cluster (cluster: 'futriix-main-cluster')
|
||||
36
lua_script/init.lua
Executable file
36
lua_script/init.lua
Executable file
@ -0,0 +1,36 @@
|
||||
-- lua_scripts/init.lua
|
||||
-- Инициализационный Lua скрипт для Futriix Server
|
||||
-- Автоматически выполняется при старте сервера для настройки окружения
|
||||
|
||||
falcot_log("Initializing Futriix Server v1.0.0 with Lua scripting...")
|
||||
|
||||
-- Создаем глобальные функции для бэкапов
|
||||
function futriix.engine.backup.start()
|
||||
return futriix_db.backup_start()
|
||||
end
|
||||
|
||||
function futriix.engine.backup.restore(backup_path)
|
||||
return futriix_db.backup_restore(backup_path)
|
||||
end
|
||||
|
||||
-- Пример создания коллекции при старте с временной меткой
|
||||
local current_timestamp = os.date("%d-%m-%Y")
|
||||
futriix_db.create("system_config", '{"key": "server_start_time", "value": "' .. current_timestamp .. '"}')
|
||||
falcot_log("System configuration initialized with timestamp: " .. current_timestamp)
|
||||
|
||||
-- Пример ACL проверки
|
||||
function check_access(ip_address)
|
||||
if ip_address == "127.0.0.1" then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Логирование информации о кластере
|
||||
if futriix.cluster.enabled then
|
||||
falcot_log("Cluster mode enabled: " .. futriix.cluster.name)
|
||||
else
|
||||
falcot_log("Standalone mode enabled")
|
||||
end
|
||||
|
||||
falcot_log("Lua initialization script completed")
|
||||
154
protocol.rs
Executable file
154
protocol.rs
Executable file
@ -0,0 +1,154 @@
|
||||
// src/common/protocol.rs
|
||||
//! Протокол обмена данными для Falcot
|
||||
//!
|
||||
//! Определяет структуры команд и ответов для взаимодействия между
|
||||
//! компонентами системы с использованием wait-free сериализации.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::server::database::Index;
|
||||
|
||||
/// Команды для выполнения в базе данных
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Command {
|
||||
Create {
|
||||
collection: String,
|
||||
document: Vec<u8>,
|
||||
},
|
||||
Read {
|
||||
collection: String,
|
||||
id: String,
|
||||
},
|
||||
Update {
|
||||
collection: String,
|
||||
id: String,
|
||||
document: Vec<u8>,
|
||||
},
|
||||
Delete {
|
||||
collection: String,
|
||||
id: String,
|
||||
},
|
||||
Query {
|
||||
collection: String,
|
||||
filter: Vec<u8>,
|
||||
},
|
||||
CreateProcedure {
|
||||
name: String,
|
||||
code: Vec<u8>,
|
||||
},
|
||||
CallProcedure {
|
||||
name: String,
|
||||
},
|
||||
BeginTransaction {
|
||||
transaction_id: String,
|
||||
},
|
||||
CommitTransaction {
|
||||
transaction_id: String,
|
||||
},
|
||||
RollbackTransaction {
|
||||
transaction_id: String,
|
||||
},
|
||||
CreateIndex {
|
||||
collection: String,
|
||||
index: Index,
|
||||
},
|
||||
QueryByIndex {
|
||||
collection: String,
|
||||
index_name: String,
|
||||
value: Vec<u8>,
|
||||
},
|
||||
// Новые команды для шардинга
|
||||
AddShardNode {
|
||||
node_id: String,
|
||||
address: String,
|
||||
capacity: u64,
|
||||
},
|
||||
RemoveShardNode {
|
||||
node_id: String,
|
||||
},
|
||||
MigrateShard {
|
||||
collection: String,
|
||||
from_node: String,
|
||||
to_node: String,
|
||||
shard_key: String,
|
||||
},
|
||||
RebalanceCluster,
|
||||
GetClusterStatus,
|
||||
// Команды для constraints
|
||||
AddConstraint {
|
||||
collection: String,
|
||||
constraint_name: String,
|
||||
constraint_type: String,
|
||||
field: String,
|
||||
value: Vec<u8>,
|
||||
},
|
||||
RemoveConstraint {
|
||||
collection: String,
|
||||
constraint_name: String,
|
||||
},
|
||||
// Команды для компрессии
|
||||
EnableCompression {
|
||||
collection: String,
|
||||
algorithm: String,
|
||||
},
|
||||
DisableCompression {
|
||||
collection: String,
|
||||
},
|
||||
// Команды для глобальных индексов
|
||||
CreateGlobalIndex {
|
||||
name: String,
|
||||
field: String,
|
||||
unique: bool,
|
||||
},
|
||||
QueryGlobalIndex {
|
||||
index_name: String,
|
||||
value: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Ответы от базы данных
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Response {
|
||||
Success(Vec<u8>),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
/// Сообщение для репликации
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReplicationMessage {
|
||||
pub sequence: u64,
|
||||
pub command: Command,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
/// Структура для информации о шарде
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ShardInfo {
|
||||
pub node_id: String,
|
||||
pub address: String,
|
||||
pub capacity: u64,
|
||||
pub used: u64,
|
||||
pub collections: Vec<String>,
|
||||
}
|
||||
|
||||
/// Структура для статуса кластера
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClusterStatus {
|
||||
pub nodes: Vec<ShardInfo>,
|
||||
pub total_capacity: u64,
|
||||
pub total_used: u64,
|
||||
pub rebalance_needed: bool,
|
||||
}
|
||||
|
||||
/// Wait-Free сериализация сообщений
|
||||
pub fn serialize<T: serde::Serialize>(value: &T) -> crate::common::error::Result<Vec<u8>> {
|
||||
rmp_serde::to_vec(value)
|
||||
.map_err(|e| crate::common::error::FalcotError::SerializationError(e.to_string()))
|
||||
}
|
||||
|
||||
/// Wait-Free десериализация сообщений
|
||||
pub fn deserialize<'a, T: serde::Deserialize<'a>>(bytes: &'a [u8]) -> crate::common::error::Result<T> {
|
||||
rmp_serde::from_slice(bytes)
|
||||
.map_err(|e| crate::common::error::FalcotError::SerializationError(e.to_string()))
|
||||
}
|
||||
566
src/common/mod.rs
Executable file
566
src/common/mod.rs
Executable file
@ -0,0 +1,566 @@
|
||||
// src/common/mod.rs
|
||||
//! Общие модули для Futriix
|
||||
//!
|
||||
//! Содержит общие структуры данных, ошибки, протоколы и конфигурацию,
|
||||
//! используемые во всех компонентах системы с wait-free архитектурой.
|
||||
|
||||
use thiserror::Error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Основной тип ошибки для Futriix
|
||||
#[derive(Error, Debug)]
|
||||
pub enum FutriixError {
|
||||
#[error("Configuration error: {0}")]
|
||||
ConfigError(String),
|
||||
|
||||
#[error("Database error: {0}")]
|
||||
DatabaseError(String),
|
||||
|
||||
#[error("Lua error: {0}")]
|
||||
LuaError(String),
|
||||
|
||||
#[error("Network error: {0}")]
|
||||
NetworkError(String),
|
||||
|
||||
#[error("Replication error: {0}")]
|
||||
ReplicationError(String),
|
||||
|
||||
#[error("HTTP error: {0}")]
|
||||
HttpError(String),
|
||||
|
||||
#[error("Serialization error: {0}")]
|
||||
SerializationError(String),
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("CSV error: {0}")]
|
||||
CsvError(String),
|
||||
|
||||
#[error("Unknown error: {0}")]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
// Реализация преобразования из rlua::Error в FutriixError
|
||||
impl From<rlua::Error> for FutriixError {
|
||||
fn from(error: rlua::Error) -> Self {
|
||||
FutriixError::LuaError(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Тип результата для Futriix
|
||||
pub type Result<T> = std::result::Result<T, FutriixError>;
|
||||
|
||||
// Модуль конфигурации
|
||||
pub mod config {
|
||||
use super::*;
|
||||
|
||||
/// Конфигурация сервера
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
#[serde(default = "ServerConfig::default")]
|
||||
pub server: ServerConfig,
|
||||
#[serde(default = "ReplicationConfig::default")]
|
||||
pub replication: ReplicationConfig,
|
||||
#[serde(default = "ClusterConfig::default")] // Новая секция кластера
|
||||
pub cluster: ClusterConfig,
|
||||
#[serde(default = "LuaConfig::default")]
|
||||
pub lua: LuaConfig,
|
||||
#[serde(default = "AclConfig::default")]
|
||||
pub acl: AclConfig,
|
||||
#[serde(default = "TlsConfig::default")]
|
||||
pub tls: TlsConfig,
|
||||
#[serde(default = "CsvConfig::default")]
|
||||
pub csv: CsvConfig,
|
||||
}
|
||||
|
||||
/// Конфигурация серверных параметров
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ServerConfig {
|
||||
#[serde(default = "default_host")]
|
||||
pub host: String,
|
||||
#[serde(default = "default_port")]
|
||||
pub port: u16,
|
||||
#[serde(default)]
|
||||
pub http_port: Option<u16>,
|
||||
#[serde(default)]
|
||||
pub https_port: Option<u16>,
|
||||
#[serde(default)]
|
||||
pub http2_enabled: Option<bool>,
|
||||
#[serde(default = "default_http_enabled")] // Новая директива
|
||||
pub http: bool,
|
||||
#[serde(default = "default_https_enabled")] // Новая директива
|
||||
pub https: bool,
|
||||
}
|
||||
|
||||
/// Конфигурация репликации
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ReplicationConfig {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_master_nodes")]
|
||||
pub master_nodes: Vec<String>,
|
||||
#[serde(default = "default_sync_interval")]
|
||||
pub sync_interval: u64,
|
||||
}
|
||||
|
||||
/// Конфигурация кластера
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ClusterConfig {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_cluster_name")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Конфигурация Lua
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct LuaConfig {
|
||||
#[serde(default = "default_scripts_dir")]
|
||||
pub scripts_dir: String,
|
||||
#[serde(default)]
|
||||
pub auto_execute: Vec<String>,
|
||||
}
|
||||
|
||||
/// Конфигурация ACL
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AclConfig {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_allowed_ips")]
|
||||
pub allowed_ips: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub denied_ips: Vec<String>,
|
||||
}
|
||||
|
||||
/// Конфигурация TLS
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct TlsConfig {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_cert_path")]
|
||||
pub cert_path: String,
|
||||
#[serde(default = "default_key_path")]
|
||||
pub key_path: String,
|
||||
}
|
||||
|
||||
/// Конфигурация CSV
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CsvConfig {
|
||||
#[serde(default = "default_csv_import_dir")]
|
||||
pub import_dir: String,
|
||||
#[serde(default = "default_csv_export_dir")]
|
||||
pub export_dir: String,
|
||||
#[serde(default = "default_max_csv_file_size")]
|
||||
pub max_file_size: u64,
|
||||
}
|
||||
|
||||
// Функции для значений по умолчанию
|
||||
fn default_host() -> String {
|
||||
"127.0.0.1".to_string()
|
||||
}
|
||||
|
||||
fn default_port() -> u16 {
|
||||
8081
|
||||
}
|
||||
|
||||
fn default_http_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_https_enabled() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn default_master_nodes() -> Vec<String> {
|
||||
vec!["127.0.0.1:8081".to_string(), "127.0.0.1:8083".to_string()]
|
||||
}
|
||||
|
||||
fn default_sync_interval() -> u64 {
|
||||
5000
|
||||
}
|
||||
|
||||
fn default_cluster_name() -> String {
|
||||
"futriix-default-cluster".to_string()
|
||||
}
|
||||
|
||||
fn default_scripts_dir() -> String {
|
||||
"lua_scripts".to_string()
|
||||
}
|
||||
|
||||
fn default_allowed_ips() -> Vec<String> {
|
||||
vec!["127.0.0.1".to_string(), "::1".to_string()]
|
||||
}
|
||||
|
||||
fn default_cert_path() -> String {
|
||||
"certs/cert.pem".to_string()
|
||||
}
|
||||
|
||||
fn default_key_path() -> String {
|
||||
"certs/key.pem".to_string()
|
||||
}
|
||||
|
||||
fn default_csv_import_dir() -> String {
|
||||
"/futriix/csv/import".to_string()
|
||||
}
|
||||
|
||||
fn default_csv_export_dir() -> String {
|
||||
"/futriix/csv/export".to_string()
|
||||
}
|
||||
|
||||
fn default_max_csv_file_size() -> u64 {
|
||||
104857600 // 100MB
|
||||
}
|
||||
|
||||
impl Default for ServerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: default_host(),
|
||||
port: default_port(),
|
||||
http_port: Some(8082),
|
||||
https_port: None,
|
||||
http2_enabled: Some(false),
|
||||
http: default_http_enabled(),
|
||||
https: default_https_enabled(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ReplicationConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
master_nodes: default_master_nodes(),
|
||||
sync_interval: default_sync_interval(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ClusterConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
name: default_cluster_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LuaConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scripts_dir: default_scripts_dir(),
|
||||
auto_execute: vec!["init.lua".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AclConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
allowed_ips: default_allowed_ips(),
|
||||
denied_ips: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TlsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
cert_path: default_cert_path(),
|
||||
key_path: default_key_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CsvConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
import_dir: default_csv_import_dir(),
|
||||
export_dir: default_csv_export_dir(),
|
||||
max_file_size: default_max_csv_file_size(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server: ServerConfig::default(),
|
||||
replication: ReplicationConfig::default(),
|
||||
cluster: ClusterConfig::default(),
|
||||
lua: LuaConfig::default(),
|
||||
acl: AclConfig::default(),
|
||||
tls: TlsConfig::default(),
|
||||
csv: CsvConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Загрузка конфигурации из файла
|
||||
pub fn load(path: &str) -> Result<Self> {
|
||||
let path = Path::new(path);
|
||||
|
||||
if !path.exists() {
|
||||
// Создание конфигурации по умолчанию
|
||||
let default_config = Config::default();
|
||||
let toml_content = toml::to_string_pretty(&default_config)
|
||||
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
|
||||
|
||||
fs::write(path, toml_content)
|
||||
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
|
||||
|
||||
println!("Created default configuration file: {}", path.display());
|
||||
return Ok(default_config);
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(path)
|
||||
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
|
||||
|
||||
// Парсим конфигурацию с использованием значений по умолчанию
|
||||
let mut config: Config = toml::from_str(&content)
|
||||
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
|
||||
|
||||
// Убеждаемся, что все поля имеют значения по умолчанию, если они отсутствуют
|
||||
if config.server.host.is_empty() {
|
||||
config.server.host = default_host();
|
||||
}
|
||||
if config.server.port == 0 {
|
||||
config.server.port = default_port();
|
||||
}
|
||||
if config.replication.master_nodes.is_empty() {
|
||||
config.replication.master_nodes = default_master_nodes();
|
||||
}
|
||||
if config.replication.sync_interval == 0 {
|
||||
config.replication.sync_interval = default_sync_interval();
|
||||
}
|
||||
if config.cluster.name.is_empty() {
|
||||
config.cluster.name = default_cluster_name();
|
||||
}
|
||||
if config.lua.scripts_dir.is_empty() {
|
||||
config.lua.scripts_dir = default_scripts_dir();
|
||||
}
|
||||
if config.acl.allowed_ips.is_empty() {
|
||||
config.acl.allowed_ips = default_allowed_ips();
|
||||
}
|
||||
if config.tls.cert_path.is_empty() {
|
||||
config.tls.cert_path = default_cert_path();
|
||||
}
|
||||
if config.tls.key_path.is_empty() {
|
||||
config.tls.key_path = default_key_path();
|
||||
}
|
||||
if config.csv.import_dir.is_empty() {
|
||||
config.csv.import_dir = default_csv_import_dir();
|
||||
}
|
||||
if config.csv.export_dir.is_empty() {
|
||||
config.csv.export_dir = default_csv_export_dir();
|
||||
}
|
||||
if config.csv.max_file_size == 0 {
|
||||
config.csv.max_file_size = default_max_csv_file_size();
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Сохранение конфигурации в файл
|
||||
#[allow(dead_code)]
|
||||
pub fn save(&self, path: &str) -> Result<()> {
|
||||
let toml_content = toml::to_string_pretty(self)
|
||||
.map_err(|e| FutriixError::ConfigError(e.to_string()))?;
|
||||
|
||||
fs::write(path, toml_content)
|
||||
.map_err(|e| FutriixError::ConfigError(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Модуль протокола
|
||||
pub mod protocol {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Команды для выполнения в базе данных
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Command {
|
||||
// Базовые CRUD команды
|
||||
Create {
|
||||
collection: String,
|
||||
document: Vec<u8>,
|
||||
},
|
||||
Read {
|
||||
collection: String,
|
||||
id: String,
|
||||
},
|
||||
Update {
|
||||
collection: String,
|
||||
id: String,
|
||||
document: Vec<u8>,
|
||||
},
|
||||
Delete {
|
||||
collection: String,
|
||||
id: String,
|
||||
},
|
||||
Query {
|
||||
collection: String,
|
||||
filter: Vec<u8>,
|
||||
},
|
||||
|
||||
// Команды для процедур и транзакций
|
||||
CreateProcedure {
|
||||
name: String,
|
||||
code: Vec<u8>,
|
||||
},
|
||||
CallProcedure {
|
||||
name: String,
|
||||
},
|
||||
BeginTransaction {
|
||||
transaction_id: String,
|
||||
},
|
||||
CommitTransaction {
|
||||
transaction_id: String,
|
||||
},
|
||||
RollbackTransaction {
|
||||
transaction_id: String,
|
||||
},
|
||||
|
||||
// Команды для индексов
|
||||
CreateIndex {
|
||||
collection: String,
|
||||
index: crate::server::database::Index,
|
||||
},
|
||||
QueryByIndex {
|
||||
collection: String,
|
||||
index_name: String,
|
||||
value: Vec<u8>,
|
||||
},
|
||||
|
||||
// Команды для шардинга с Raft
|
||||
AddShardNode {
|
||||
node_id: String,
|
||||
address: String,
|
||||
capacity: u64,
|
||||
},
|
||||
RemoveShardNode {
|
||||
node_id: String,
|
||||
},
|
||||
MigrateShard {
|
||||
collection: String,
|
||||
from_node: String,
|
||||
to_node: String,
|
||||
shard_key: String,
|
||||
},
|
||||
RebalanceCluster,
|
||||
GetClusterStatus,
|
||||
StartElection, // Новая команда для Raft выборов
|
||||
GetRaftNodes, // Новая команда для получения Raft узлов
|
||||
|
||||
// Команды для constraints
|
||||
AddConstraint {
|
||||
collection: String,
|
||||
constraint_name: String,
|
||||
constraint_type: String,
|
||||
field: String,
|
||||
value: Vec<u8>,
|
||||
},
|
||||
RemoveConstraint {
|
||||
collection: String,
|
||||
constraint_name: String,
|
||||
},
|
||||
|
||||
// Команды для компрессии
|
||||
EnableCompression {
|
||||
collection: String,
|
||||
algorithm: String,
|
||||
},
|
||||
DisableCompression {
|
||||
collection: String,
|
||||
},
|
||||
|
||||
// Команды для глобальных индексов
|
||||
CreateGlobalIndex {
|
||||
name: String,
|
||||
field: String,
|
||||
unique: bool,
|
||||
},
|
||||
QueryGlobalIndex {
|
||||
index_name: String,
|
||||
value: Vec<u8>,
|
||||
},
|
||||
|
||||
// Новые команды для CSV импорта/экспорта
|
||||
ImportCsv {
|
||||
collection: String,
|
||||
file_path: String,
|
||||
},
|
||||
ExportCsv {
|
||||
collection: String,
|
||||
file_path: String,
|
||||
},
|
||||
ListCsvFiles,
|
||||
GetImportProgress {
|
||||
collection: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Ответы от базы данных
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Response {
|
||||
Success(Vec<u8>),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
/// Сообщение для репликации
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReplicationMessage {
|
||||
pub sequence: u64,
|
||||
pub command: Command,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
/// Структура для информации о шарде
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ShardInfo {
|
||||
pub node_id: String,
|
||||
pub address: String,
|
||||
pub capacity: u64,
|
||||
pub used: u64,
|
||||
pub collections: Vec<String>,
|
||||
}
|
||||
|
||||
/// Структура для информации о Raft узле
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RaftNodeInfo {
|
||||
pub node_id: String,
|
||||
pub address: String,
|
||||
pub state: String, // "leader", "follower", "candidate"
|
||||
pub term: u64,
|
||||
pub last_heartbeat: i64,
|
||||
}
|
||||
|
||||
/// Структура для статуса кластера
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClusterStatus {
|
||||
pub nodes: Vec<ShardInfo>,
|
||||
pub total_capacity: u64,
|
||||
pub total_used: u64,
|
||||
pub rebalance_needed: bool,
|
||||
pub cluster_formed: bool, // Новое поле: собран ли кластер
|
||||
pub leader_exists: bool, // Новое поле: существует ли лидер
|
||||
pub raft_nodes: Vec<RaftNodeInfo>, // Новое поле: список Raft узлов
|
||||
}
|
||||
|
||||
/// Wait-Free сериализация сообщений
|
||||
pub fn serialize<T: serde::Serialize>(value: &T) -> crate::common::Result<Vec<u8>> {
|
||||
rmp_serde::to_vec(value)
|
||||
.map_err(|e| crate::common::FutriixError::SerializationError(e.to_string()))
|
||||
}
|
||||
|
||||
/// Wait-Free десериализация сообщений
|
||||
pub fn deserialize<'a, T: serde::Deserialize<'a>>(bytes: &'a [u8]) -> crate::common::Result<T> {
|
||||
rmp_serde::from_slice(bytes)
|
||||
.map_err(|e| crate::common::FutriixError::SerializationError(e.to_string()))
|
||||
}
|
||||
}
|
||||
595
src/lua_shell.rs
Executable file
595
src/lua_shell.rs
Executable file
@ -0,0 +1,595 @@
|
||||
// src/lua_shell.rs
|
||||
//! Интерактивная Lua оболочка для Futriix
|
||||
//!
|
||||
//! Предоставляет интерфейс для взаимодействия с базой данных через Lua
|
||||
//! и CRUD команды. Использует wait-free доступ к данным через атомарные ссылки.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::server::database::Database;
|
||||
use crate::server::lua_engine::LuaEngine;
|
||||
use crate::server::sharding::ShardingManager;
|
||||
use crate::server::csv_import_export::CsvManager;
|
||||
use crate::common::protocol;
|
||||
use crate::server::database::{Index, IndexType};
|
||||
|
||||
/// Конвертация HEX цвета в ANSI escape code
|
||||
fn hex_to_ansi(hex_color: &str) -> String {
|
||||
let hex = hex_color.trim_start_matches('#');
|
||||
|
||||
if hex.len() == 6 {
|
||||
if let (Ok(r), Ok(g), Ok(b)) = (
|
||||
u8::from_str_radix(&hex[0..2], 16),
|
||||
u8::from_str_radix(&hex[2..4], 16),
|
||||
u8::from_str_radix(&hex[4..6], 16),
|
||||
) {
|
||||
return format!("\x1b[38;2;{};{};{}m", r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
"\x1b[38;2;255;255;255m".to_string()
|
||||
}
|
||||
|
||||
/// Вывод текста с красным цветом для ошибок
|
||||
fn print_error(text: &str) {
|
||||
let red_color = hex_to_ansi("#FF0000");
|
||||
println!("{}{}\x1b[0m", red_color, text);
|
||||
}
|
||||
|
||||
/// Вывод текста с зеленым цветом для успеха
|
||||
fn print_success(text: &str) {
|
||||
let green_color = hex_to_ansi("#00FF00");
|
||||
println!("{}{}\x1b[0m", green_color, text);
|
||||
}
|
||||
|
||||
/// Вывод текста с синим цветом для информации
|
||||
fn print_info(text: &str) {
|
||||
let blue_color = hex_to_ansi("#00bfff");
|
||||
println!("{}{}\x1b[0m", blue_color, text);
|
||||
}
|
||||
|
||||
/// Вывод текста с фисташковым цветом для приглашения Lua
|
||||
fn print_pistachio(text: &str) {
|
||||
let pistachio_color = hex_to_ansi("#93C572"); // Фисташковый цвет
|
||||
println!("{}{}\x1b[0m", pistachio_color, text);
|
||||
}
|
||||
|
||||
/// Интерактивная Lua оболочка
|
||||
pub struct LuaShell {
|
||||
lua_engine: LuaEngine,
|
||||
database: Arc<Database>,
|
||||
sharding_manager: Arc<ShardingManager>,
|
||||
csv_manager: Arc<CsvManager>,
|
||||
inbox_mode: bool,
|
||||
}
|
||||
|
||||
impl LuaShell {
|
||||
pub fn new(
|
||||
lua_engine: LuaEngine,
|
||||
database: Arc<Database>,
|
||||
sharding_manager: Arc<ShardingManager>,
|
||||
csv_manager: Arc<CsvManager>,
|
||||
) -> Self {
|
||||
Self {
|
||||
lua_engine,
|
||||
database,
|
||||
sharding_manager,
|
||||
csv_manager,
|
||||
inbox_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Запуск интерактивной оболочки
|
||||
pub async fn run(&mut self) -> Result<()> {
|
||||
let stdin = tokio::io::stdin();
|
||||
let mut reader = BufReader::new(stdin).lines();
|
||||
|
||||
// Выводим приветственное сообщение при запуске Lua интерпретатора
|
||||
print_pistachio("Lua interpreter started. Type 'inbox.start' for database commands or Lua code.");
|
||||
println!();
|
||||
|
||||
loop {
|
||||
if self.inbox_mode {
|
||||
let inbox_prompt_color = hex_to_ansi("#00bfff");
|
||||
print!("{}futriix:~>\x1b[0m ", inbox_prompt_color);
|
||||
} else {
|
||||
// ПРИГЛАШЕНИЕ LUA ТЕПЕРЬ ФИСТАШКОВОГО ЦВЕТА
|
||||
let pistachio_color = hex_to_ansi("#93C572");
|
||||
print!("{}lua>\x1b[0m ", pistachio_color);
|
||||
}
|
||||
|
||||
let _ = std::io::Write::flush(&mut std::io::stdout());
|
||||
|
||||
let line = match reader.next_line().await {
|
||||
Ok(Some(line)) => line,
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
eprintln!("Read error: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let input = line.trim();
|
||||
|
||||
match input {
|
||||
"exit" | "quit" => break,
|
||||
"inbox.start" => {
|
||||
self.inbox_mode = true;
|
||||
print_success("Entering database mode. Type CRUD commands or 'inbox.stop' to exit.");
|
||||
continue;
|
||||
}
|
||||
"inbox.stop" if self.inbox_mode => {
|
||||
self.inbox_mode = false;
|
||||
print_success("Exiting database mode. Back to Lua interpreter.");
|
||||
continue;
|
||||
}
|
||||
"help" if self.inbox_mode => {
|
||||
self.show_help().await?;
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if self.inbox_mode {
|
||||
self.handle_inbox_command(input).await?;
|
||||
} else {
|
||||
self.handle_lua_command(input).await?;
|
||||
}
|
||||
}
|
||||
|
||||
print_info("Shutting down Futriix server...");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Обработка Lua команд
|
||||
async fn handle_lua_command(&self, input: &str) -> Result<()> {
|
||||
if input.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match self.lua_engine.execute_script(input) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let error_msg = e.to_string();
|
||||
if error_msg.contains("Lua error: syntax error:") || error_msg.contains("Unknown command:") {
|
||||
print_error(&error_msg);
|
||||
} else {
|
||||
eprintln!("Lua error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Обработка команд inbox (CRUD + новые команды)
|
||||
async fn handle_inbox_command(&self, input: &str) -> Result<()> {
|
||||
let parts: Vec<&str> = input.split_whitespace().collect();
|
||||
|
||||
if parts.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match parts[0] {
|
||||
// Базовые CRUD команды
|
||||
"create" => self.handle_create(parts).await,
|
||||
"read" => self.handle_read(parts).await,
|
||||
"update" => self.handle_update(parts).await,
|
||||
"delete" => self.handle_delete(parts).await,
|
||||
"list" => self.handle_list(parts).await,
|
||||
|
||||
// Новые команды для управления кластером
|
||||
"cluster.status" => self.handle_cluster_status(parts).await,
|
||||
"add.node" => self.handle_add_node(parts).await,
|
||||
"evict.node" => self.handle_evict_node(parts).await,
|
||||
"list.raft.nodes" => self.handle_list_raft_nodes(parts).await,
|
||||
"cluster.rebalance" => self.handle_cluster_rebalance(parts).await,
|
||||
|
||||
// Новые команды для CSV операций
|
||||
"csv" => self.handle_csv(parts).await,
|
||||
|
||||
"help" => self.show_help().await,
|
||||
_ => {
|
||||
let error_msg = format!("Unknown command: {}. Type 'help' for available commands.", parts[0]);
|
||||
print_error(&error_msg);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Новые методы для управления кластером
|
||||
async fn handle_cluster_status(&self, _parts: Vec<&str>) -> Result<()> {
|
||||
match self.sharding_manager.get_cluster_status() {
|
||||
Ok(status) => {
|
||||
println!("Cluster Status:");
|
||||
println!(" Formed: {}", status.cluster_formed);
|
||||
println!(" Leader Exists: {}", status.leader_exists);
|
||||
println!(" Total Capacity: {}", status.total_capacity);
|
||||
println!(" Total Used: {}", status.total_used);
|
||||
println!(" Nodes: {}", status.nodes.len());
|
||||
for node in status.nodes {
|
||||
println!(" - {}: {} ({}% used)", node.node_id, node.address, (node.used as f64 / node.capacity as f64) * 100.0);
|
||||
}
|
||||
println!(" Raft Nodes: {}", status.raft_nodes.len());
|
||||
for raft_node in status.raft_nodes {
|
||||
println!(" - {}: {} (term: {}, state: {})", raft_node.node_id, raft_node.address, raft_node.term, raft_node.state);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error getting cluster status: {}", e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_add_node(&self, parts: Vec<&str>) -> Result<()> {
|
||||
if parts.len() < 2 {
|
||||
println!("Usage: add.node <node_url> or add.node <node_ip>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let node_address = parts[1].to_string();
|
||||
let node_id = format!("node_{}", uuid::Uuid::new_v4().to_string()[..8].to_string());
|
||||
|
||||
match self.sharding_manager.add_node(node_id.clone(), node_address.clone(), 1024 * 1024 * 1024) {
|
||||
Ok(_) => {
|
||||
println!("Node '{}' added to cluster at address '{}'", node_id, node_address);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error adding node: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_evict_node(&self, parts: Vec<&str>) -> Result<()> {
|
||||
if parts.len() < 2 {
|
||||
println!("Usage: evict.node <node_url> or evict.node <node_ip>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let node_address = parts[1].to_string();
|
||||
|
||||
// Находим node_id по адресу
|
||||
let mut node_id_to_remove = None;
|
||||
for entry in self.sharding_manager.get_nodes() {
|
||||
if entry.address == node_address {
|
||||
node_id_to_remove = Some(entry.node_id.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(node_id) = node_id_to_remove {
|
||||
match self.sharding_manager.remove_node(&node_id) {
|
||||
Ok(_) => {
|
||||
println!("Node '{}' at address '{}' removed from cluster", node_id, node_address);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error removing node: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Node with address '{}' not found in cluster", node_address);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_list_raft_nodes(&self, _parts: Vec<&str>) -> Result<()> {
|
||||
let raft_nodes = self.sharding_manager.get_raft_nodes();
|
||||
println!("Raft Nodes ({}):", raft_nodes.len());
|
||||
for node in raft_nodes {
|
||||
println!(" - {}: {} (term: {}, state: {:?}, last_heartbeat: {})",
|
||||
node.node_id, node.address, node.term, node.state, node.last_heartbeat);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_cluster_rebalance(&self, _parts: Vec<&str>) -> Result<()> {
|
||||
match self.sharding_manager.rebalance_cluster() {
|
||||
Ok(_) => {
|
||||
println!("Cluster rebalancing completed successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error rebalancing cluster: {}", e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Новый метод для CSV операций
|
||||
async fn handle_csv(&self, parts: Vec<&str>) -> Result<()> {
|
||||
if parts.len() < 2 {
|
||||
println!("Usage: csv import <collection> <file_path>");
|
||||
println!(" csv export <collection> <file_path>");
|
||||
println!(" csv list");
|
||||
println!(" csv progress <collection>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match parts[1] {
|
||||
"import" => {
|
||||
if parts.len() < 4 {
|
||||
println!("Usage: csv import <collection> <file_path>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = parts[2].to_string();
|
||||
let file_path = parts[3].to_string();
|
||||
|
||||
match self.csv_manager.import_csv(&collection, &file_path) {
|
||||
Ok(count) => {
|
||||
println!("Successfully imported {} records from '{}'", count, file_path);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error importing CSV: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
"export" => {
|
||||
if parts.len() < 4 {
|
||||
println!("Usage: csv export <collection> <file_path>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = parts[2].to_string();
|
||||
let file_path = parts[3].to_string();
|
||||
|
||||
match self.csv_manager.export_csv(&collection, &file_path) {
|
||||
Ok(count) => {
|
||||
println!("Successfully exported {} records to '{}'", count, file_path);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error exporting CSV: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
"list" => {
|
||||
match self.csv_manager.list_csv_files() {
|
||||
Ok(files) => {
|
||||
if files.is_empty() {
|
||||
println!("No CSV files found");
|
||||
} else {
|
||||
println!("CSV files:");
|
||||
for file in files {
|
||||
println!(" - {}", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error listing CSV files: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
"progress" => {
|
||||
if parts.len() < 3 {
|
||||
println!("Usage: csv progress <collection>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = parts[2].to_string();
|
||||
let progress = self.csv_manager.get_import_progress(&collection);
|
||||
println!("Import progress for '{}': {:.2}%", collection, progress);
|
||||
}
|
||||
_ => {
|
||||
println!("Usage: csv import <collection> <file_path>");
|
||||
println!(" csv export <collection> <file_path>");
|
||||
println!(" csv list");
|
||||
println!(" csv progress <collection>");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Базовые методы CRUD (упрощенные)
|
||||
async fn handle_create(&self, parts: Vec<&str>) -> Result<()> {
|
||||
if parts.len() < 3 {
|
||||
println!("Usage: create <collection> <json_data>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = parts[1].to_string();
|
||||
let document = parts[2..].join(" ").into_bytes();
|
||||
|
||||
let command = protocol::Command::Create {
|
||||
collection,
|
||||
document,
|
||||
};
|
||||
|
||||
match self.database.execute_command(command) {
|
||||
Ok(response) => {
|
||||
if let protocol::Response::Success(data) = response {
|
||||
if let Ok(id) = String::from_utf8(data) {
|
||||
println!("Document created with ID: {}", id);
|
||||
} else {
|
||||
println!("Document created successfully");
|
||||
}
|
||||
} else if let protocol::Response::Error(e) = response {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_read(&self, parts: Vec<&str>) -> Result<()> {
|
||||
if parts.len() < 3 {
|
||||
println!("Usage: read <collection> <id>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = parts[1].to_string();
|
||||
let id = parts[2].to_string();
|
||||
|
||||
let command = protocol::Command::Read {
|
||||
collection,
|
||||
id,
|
||||
};
|
||||
|
||||
match self.database.execute_command(command) {
|
||||
Ok(response) => {
|
||||
if let protocol::Response::Success(data) = response {
|
||||
if let Ok(document) = String::from_utf8(data) {
|
||||
println!("{}", document);
|
||||
} else {
|
||||
println!("Document read successfully (binary data)");
|
||||
}
|
||||
} else if let protocol::Response::Error(e) = response {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_update(&self, parts: Vec<&str>) -> Result<()> {
|
||||
if parts.len() < 4 {
|
||||
println!("Usage: update <collection> <id> <json_data>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = parts[1].to_string();
|
||||
let id = parts[2].to_string();
|
||||
let document = parts[3..].join(" ").into_bytes();
|
||||
|
||||
let command = protocol::Command::Update {
|
||||
collection,
|
||||
id,
|
||||
document,
|
||||
};
|
||||
|
||||
match self.database.execute_command(command) {
|
||||
Ok(response) => {
|
||||
if let protocol::Response::Success(_) = response {
|
||||
println!("Document updated successfully");
|
||||
} else if let protocol::Response::Error(e) = response {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_delete(&self, parts: Vec<&str>) -> Result<()> {
|
||||
if parts.len() < 3 {
|
||||
println!("Usage: delete <collection> <id>");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = parts[1].to_string();
|
||||
let id = parts[2].to_string();
|
||||
|
||||
let command = protocol::Command::Delete {
|
||||
collection,
|
||||
id,
|
||||
};
|
||||
|
||||
match self.database.execute_command(command) {
|
||||
Ok(response) => {
|
||||
if let protocol::Response::Success(_) = response {
|
||||
println!("Document deleted successfully");
|
||||
} else if let protocol::Response::Error(e) = response {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_list(&self, parts: Vec<&str>) -> Result<()> {
|
||||
if parts.len() < 2 {
|
||||
println!("Usage: list <collection> [filter]");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = parts[1].to_string();
|
||||
let filter = if parts.len() > 2 {
|
||||
parts[2..].join(" ").into_bytes()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let command = protocol::Command::Query {
|
||||
collection,
|
||||
filter,
|
||||
};
|
||||
|
||||
match self.database.execute_command(command) {
|
||||
Ok(response) => {
|
||||
if let protocol::Response::Success(data) = response {
|
||||
if let Ok(documents) = String::from_utf8(data) {
|
||||
// Используем std::result::Result вместо нашего Result
|
||||
let parsed: std::result::Result<Value, _> = serde_json::from_str(&documents);
|
||||
match parsed {
|
||||
Ok(value) => {
|
||||
println!("{}", serde_json::to_string_pretty(&value).unwrap());
|
||||
}
|
||||
Err(_) => {
|
||||
println!("{}", documents);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Documents read successfully (binary data)");
|
||||
}
|
||||
} else if let protocol::Response::Error(e) = response {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Показать справку по командам
|
||||
async fn show_help(&self) -> Result<()> {
|
||||
println!("Available commands:");
|
||||
println!(" Basic CRUD:");
|
||||
println!(" create <collection> <json_data> - Create document");
|
||||
println!(" read <collection> <id> - Read document");
|
||||
println!(" update <collection> <id> <json> - Update document");
|
||||
println!(" delete <collection> <id> - Delete document");
|
||||
println!(" list <collection> [filter] - List documents");
|
||||
println!(" Cluster Management:");
|
||||
println!(" cluster.status - Show cluster status");
|
||||
println!(" add.node <node_url> - Add node to cluster");
|
||||
println!(" evict.node <node_url> - Remove node from cluster");
|
||||
println!(" list.raft.nodes - List Raft nodes");
|
||||
println!(" cluster.rebalance - Rebalance cluster (shards and nodes)");
|
||||
println!(" CSV Operations:");
|
||||
println!(" csv import <coll> <file> - Import CSV to collection");
|
||||
println!(" csv export <coll> <file> - Export collection to CSV");
|
||||
println!(" csv list - List CSV files");
|
||||
println!(" csv progress <coll> - Show import progress");
|
||||
println!(" Other:");
|
||||
println!(" inbox.stop - Exit database mode");
|
||||
println!(" help - Show this help");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
158
src/main.rs
Executable file
158
src/main.rs
Executable file
@ -0,0 +1,158 @@
|
||||
// src/main.rs
|
||||
//! Главный модуль сервера Futriix
|
||||
//!
|
||||
//! Точка входа в приложение, инициализирует сервер и запускает его.
|
||||
//! Использует wait-free архитектуру с lock-free структурами данных.
|
||||
|
||||
mod common;
|
||||
mod server;
|
||||
mod lua_shell;
|
||||
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::common::FutriixError;
|
||||
|
||||
/// Функция для логирования в файл
|
||||
fn log_to_file(message: &str) {
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("futriix.log")
|
||||
{
|
||||
Ok(mut file) => {
|
||||
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
|
||||
let log_message = format!("[{}] {}\n", timestamp, message);
|
||||
let _ = file.write_all(log_message.as_bytes());
|
||||
}
|
||||
Err(e) => eprintln!("Failed to write to log file: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Простая структура для аргументов командной строки
|
||||
struct Args {
|
||||
config: String,
|
||||
debug: bool,
|
||||
http_port: Option<u16>,
|
||||
https_port: Option<u16>,
|
||||
host: Option<String>,
|
||||
}
|
||||
|
||||
/// Простой парсер аргументов командной строки
|
||||
fn parse_args() -> Args {
|
||||
let mut args = Args {
|
||||
config: "config.toml".to_string(),
|
||||
debug: false,
|
||||
http_port: None,
|
||||
https_port: None,
|
||||
host: None,
|
||||
};
|
||||
|
||||
let mut iter = env::args().skip(1);
|
||||
while let Some(arg) = iter.next() {
|
||||
match arg.as_str() {
|
||||
"--config" | "-c" => {
|
||||
if let Some(value) = iter.next() {
|
||||
args.config = value;
|
||||
}
|
||||
}
|
||||
"--debug" | "-d" => {
|
||||
args.debug = true;
|
||||
}
|
||||
"--http-port" => {
|
||||
if let Some(value) = iter.next() {
|
||||
if let Ok(port) = value.parse() {
|
||||
args.http_port = Some(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
"--https-port" => {
|
||||
if let Some(value) = iter.next() {
|
||||
if let Ok(port) = value.parse() {
|
||||
args.https_port = Some(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
"--host" => {
|
||||
if let Some(value) = iter.next() {
|
||||
args.host = Some(value);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if arg.starts_with("--config=") {
|
||||
args.config = arg.trim_start_matches("--config=").to_string();
|
||||
} else if arg.starts_with("-c=") {
|
||||
args.config = arg.trim_start_matches("-c=").to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
/// Функция для вывода текста с ANSI цветом
|
||||
fn print_colored(text: &str, ansi_color: &str) {
|
||||
println!("{}{}\x1b[0m", ansi_color, text);
|
||||
}
|
||||
|
||||
/// Конвертация HEX цвета в ANSI escape code
|
||||
fn hex_to_ansi(hex_color: &str) -> String {
|
||||
let hex = hex_color.trim_start_matches('#');
|
||||
|
||||
if hex.len() == 6 {
|
||||
if let (Ok(r), Ok(g), Ok(b)) = (
|
||||
u8::from_str_radix(&hex[0..2], 16),
|
||||
u8::from_str_radix(&hex[2..4], 16),
|
||||
u8::from_str_radix(&hex[4..6], 16),
|
||||
) {
|
||||
return format!("\x1b[38;2;{};{};{}m", r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
"\x1b[38;2;255;255;255m".to_string()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), FutriixError> {
|
||||
// Инициализация логирования в файл
|
||||
log_to_file("Starting Futriix server");
|
||||
|
||||
// Вывод приветственного сообщения с цветом #00bfff перед загрузкой конфигурации
|
||||
let color_code = hex_to_ansi("#00bfff");
|
||||
println!(); // Добавляем пустую строку перед фразой
|
||||
print_colored("Futriix Database Server", &color_code);
|
||||
print_colored("futriix 3i²(by 26.11.2025)", &color_code);
|
||||
println!(); // Добавляем пустую строку после фразы
|
||||
|
||||
// Парсим аргументы командной строки
|
||||
let args = parse_args();
|
||||
let config_path = args.config;
|
||||
|
||||
let message = format!("Loading configuration from: {}", config_path);
|
||||
println!("{}", message);
|
||||
log_to_file(&message);
|
||||
|
||||
// Создание и запуск сервера
|
||||
match server::FutriixServer::new(&config_path).await {
|
||||
Ok(server) => {
|
||||
log_to_file("Server created successfully");
|
||||
if let Err(e) = server.run().await {
|
||||
let error_message = format!("Server error: {}", e);
|
||||
eprintln!("{}", error_message);
|
||||
log_to_file(&error_message);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_message = format!("Failed to create server: {}", e);
|
||||
eprintln!("{}", error_message);
|
||||
log_to_file(&error_message);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
log_to_file("Futriix server stopped");
|
||||
Ok(())
|
||||
}
|
||||
219
src/server/csv_import_export.rs
Executable file
219
src/server/csv_import_export.rs
Executable file
@ -0,0 +1,219 @@
|
||||
// src/server/csv_import_export.rs
|
||||
//! Модуль для импорта/экспорта данных в формате CSV
|
||||
//!
|
||||
//! Обеспечивает lock-free операции импорта CSV в базу данных
|
||||
//! и экспорта коллекций в CSV формат.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
use csv::{Reader, Writer};
|
||||
use serde_json::Value;
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::common::config::CsvConfig;
|
||||
use crate::server::database::Database;
|
||||
|
||||
/// Менеджер CSV операций
|
||||
#[derive(Clone)]
|
||||
pub struct CsvManager {
|
||||
database: Arc<Database>,
|
||||
config: CsvConfig,
|
||||
import_progress: Arc<DashMap<String, f64>>, // Lock-free отслеживание прогресса
|
||||
}
|
||||
|
||||
impl CsvManager {
|
||||
/// Создание нового менеджера CSV
|
||||
pub fn new(database: Arc<Database>, config: CsvConfig) -> Self {
|
||||
Self {
|
||||
database,
|
||||
config,
|
||||
import_progress: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Импорт CSV файла в коллекцию
|
||||
pub fn import_csv(&self, collection_name: &str, file_path: &str) -> Result<usize> {
|
||||
println!("Importing CSV file '{}' into collection '{}'", file_path, collection_name);
|
||||
|
||||
let file = File::open(file_path)
|
||||
.map_err(|e| crate::common::FutriixError::IoError(e))?;
|
||||
|
||||
let mut reader = Reader::from_reader(BufReader::new(file));
|
||||
|
||||
// Получаем заголовки заранее, чтобы избежать множественных заимствований
|
||||
let headers: Vec<String> = reader.headers()
|
||||
.map_err(|e| crate::common::FutriixError::CsvError(e.to_string()))?
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
let mut record_count = 0;
|
||||
|
||||
// Устанавливаем начальный прогресс
|
||||
self.import_progress.insert(collection_name.to_string(), 0.0);
|
||||
|
||||
for result in reader.records() {
|
||||
let record = result
|
||||
.map_err(|e| crate::common::FutriixError::CsvError(e.to_string()))?;
|
||||
|
||||
// Преобразуем CSV запись в JSON документ
|
||||
let mut document = serde_json::Map::new();
|
||||
|
||||
for (i, field) in record.iter().enumerate() {
|
||||
let header = if i < headers.len() {
|
||||
&headers[i]
|
||||
} else {
|
||||
&format!("field_{}", i)
|
||||
};
|
||||
|
||||
// Пытаемся определить тип данных
|
||||
let value = if let Ok(num) = field.parse::<f64>() {
|
||||
Value::Number(serde_json::Number::from_f64(num).unwrap_or(serde_json::Number::from(0)))
|
||||
} else if field.eq_ignore_ascii_case("true") {
|
||||
Value::Bool(true)
|
||||
} else if field.eq_ignore_ascii_case("false") {
|
||||
Value::Bool(false)
|
||||
} else {
|
||||
Value::String(field.to_string())
|
||||
};
|
||||
|
||||
document.insert(header.to_string(), value);
|
||||
}
|
||||
|
||||
// Сохраняем документ в базу данных
|
||||
let json_value = Value::Object(document);
|
||||
let json_string = serde_json::to_string(&json_value)
|
||||
.map_err(|e| crate::common::FutriixError::DatabaseError(e.to_string()))?;
|
||||
|
||||
let command = crate::common::protocol::Command::Create {
|
||||
collection: collection_name.to_string(),
|
||||
document: json_string.into_bytes(),
|
||||
};
|
||||
|
||||
self.database.execute_command(command)?;
|
||||
record_count += 1;
|
||||
|
||||
// Обновляем прогресс каждые 100 записей
|
||||
if record_count % 100 == 0 {
|
||||
let progress = (record_count as f64) / 1000.0; // Примерный расчет
|
||||
self.import_progress.insert(collection_name.to_string(), progress.min(100.0));
|
||||
println!("Imported {} records...", record_count);
|
||||
}
|
||||
}
|
||||
|
||||
// Завершаем импорт
|
||||
self.import_progress.insert(collection_name.to_string(), 100.0);
|
||||
println!("Successfully imported {} records into collection '{}'", record_count, collection_name);
|
||||
|
||||
Ok(record_count)
|
||||
}
|
||||
|
||||
/// Экспорт коллекции в CSV файл
|
||||
pub fn export_csv(&self, collection_name: &str, file_path: &str) -> Result<usize> {
|
||||
println!("Exporting collection '{}' to CSV file '{}'", collection_name, file_path);
|
||||
|
||||
let file = File::create(file_path)
|
||||
.map_err(|e| crate::common::FutriixError::IoError(e))?;
|
||||
|
||||
let mut writer = Writer::from_writer(BufWriter::new(file));
|
||||
|
||||
// Получаем все документы из коллекции
|
||||
let command = crate::common::protocol::Command::Query {
|
||||
collection: collection_name.to_string(),
|
||||
filter: vec![], // Без фильтра
|
||||
};
|
||||
|
||||
let response = self.database.execute_command(command)?;
|
||||
|
||||
let documents = match response {
|
||||
crate::common::protocol::Response::Success(data) => {
|
||||
serde_json::from_slice::<Vec<Value>>(&data)
|
||||
.map_err(|e| crate::common::FutriixError::DatabaseError(e.to_string()))?
|
||||
}
|
||||
crate::common::protocol::Response::Error(e) => {
|
||||
return Err(crate::common::FutriixError::DatabaseError(e));
|
||||
}
|
||||
};
|
||||
|
||||
if documents.is_empty() {
|
||||
println!("Collection '{}' is empty", collection_name);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Определяем заголовки из первого документа
|
||||
let first_doc = &documents[0];
|
||||
let headers: Vec<String> = if let Value::Object(obj) = first_doc {
|
||||
obj.keys().map(|k| k.to_string()).collect()
|
||||
} else {
|
||||
vec!["data".to_string()]
|
||||
};
|
||||
|
||||
// Записываем заголовки
|
||||
writer.write_record(&headers)
|
||||
.map_err(|e| crate::common::FutriixError::CsvError(e.to_string()))?;
|
||||
|
||||
let mut record_count = 0;
|
||||
|
||||
// Записываем данные
|
||||
for document in documents {
|
||||
if let Value::Object(obj) = document {
|
||||
let mut record = Vec::new();
|
||||
|
||||
// Сохраняем порядок полей согласно заголовкам
|
||||
for header in &headers {
|
||||
let value = obj.get(header).unwrap_or(&Value::Null);
|
||||
let value_str = match value {
|
||||
Value::String(s) => s.clone(),
|
||||
Value::Number(n) => n.to_string(),
|
||||
Value::Bool(b) => b.to_string(),
|
||||
Value::Null => "".to_string(),
|
||||
_ => value.to_string(),
|
||||
};
|
||||
record.push(value_str);
|
||||
}
|
||||
|
||||
writer.write_record(&record)
|
||||
.map_err(|e| crate::common::FutriixError::CsvError(e.to_string()))?;
|
||||
record_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
writer.flush()
|
||||
.map_err(|e| crate::common::FutriixError::CsvError(e.to_string()))?;
|
||||
|
||||
println!("Successfully exported {} records to '{}'", record_count, file_path);
|
||||
Ok(record_count)
|
||||
}
|
||||
|
||||
/// Получение прогресса импорта
|
||||
pub fn get_import_progress(&self, collection_name: &str) -> f64 {
|
||||
self.import_progress.get(collection_name)
|
||||
.map(|entry| *entry.value())
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
/// Список CSV файлов в директории
|
||||
pub fn list_csv_files(&self) -> Result<Vec<String>> {
|
||||
let csv_dir = &self.config.import_dir;
|
||||
let mut csv_files = Vec::new();
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir(csv_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
if let Some(extension) = path.extension() {
|
||||
if extension == "csv" {
|
||||
if let Some(file_name) = path.file_name() {
|
||||
csv_files.push(file_name.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(csv_files)
|
||||
}
|
||||
}
|
||||
718
src/server/database.rs
Executable file
718
src/server/database.rs
Executable file
@ -0,0 +1,718 @@
|
||||
// src/server/database.rs
|
||||
//! Wait-Free документо-ориентированная база данных Futriix
|
||||
//!
|
||||
//! Реализует wait-free доступ к данным с использованием атомарных
|
||||
//! ссылок и lock-free структур данных для максимальной производительности.
|
||||
//! Автоматически добавляет временные метки ко всем операциям с документами.
|
||||
|
||||
#![allow(unused_imports)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::RwLock;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::common::protocol;
|
||||
|
||||
/// Триггеры для коллекций
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum TriggerEvent {
|
||||
BeforeCreate,
|
||||
AfterCreate,
|
||||
BeforeUpdate,
|
||||
AfterUpdate,
|
||||
BeforeDelete,
|
||||
AfterDelete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Trigger {
|
||||
pub name: String,
|
||||
pub event: TriggerEvent,
|
||||
pub collection: String,
|
||||
pub lua_code: String,
|
||||
}
|
||||
|
||||
/// Типы индексов
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum IndexType {
|
||||
Primary,
|
||||
Secondary,
|
||||
}
|
||||
|
||||
/// Структура индекса
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Index {
|
||||
pub name: String,
|
||||
pub index_type: IndexType,
|
||||
pub field: String,
|
||||
pub unique: bool,
|
||||
}
|
||||
|
||||
/// Wait-Free коллекция документов
|
||||
#[derive(Clone)]
|
||||
pub struct Collection {
|
||||
name: String,
|
||||
documents: Arc<RwLock<std::collections::HashMap<String, Vec<u8>>>>,
|
||||
sequence: Arc<AtomicU64>,
|
||||
triggers: Arc<RwLock<Vec<Trigger>>>,
|
||||
indexes: Arc<RwLock<std::collections::HashMap<String, Index>>>,
|
||||
index_data: Arc<DashMap<String, std::collections::HashMap<String, Vec<String>>>>,
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
/// Создание новой wait-free коллекции
|
||||
pub fn new(name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
documents: Arc::new(RwLock::new(std::collections::HashMap::new())),
|
||||
sequence: Arc::new(AtomicU64::new(0)),
|
||||
triggers: Arc::new(RwLock::new(Vec::new())),
|
||||
indexes: Arc::new(RwLock::new(std::collections::HashMap::new())),
|
||||
index_data: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Функция для логирования операций с временной меткой
|
||||
fn log_operation(&self, operation: &str, id: &str) {
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
|
||||
let log_message = format!("[{}] Collection: '{}', Operation: '{}', Document ID: '{}'\n",
|
||||
timestamp, self.name, operation, id);
|
||||
|
||||
// Логируем в файл
|
||||
if let Ok(mut file) = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("futriix.log")
|
||||
{
|
||||
let _ = file.write_all(log_message.as_bytes());
|
||||
}
|
||||
|
||||
// Также выводим в консоль для отладки
|
||||
println!("{}", log_message.trim());
|
||||
}
|
||||
|
||||
/// Добавление временной метки к документу
|
||||
fn add_timestamp_to_document(&self, document: Vec<u8>, operation: &str) -> Result<Vec<u8>> {
|
||||
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
|
||||
|
||||
// Парсим документ как JSON
|
||||
let mut doc_value: Value = serde_json::from_slice(&document)
|
||||
.map_err(|e| crate::common::FutriixError::DatabaseError(e.to_string()))?;
|
||||
|
||||
// Добавляем временные метки
|
||||
if let Value::Object(ref mut obj) = doc_value {
|
||||
obj.insert("_timestamp".to_string(), Value::String(timestamp.clone()));
|
||||
obj.insert("_operation".to_string(), Value::String(operation.to_string()));
|
||||
}
|
||||
|
||||
// Сериализуем обратно в байты
|
||||
serde_json::to_vec(&doc_value)
|
||||
.map_err(|e| crate::common::FutriixError::DatabaseError(e.to_string()))
|
||||
}
|
||||
|
||||
/// Добавление триггера
|
||||
pub fn add_trigger(&self, trigger: Trigger) -> Result<()> {
|
||||
let mut triggers = self.triggers.write()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
triggers.push(trigger);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Получение триггеров для события
|
||||
#[allow(dead_code)]
|
||||
pub fn get_triggers_for_event(&self, event: TriggerEvent) -> Result<Vec<Trigger>> {
|
||||
let triggers = self.triggers.read()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
Ok(triggers.iter()
|
||||
.filter(|t| t.event == event)
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Создание индекса
|
||||
pub fn create_index(&self, index: Index) -> Result<()> {
|
||||
let mut indexes = self.indexes.write()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
if indexes.contains_key(&index.name) {
|
||||
return Err(crate::common::FutriixError::DatabaseError(
|
||||
format!("Index already exists: {}", index.name)
|
||||
));
|
||||
}
|
||||
|
||||
// Создаем структуру для хранения данных индекса
|
||||
self.index_data.insert(index.name.clone(), std::collections::HashMap::new());
|
||||
|
||||
let index_clone = index.clone();
|
||||
indexes.insert(index.name.clone(), index);
|
||||
|
||||
// Перестраиваем индекс для существующих документов
|
||||
self.rebuild_index(&index_clone.name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Перестроение индекса
|
||||
fn rebuild_index(&self, index_name: &str) -> Result<()> {
|
||||
let indexes = self.indexes.read()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
let index = indexes.get(index_name)
|
||||
.ok_or_else(|| crate::common::FutriixError::DatabaseError(
|
||||
format!("Index not found: {}", index_name)
|
||||
))?;
|
||||
|
||||
let documents = self.documents.read()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
let mut index_map = std::collections::HashMap::new();
|
||||
|
||||
for (id, document_bytes) in documents.iter() {
|
||||
if let Ok(document) = serde_json::from_slice::<Value>(document_bytes) {
|
||||
if let Some(field_value) = document.get(&index.field) {
|
||||
// Конвертируем значение в строку для использования в HashMap
|
||||
let value_str = field_value.to_string();
|
||||
let entry = index_map.entry(value_str).or_insert_with(Vec::new);
|
||||
entry.push(id.clone());
|
||||
|
||||
// Проверка уникальности для уникальных индексов
|
||||
if index.unique && entry.len() > 1 {
|
||||
return Err(crate::common::FutriixError::DatabaseError(
|
||||
format!("Duplicate value {} for unique index {}", field_value, index_name)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.index_data.insert(index_name.to_string(), index_map);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Обновление индекса при изменении документа
|
||||
fn update_indexes(&self, old_document: Option<&[u8]>, new_document: &[u8], document_id: &str) -> Result<()> {
|
||||
let indexes = self.indexes.read()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
let new_doc_value: Value = serde_json::from_slice(new_document)
|
||||
.map_err(|e| crate::common::FutriixError::DatabaseError(e.to_string()))?;
|
||||
|
||||
let old_doc_value: Option<Value> = old_document
|
||||
.and_then(|doc| serde_json::from_slice(doc).ok());
|
||||
|
||||
for (index_name, index) in indexes.iter() {
|
||||
if let Some(mut index_map) = self.index_data.get_mut(index_name) {
|
||||
// Удаляем старые значения из индекса
|
||||
if let Some(old_doc) = &old_doc_value {
|
||||
if let Some(old_value) = old_doc.get(&index.field) {
|
||||
let old_value_str = old_value.to_string();
|
||||
if let Some(entries) = index_map.get_mut(&old_value_str) {
|
||||
entries.retain(|id| id != document_id);
|
||||
if entries.is_empty() {
|
||||
index_map.remove(&old_value_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем новые значения в индекс
|
||||
if let Some(new_value) = new_doc_value.get(&index.field) {
|
||||
let new_value_str = new_value.to_string();
|
||||
let entries = index_map.entry(new_value_str).or_insert_with(Vec::new);
|
||||
|
||||
// Проверка уникальности
|
||||
if index.unique && !entries.is_empty() && entries[0] != document_id {
|
||||
return Err(crate::common::FutriixError::DatabaseError(
|
||||
format!("Duplicate value {} for unique index {}", new_value, index_name)
|
||||
));
|
||||
}
|
||||
|
||||
if !entries.contains(&document_id.to_string()) {
|
||||
entries.push(document_id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Поиск по индексу
|
||||
pub fn query_by_index(&self, index_name: &str, value: &Value) -> Result<Vec<String>> {
|
||||
let index_map = self.index_data.get(index_name)
|
||||
.ok_or_else(|| crate::common::FutriixError::DatabaseError(
|
||||
format!("Index not found: {}", index_name)
|
||||
))?;
|
||||
|
||||
let value_str = value.to_string();
|
||||
Ok(index_map.get(&value_str).cloned().unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Wait-Free создание документа с временной меткой
|
||||
pub fn create_document(&self, document: Vec<u8>) -> Result<String> {
|
||||
let id = Uuid::new_v4().to_string();
|
||||
let seq = self.sequence.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// Добавляем временную метку к документу
|
||||
let document_with_timestamp = self.add_timestamp_to_document(document, "create")?;
|
||||
|
||||
let mut documents = self.documents.write()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
// Проверяем уникальность перед вставкой
|
||||
self.update_indexes(None, &document_with_timestamp, &id)?;
|
||||
|
||||
documents.insert(id.clone(), document_with_timestamp);
|
||||
|
||||
// Логируем операцию
|
||||
self.log_operation("create", &id);
|
||||
|
||||
println!("Document created in collection '{}' with ID: {} (seq: {})", self.name, id, seq);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Wait-Free чтение документа
|
||||
pub fn read_document(&self, id: &str) -> Result<Option<Vec<u8>>> {
|
||||
let documents = self.documents.read()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
// Логируем операцию чтения
|
||||
self.log_operation("read", id);
|
||||
|
||||
Ok(documents.get(id).cloned())
|
||||
}
|
||||
|
||||
/// Wait-Free обновление документа с временной меткой
|
||||
pub fn update_document(&self, id: &str, document: Vec<u8>) -> Result<()> {
|
||||
let seq = self.sequence.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// Добавляем временную метку к документу
|
||||
let document_with_timestamp = self.add_timestamp_to_document(document, "update")?;
|
||||
|
||||
let mut documents = self.documents.write()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
if let Some(old_document) = documents.get(id) {
|
||||
// Обновляем индексы
|
||||
self.update_indexes(Some(old_document), &document_with_timestamp, id)?;
|
||||
|
||||
documents.insert(id.to_string(), document_with_timestamp);
|
||||
|
||||
// Логируем операцию
|
||||
self.log_operation("update", id);
|
||||
|
||||
println!("Document updated in collection '{}': {} (seq: {})", self.name, id, seq);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::common::FutriixError::DatabaseError(
|
||||
format!("Document not found: {}", id)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait-Free удаление документа с временной меткой
|
||||
pub fn delete_document(&self, id: &str) -> Result<()> {
|
||||
let seq = self.sequence.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
let mut documents = self.documents.write()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
if let Some(old_document) = documents.get(id) {
|
||||
// Удаляем из индексов
|
||||
self.update_indexes(Some(old_document), &[], id)?;
|
||||
|
||||
documents.remove(id);
|
||||
|
||||
// Логируем операцию
|
||||
self.log_operation("delete", id);
|
||||
|
||||
println!("Document deleted from collection '{}': {} (seq: {})", self.name, id, seq);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::common::FutriixError::DatabaseError(
|
||||
format!("Document not found: {}", id)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait-Free запрос документов
|
||||
pub fn query_documents(&self, _filter: Vec<u8>) -> Result<Vec<Vec<u8>>> {
|
||||
let documents = self.documents.read()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
// Логируем операцию запроса
|
||||
self.log_operation("query", "multiple");
|
||||
|
||||
// TODO: Реализовать wait-free фильтрацию на основе filter
|
||||
let documents: Vec<Vec<u8>> = documents.values().cloned().collect();
|
||||
|
||||
Ok(documents)
|
||||
}
|
||||
|
||||
/// Получение имени коллекции (wait-free)
|
||||
#[allow(dead_code)]
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Получение количества документов (wait-free)
|
||||
#[allow(dead_code)]
|
||||
pub fn count_documents(&self) -> Result<usize> {
|
||||
let documents = self.documents.read()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
Ok(documents.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait-Free база данных
|
||||
#[derive(Clone)]
|
||||
pub struct Database {
|
||||
collections: Arc<DashMap<String, Collection>>,
|
||||
procedures: Arc<DashMap<String, Vec<u8>>>,
|
||||
transactions: Arc<DashMap<String, Vec<protocol::Command>>>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
/// Создание новой wait-free базы данных
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
collections: Arc::new(DashMap::new()),
|
||||
procedures: Arc::new(DashMap::new()),
|
||||
transactions: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait-Free получение или создание коллекции
|
||||
pub fn get_collection(&self, name: &str) -> Collection {
|
||||
if let Some(collection) = self.collections.get(name) {
|
||||
return collection.clone();
|
||||
}
|
||||
|
||||
// Создаем новую коллекцию wait-free способом
|
||||
let new_collection = Collection::new(name.to_string());
|
||||
self.collections.insert(name.to_string(), new_collection.clone());
|
||||
|
||||
new_collection
|
||||
}
|
||||
|
||||
/// Wait-Free выполнение команды
|
||||
pub fn execute_command(&self, command: protocol::Command) -> Result<protocol::Response> {
|
||||
match command {
|
||||
protocol::Command::Create { collection, document } => {
|
||||
let coll = self.get_collection(&collection);
|
||||
match coll.create_document(document) {
|
||||
Ok(id) => Ok(protocol::Response::Success(id.into_bytes())),
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
protocol::Command::Read { collection, id } => {
|
||||
let coll = self.get_collection(&collection);
|
||||
match coll.read_document(&id) {
|
||||
Ok(Some(document)) => Ok(protocol::Response::Success(document)),
|
||||
Ok(None) => Ok(protocol::Response::Error(format!("Document not found: {}", id))),
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
protocol::Command::Update { collection, id, document } => {
|
||||
let coll = self.get_collection(&collection);
|
||||
match coll.update_document(&id, document) {
|
||||
Ok(_) => Ok(protocol::Response::Success(vec![])),
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
protocol::Command::Delete { collection, id } => {
|
||||
let coll = self.get_collection(&collection);
|
||||
match coll.delete_document(&id) {
|
||||
Ok(_) => Ok(protocol::Response::Success(vec![])),
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
protocol::Command::Query { collection, filter } => {
|
||||
let coll = self.get_collection(&collection);
|
||||
match coll.query_documents(filter) {
|
||||
Ok(documents) => {
|
||||
let json_docs: Vec<Value> = documents.into_iter()
|
||||
.filter_map(|doc| serde_json::from_slice(&doc).ok())
|
||||
.collect();
|
||||
|
||||
match serde_json::to_vec(&json_docs) {
|
||||
Ok(data) => Ok(protocol::Response::Success(data)),
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
protocol::Command::CreateProcedure { name, code } => {
|
||||
self.procedures.insert(name, code);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::CallProcedure { name } => {
|
||||
if self.procedures.contains_key(&name) {
|
||||
// TODO: Выполнить Lua код процедура
|
||||
Ok(protocol::Response::Success(format!("Procedure {} executed", name).into_bytes()))
|
||||
} else {
|
||||
Ok(protocol::Response::Error(format!("Procedure not found: {}", name)))
|
||||
}
|
||||
}
|
||||
protocol::Command::BeginTransaction { transaction_id } => {
|
||||
if self.transactions.contains_key(&transaction_id) {
|
||||
return Ok(protocol::Response::Error("Transaction already exists".to_string()));
|
||||
}
|
||||
|
||||
self.transactions.insert(transaction_id, Vec::new());
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::CommitTransaction { transaction_id } => {
|
||||
if let Some((_, commands)) = self.transactions.remove(&transaction_id) {
|
||||
// Выполняем все команды транзакции wait-free способом
|
||||
for cmd in commands {
|
||||
if let Err(e) = self.execute_command(cmd) {
|
||||
return Ok(protocol::Response::Error(format!("Transaction failed: {}", e)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
} else {
|
||||
Ok(protocol::Response::Error("Transaction not found".to_string()))
|
||||
}
|
||||
}
|
||||
protocol::Command::RollbackTransaction { transaction_id } => {
|
||||
if self.transactions.remove(&transaction_id).is_some() {
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
} else {
|
||||
Ok(protocol::Response::Error("Transaction not found".to_string()))
|
||||
}
|
||||
}
|
||||
protocol::Command::CreateIndex { collection, index } => {
|
||||
let coll = self.get_collection(&collection);
|
||||
match coll.create_index(index) {
|
||||
Ok(_) => Ok(protocol::Response::Success(vec![])),
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
protocol::Command::QueryByIndex { collection, index_name, value } => {
|
||||
let coll = self.get_collection(&collection);
|
||||
let value: Value = serde_json::from_slice(&value)
|
||||
.map_err(|e| crate::common::FutriixError::DatabaseError(e.to_string()))?;
|
||||
|
||||
match coll.query_by_index(&index_name, &value) {
|
||||
Ok(document_ids) => {
|
||||
let result = serde_json::to_vec(&document_ids)
|
||||
.map_err(|e| crate::common::FutriixError::DatabaseError(e.to_string()))?;
|
||||
Ok(protocol::Response::Success(result))
|
||||
}
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
// Обработка новых команд для шардинга, constraints, компрессии и глобальных индексов
|
||||
protocol::Command::AddShardNode { node_id, address, capacity } => {
|
||||
// TODO: Реализовать добавление шард-узла
|
||||
println!("Adding shard node: {} at {} with capacity {}", node_id, address, capacity);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::RemoveShardNode { node_id } => {
|
||||
// TODO: Реализовать удаление шард-узла
|
||||
println!("Removing shard node: {}", node_id);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::MigrateShard { collection, from_node, to_node, shard_key } => {
|
||||
// TODO: Реализовать миграцию шарда
|
||||
println!("Migrating shard from {} to {} for collection {} with key {}", from_node, to_node, collection, shard_key);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::RebalanceCluster => {
|
||||
// TODO: Реализовать ребалансировку кластера
|
||||
println!("Rebalancing cluster");
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::GetClusterStatus => {
|
||||
// TODO: Реализовать получение статуса кластера
|
||||
let status = protocol::ClusterStatus {
|
||||
nodes: vec![],
|
||||
total_capacity: 0,
|
||||
total_used: 0,
|
||||
rebalance_needed: false,
|
||||
cluster_formed: false,
|
||||
leader_exists: false,
|
||||
raft_nodes: vec![],
|
||||
};
|
||||
match protocol::serialize(&status) {
|
||||
Ok(data) => Ok(protocol::Response::Success(data)),
|
||||
Err(e) => Ok(protocol::Response::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
protocol::Command::StartElection => {
|
||||
// TODO: Реализовать Raft выборы
|
||||
println!("Starting Raft election");
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::GetRaftNodes => {
|
||||
// TODO: Реализовать получение Raft узлов
|
||||
println!("Getting Raft nodes");
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::AddConstraint { collection, constraint_name, constraint_type, field, value } => {
|
||||
// TODO: Реализовать добавление constraint
|
||||
println!("Adding constraint {} to collection {}: {} {} with value {:?}", constraint_name, collection, constraint_type, field, value);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::RemoveConstraint { collection, constraint_name } => {
|
||||
// TODO: Реализовать удаление constraint
|
||||
println!("Removing constraint {} from collection {}", constraint_name, collection);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::EnableCompression { collection, algorithm } => {
|
||||
// TODO: Реализовать включение компрессии
|
||||
println!("Enabling {} compression for collection {}", algorithm, collection);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::DisableCompression { collection } => {
|
||||
// TODO: Реализовать отключение компрессии
|
||||
println!("Disabling compression for collection {}", collection);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::CreateGlobalIndex { name, field, unique } => {
|
||||
// TODO: Реализовать создание глобального индекса
|
||||
println!("Creating global index {} on field {} (unique: {})", name, field, unique);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::QueryGlobalIndex { index_name, value } => {
|
||||
// TODO: Реализовать запрос по глобальному индексу
|
||||
println!("Querying global index {} with value {:?}", index_name, value);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
// Новые команды для CSV
|
||||
protocol::Command::ImportCsv { collection, file_path } => {
|
||||
// TODO: Интегрировать с CsvManager
|
||||
println!("Importing CSV to collection '{}' from '{}'", collection, file_path);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::ExportCsv { collection, file_path } => {
|
||||
// TODO: Интегрировать с CsvManager
|
||||
println!("Exporting collection '{}' to CSV file '{}'", collection, file_path);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::ListCsvFiles => {
|
||||
// TODO: Интегрировать с CsvManager
|
||||
println!("Listing CSV files");
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
protocol::Command::GetImportProgress { collection } => {
|
||||
// TODO: Интегрировать с CsvManager
|
||||
println!("Getting import progress for '{}'", collection);
|
||||
Ok(protocol::Response::Success(vec![]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait-Free получение статистики базы данных
|
||||
#[allow(dead_code)]
|
||||
pub fn get_stats(&self) -> Result<std::collections::HashMap<String, usize>> {
|
||||
let mut stats = std::collections::HashMap::new();
|
||||
stats.insert("collections".to_string(), self.collections.len());
|
||||
stats.insert("procedures".to_string(), self.procedures.len());
|
||||
stats.insert("active_transactions".to_string(), self.transactions.len());
|
||||
|
||||
// Подсчет документов во всех коллекциях
|
||||
let total_documents: usize = self.collections.iter()
|
||||
.map(|entry| entry.value().count_documents().unwrap_or(0))
|
||||
.sum();
|
||||
|
||||
stats.insert("total_documents".to_string(), total_documents);
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
/// Создание бэкапа базы данных
|
||||
pub fn create_backup(&self) -> Result<std::collections::HashMap<String, std::collections::HashMap<String, Vec<u8>>>> {
|
||||
let mut backup = std::collections::HashMap::new();
|
||||
|
||||
for entry in self.collections.iter() {
|
||||
let name = entry.key().clone();
|
||||
let collection = entry.value();
|
||||
|
||||
let documents = collection.documents.read()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
let mut collection_backup = std::collections::HashMap::new();
|
||||
for (id, document) in documents.iter() {
|
||||
collection_backup.insert(id.clone(), document.clone());
|
||||
}
|
||||
|
||||
backup.insert(name, collection_backup);
|
||||
}
|
||||
|
||||
Ok(backup)
|
||||
}
|
||||
|
||||
/// Восстановление из бэкапа
|
||||
pub fn restore_from_backup(&self, backup: std::collections::HashMap<String, std::collections::HashMap<String, Vec<u8>>>) -> Result<()> {
|
||||
// Очищаем существующие коллекции
|
||||
self.collections.clear();
|
||||
|
||||
// Восстанавливаем данные из бэкапа
|
||||
for (collection_name, documents) in backup {
|
||||
let collection = Collection::new(collection_name.clone());
|
||||
|
||||
{
|
||||
let mut collection_docs = collection.documents.write()
|
||||
.map_err(|e| crate::common::FutriixError::IoError(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
|
||||
))?;
|
||||
|
||||
for (id, document) in documents {
|
||||
collection_docs.insert(id, document);
|
||||
}
|
||||
}
|
||||
|
||||
self.collections.insert(collection_name, collection);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Добавление триггера к коллекции
|
||||
pub fn add_trigger(&self, trigger: Trigger) -> Result<()> {
|
||||
let collection = self.get_collection(&trigger.collection);
|
||||
collection.add_trigger(trigger)
|
||||
}
|
||||
}
|
||||
374
src/server/http.rs
Executable file
374
src/server/http.rs
Executable file
@ -0,0 +1,374 @@
|
||||
// src/server/http.rs
|
||||
//! HTTP/HTTPS сервер с wait-free обработкой запросов
|
||||
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use hyper::{Body, Request, Response, Server, StatusCode};
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::server::database::Database;
|
||||
|
||||
/// Конфигурация статических файлов
|
||||
#[derive(Clone)]
|
||||
pub struct StaticFilesConfig {
|
||||
pub enabled: bool,
|
||||
pub directory: String,
|
||||
}
|
||||
|
||||
impl Default for StaticFilesConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
directory: "static".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Конфигурация TLS
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConfig {
|
||||
pub enabled: bool,
|
||||
pub cert_path: String,
|
||||
pub key_path: String,
|
||||
}
|
||||
|
||||
impl Default for TlsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
cert_path: "certs/cert.pem".to_string(),
|
||||
key_path: "certs/key.pem".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Конфигурация HTTP сервера
|
||||
#[derive(Clone)]
|
||||
pub struct HttpConfig {
|
||||
pub enabled: bool,
|
||||
pub port: u16,
|
||||
pub http2_enabled: bool,
|
||||
}
|
||||
|
||||
/// Конфигурация ACL
|
||||
#[derive(Clone)]
|
||||
pub struct AclConfig {
|
||||
pub enabled: bool,
|
||||
pub allowed_ips: Vec<String>,
|
||||
pub denied_ips: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for AclConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
allowed_ips: vec!["127.0.0.1".to_string(), "::1".to_string()],
|
||||
denied_ips: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait-free обработчик HTTP запросов с поддержкой ACL
|
||||
async fn handle_request(
|
||||
req: Request<Body>,
|
||||
db: Arc<Database>,
|
||||
static_config: StaticFilesConfig,
|
||||
acl_config: AclConfig,
|
||||
) -> Result<Response<Body>> {
|
||||
// Проверка ACL, если включена
|
||||
if acl_config.enabled {
|
||||
if let Some(remote_addr) = req.extensions().get::<std::net::SocketAddr>() {
|
||||
let ip = remote_addr.ip().to_string();
|
||||
|
||||
// Проверка запрещенных IP
|
||||
if acl_config.denied_ips.contains(&ip) {
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::FORBIDDEN)
|
||||
.body(Body::from("Access denied"))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
// Проверка разрешенных IP (если список не пустой)
|
||||
if !acl_config.allowed_ips.is_empty() && !acl_config.allowed_ips.contains(&ip) {
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::FORBIDDEN)
|
||||
.body(Body::from("Access denied"))
|
||||
.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let path = req.uri().path();
|
||||
|
||||
println!("HTTP Request: {} {}", req.method(), path);
|
||||
|
||||
// Обработка API запросов
|
||||
if path.starts_with("/api/") {
|
||||
handle_api_request(req, db).await
|
||||
}
|
||||
// Обслуживание статических файлов
|
||||
else if static_config.enabled {
|
||||
handle_static_file(path, static_config).await
|
||||
}
|
||||
// Корневой путь
|
||||
else if path == "/" {
|
||||
// ВОЗВРАЩАЕМ ПРОСТОЙ HTML ДЛЯ КОРНЕВОГО ПУТИ
|
||||
Ok(Response::builder()
|
||||
.header("Content-Type", "text/html; charset=utf-8")
|
||||
.body(Body::from(r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Futriix Database Server</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { color: #00bfff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Futriix Database Server</h1>
|
||||
<p>Server is running successfully!</p>
|
||||
<p>Try accessing <a href="/index.html">/index.html</a> for the main interface.</p>
|
||||
</body>
|
||||
</html>
|
||||
"#))
|
||||
.unwrap())
|
||||
}
|
||||
// 404 для остальных запросов
|
||||
else {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::from("Not Found"))
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait-free обработка API запросов
|
||||
async fn handle_api_request(
|
||||
_req: Request<Body>,
|
||||
_db: Arc<Database>,
|
||||
) -> Result<Response<Body>> {
|
||||
// TODO: Реализовать wait-free обработку CRUD операций через HTTP
|
||||
Ok(Response::builder()
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(r#"{"status": "ok", "message": "Futriix Server is running"}"#))
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
/// Wait-free обслуживание статических файлов
|
||||
async fn handle_static_file(
|
||||
path: &str,
|
||||
config: StaticFilesConfig,
|
||||
) -> Result<Response<Body>> {
|
||||
// Убираем начальный слеш из пути
|
||||
let clean_path = path.trim_start_matches('/');
|
||||
|
||||
// Если путь пустой или корневой, используем index.html
|
||||
let file_path = if clean_path.is_empty() || clean_path == "/" {
|
||||
format!("{}/index.html", config.directory)
|
||||
} else {
|
||||
format!("{}/{}", config.directory, clean_path)
|
||||
};
|
||||
|
||||
// ДОБАВЛЯЕМ ДЕБАГ-ЛОГИРОВАНИЕ
|
||||
println!("Trying to serve static file: {}", file_path);
|
||||
|
||||
match File::open(&file_path).await {
|
||||
Ok(mut file) => {
|
||||
let mut contents = Vec::new();
|
||||
if let Err(e) = file.read_to_end(&mut contents).await {
|
||||
eprintln!("Failed to read file {}: {}", file_path, e);
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(Body::from("Internal server error"))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
let content_type = get_content_type(&file_path);
|
||||
|
||||
println!("Successfully served static file: {} ({} bytes)", file_path, contents.len());
|
||||
|
||||
Ok(Response::builder()
|
||||
.header("Content-Type", content_type)
|
||||
.body(Body::from(contents))
|
||||
.unwrap())
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("File not found: {} (error: {})", file_path, e);
|
||||
|
||||
// ДОБАВЛЯЕМ ПРОСТОЙ HTML ДЛЯ ТЕСТИРОВАНИЯ, ЕСЛИ ФАЙЛ НЕ НАЙДЕН
|
||||
if clean_path == "index.html" {
|
||||
let fallback_html = r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Futriix Database Server</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { color: #00bfff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Futriix Database Server</h1>
|
||||
<p>Welcome to Futriix Database Server!</p>
|
||||
<p>Static file serving is working correctly.</p>
|
||||
<p>Current time: PLACEHOLDER_TIME</p>
|
||||
<p>Requested path: PLACEHOLDER_PATH</p>
|
||||
</body>
|
||||
</html>
|
||||
"#.replace("PLACEHOLDER_TIME", &chrono::Local::now().to_rfc2822())
|
||||
.replace("PLACEHOLDER_PATH", path);
|
||||
|
||||
return Ok(Response::builder()
|
||||
.header("Content-Type", "text/html; charset=utf-8")
|
||||
.body(Body::from(fallback_html))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::from("File not found"))
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Определение Content-Type по расширению файла
|
||||
fn get_content_type(file_path: &str) -> &'static str {
|
||||
if file_path.ends_with(".html") {
|
||||
"text/html; charset=utf-8"
|
||||
} else if file_path.ends_with(".css") {
|
||||
"text/css; charset=utf-8"
|
||||
} else if file_path.ends_with(".js") {
|
||||
"application/javascript; charset=utf-8"
|
||||
} else if file_path.ends_with(".png") {
|
||||
"image/png"
|
||||
} else if file_path.ends_with(".jpg") || file_path.ends_with(".jpeg") {
|
||||
"image/jpeg"
|
||||
} else if file_path.ends_with(".json") {
|
||||
"application/json; charset=utf-8"
|
||||
} else if file_path.ends_with(".ico") {
|
||||
"image/x-icon"
|
||||
} else if file_path.ends_with(".svg") {
|
||||
"image/svg+xml"
|
||||
} else {
|
||||
"text/plain; charset=utf-8"
|
||||
}
|
||||
}
|
||||
|
||||
/// Запуск HTTP сервера с wait-free архитектурой
|
||||
pub async fn start_http_server(
|
||||
addr: &str,
|
||||
db: Arc<Database>,
|
||||
static_config: StaticFilesConfig,
|
||||
http_config: HttpConfig,
|
||||
acl_config: AclConfig,
|
||||
) -> Result<()> {
|
||||
let addr_parsed: std::net::SocketAddr = addr.parse()
|
||||
.map_err(|e: std::net::AddrParseError| crate::common::FutriixError::HttpError(e.to_string()))?;
|
||||
|
||||
let db_clone = db.clone();
|
||||
let static_clone = static_config.clone();
|
||||
let acl_clone = acl_config.clone();
|
||||
|
||||
// Создание wait-free сервиса
|
||||
let make_svc = make_service_fn(move |_conn| {
|
||||
let db = db_clone.clone();
|
||||
let static_config = static_clone.clone();
|
||||
let acl_config = acl_clone.clone();
|
||||
|
||||
async move {
|
||||
Ok::<_, hyper::Error>(service_fn(move |req| {
|
||||
let db = db.clone();
|
||||
let static_config = static_config.clone();
|
||||
let acl_config = acl_config.clone();
|
||||
async move {
|
||||
handle_request(req, db, static_config, acl_config).await
|
||||
}
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
println!("HTTP server starting on {}...", addr);
|
||||
|
||||
// ЗАПУСКАЕМ СЕРВЕР И БЛОКИРУЕМСЯ НА ЕГО ВЫПОЛНЕНИИ
|
||||
// Это гарантирует, что сервер продолжит работать
|
||||
let server = Server::bind(&addr_parsed).serve(make_svc);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("HTTP server error: {}", e);
|
||||
return Err(crate::common::FutriixError::HttpError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Запуск HTTPS сервера с wait-free архитектурой
|
||||
pub async fn start_https_server(
|
||||
addr: &str,
|
||||
db: Arc<Database>,
|
||||
static_config: StaticFilesConfig,
|
||||
tls_config: TlsConfig,
|
||||
acl_config: AclConfig,
|
||||
) -> Result<()> {
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
if !tls_config.enabled {
|
||||
println!("HTTPS disabled: TLS not enabled");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// ПРОСТОЙ ВАРИАНТ БЕЗ TLS ДЛЯ ТЕСТИРОВАНИЯ
|
||||
// В реальном коде здесь должна быть TLS конфигурация
|
||||
println!("HTTPS server would start on {} (TLS configuration needed)", addr);
|
||||
|
||||
// ЗАПУСКАЕМ ОБЫЧНЫЙ HTTP СЕРВЕР НА HTTPS ПОРТУ ДЛЯ ТЕСТИРОВАНИЯ
|
||||
// Но используем тот же порт, чтобы не путать
|
||||
let http_config = HttpConfig {
|
||||
enabled: true,
|
||||
port: 8443, // Используем HTTPS порт для тестирования
|
||||
http2_enabled: false,
|
||||
};
|
||||
|
||||
// ИСПРАВЛЕНИЕ ОШИБКИ: создаем owned копии всех данных для использования в async move
|
||||
let owned_addr = addr.to_string();
|
||||
let owned_db = db.clone();
|
||||
let owned_static_config = static_config.clone();
|
||||
let owned_acl_config = acl_config.clone();
|
||||
|
||||
// Запускаем обычный HTTP сервер на HTTPS порту для тестирования
|
||||
// Это временное решение до настройки TLS
|
||||
let server_future = async move {
|
||||
start_http_server(&owned_addr, owned_db, owned_static_config, http_config, owned_acl_config).await
|
||||
};
|
||||
|
||||
// Запускаем сервер в отдельной задаче
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = server_future.await {
|
||||
eprintln!("HTTPS (HTTP fallback) server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Вспомогательная функция для логирования
|
||||
fn log_to_file(message: &str) {
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
if let Ok(mut file) = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("futriix.log")
|
||||
{
|
||||
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
|
||||
let log_message = format!("[{}] {}\n", timestamp, message);
|
||||
let _ = file.write_all(log_message.as_bytes());
|
||||
}
|
||||
}
|
||||
142
src/server/lua_engine.rs
Executable file
142
src/server/lua_engine.rs
Executable file
@ -0,0 +1,142 @@
|
||||
// src/server/lua_engine.rs
|
||||
//! Встроенный интерпретатор Lua (на основе rlua)
|
||||
|
||||
use rlua::{Lua, RluaCompat};
|
||||
use std::sync::Arc;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::common::protocol;
|
||||
use crate::server::database::{Trigger, TriggerEvent, Index, IndexType};
|
||||
|
||||
/// Движок Lua для выполнения скриптов
|
||||
#[derive(Clone)]
|
||||
pub struct LuaEngine {
|
||||
lua: Arc<Lua>,
|
||||
}
|
||||
|
||||
impl LuaEngine {
|
||||
pub fn new() -> Result<Self> {
|
||||
let lua = Lua::new();
|
||||
|
||||
// Настройка Lua окружения
|
||||
lua.load(r#"
|
||||
function futriix_log(message)
|
||||
print("LUA: " .. message)
|
||||
end
|
||||
|
||||
function futriix_error(message)
|
||||
print("LUA ERROR: " .. message)
|
||||
end
|
||||
"#).exec()?;
|
||||
|
||||
Ok(Self { lua: Arc::new(lua) })
|
||||
}
|
||||
|
||||
/// Выполнение Lua скрипта из файла
|
||||
#[allow(dead_code)]
|
||||
pub fn execute_script_file(&self, file_path: &str) -> Result<()> {
|
||||
let script_content = fs::read_to_string(file_path)
|
||||
.map_err(|e| crate::common::FutriixError::LuaError(format!("Failed to read script file {}: {}", file_path, e)))?;
|
||||
|
||||
self.execute_script(&script_content)
|
||||
}
|
||||
|
||||
/// Выполнение Lua скрипта из строки
|
||||
pub fn execute_script(&self, script: &str) -> Result<()> {
|
||||
let lua = self.lua.clone();
|
||||
lua.load(script).exec()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Выполнение всех скриптов из директории
|
||||
#[allow(dead_code)]
|
||||
pub fn execute_scripts_from_dir(&self, dir_path: &str, script_names: &[String]) -> Result<()> {
|
||||
let path = Path::new(dir_path);
|
||||
|
||||
if !path.exists() {
|
||||
println!("Lua scripts directory does not exist: {}", path.display());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err(crate::common::FutriixError::LuaError(
|
||||
format!("Lua scripts path is not a directory: {}", path.display())
|
||||
));
|
||||
}
|
||||
|
||||
for script_name in script_names {
|
||||
let script_path = path.join(script_name);
|
||||
|
||||
if script_path.exists() && script_path.is_file() {
|
||||
println!("Executing Lua script: {}", script_path.display());
|
||||
|
||||
match self.execute_script_file(script_path.to_str().unwrap()) {
|
||||
Ok(_) => println!("✓ Script executed successfully: {}", script_name),
|
||||
Err(e) => eprintln!("✗ Failed to execute script {}: {}", script_name, e),
|
||||
}
|
||||
} else {
|
||||
println!("Script not found: {}", script_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Регистрация функций базы данных в Lua
|
||||
pub fn register_db_functions(
|
||||
&self,
|
||||
db: Arc<crate::server::database::Database>,
|
||||
sharding_manager: Arc<crate::server::sharding::ShardingManager>
|
||||
) -> Result<()> {
|
||||
let lua = self.lua.clone();
|
||||
|
||||
// Создаем таблицу для функций БД
|
||||
let futriix_db = lua.create_table()?;
|
||||
|
||||
// Базовые CRUD функции
|
||||
let db_clone = db.clone();
|
||||
futriix_db.set("create", lua.create_function(move |_, (collection, data): (String, String)| {
|
||||
let command = protocol::Command::Create {
|
||||
collection,
|
||||
document: data.into_bytes(),
|
||||
};
|
||||
match db_clone.execute_command(command) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(rlua::Error::RuntimeError(e.to_string())),
|
||||
}
|
||||
})?)?;
|
||||
|
||||
let db_clone = db.clone();
|
||||
futriix_db.set("read", lua.create_function(move |_, (collection, id): (String, String)| {
|
||||
let command = protocol::Command::Read {
|
||||
collection,
|
||||
id,
|
||||
};
|
||||
match db_clone.execute_command(command) {
|
||||
Ok(response) => {
|
||||
match response {
|
||||
protocol::Response::Success(data) => {
|
||||
Ok(String::from_utf8_lossy(&data).to_string())
|
||||
}
|
||||
protocol::Response::Error(e) => {
|
||||
Err(rlua::Error::RuntimeError(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Err(rlua::Error::RuntimeError(e.to_string())),
|
||||
}
|
||||
})?)?;
|
||||
|
||||
// Добавляем таблицу в глобальное пространство имен
|
||||
lua.globals().set("futriix_db", futriix_db)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Получение директории Lua скриптов
|
||||
fn lua_scripts_dir(&self) -> &'static str {
|
||||
"lua_scripts"
|
||||
}
|
||||
}
|
||||
158
src/server/main.rs
Executable file
158
src/server/main.rs
Executable file
@ -0,0 +1,158 @@
|
||||
// src/main.rs
|
||||
//! Главный модуль сервера Futriix
|
||||
//!
|
||||
//! Точка входа в приложение, инициализирует сервер и запускает его.
|
||||
//! Использует wait-free архитектуру с lock-free структурами данных.
|
||||
|
||||
mod common;
|
||||
mod server;
|
||||
mod lua_shell;
|
||||
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::common::FutriixError;
|
||||
|
||||
/// Функция для логирования в файл
|
||||
fn log_to_file(message: &str) {
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("futriix.log")
|
||||
{
|
||||
Ok(mut file) => {
|
||||
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
|
||||
let log_message = format!("[{}] {}\n", timestamp, message);
|
||||
let _ = file.write_all(log_message.as_bytes());
|
||||
}
|
||||
Err(e) => eprintln!("Failed to write to log file: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Простая структура для аргументов командной строки
|
||||
struct Args {
|
||||
config: String,
|
||||
debug: bool,
|
||||
http_port: Option<u16>,
|
||||
https_port: Option<u16>,
|
||||
host: Option<String>,
|
||||
}
|
||||
|
||||
/// Простой парсер аргументов командной строки
|
||||
fn parse_args() -> Args {
|
||||
let mut args = Args {
|
||||
config: "config.toml".to_string(),
|
||||
debug: false,
|
||||
http_port: None,
|
||||
https_port: None,
|
||||
host: None,
|
||||
};
|
||||
|
||||
let mut iter = env::args().skip(1);
|
||||
while let Some(arg) = iter.next() {
|
||||
match arg.as_str() {
|
||||
"--config" | "-c" => {
|
||||
if let Some(value) = iter.next() {
|
||||
args.config = value;
|
||||
}
|
||||
}
|
||||
"--debug" | "-d" => {
|
||||
args.debug = true;
|
||||
}
|
||||
"--http-port" => {
|
||||
if let Some(value) = iter.next() {
|
||||
if let Ok(port) = value.parse() {
|
||||
args.http_port = Some(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
"--https-port" => {
|
||||
if let Some(value) = iter.next() {
|
||||
if let Ok(port) = value.parse() {
|
||||
args.https_port = Some(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
"--host" => {
|
||||
if let Some(value) = iter.next() {
|
||||
args.host = Some(value);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if arg.starts_with("--config=") {
|
||||
args.config = arg.trim_start_matches("--config=").to_string();
|
||||
} else if arg.starts_with("-c=") {
|
||||
args.config = arg.trim_start_matches("-c=").to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
/// Функция для вывода текста с ANSI цветом
|
||||
fn print_colored(text: &str, ansi_color: &str) {
|
||||
println!("{}{}\x1b[0m", ansi_color, text);
|
||||
}
|
||||
|
||||
/// Конвертация HEX цвета в ANSI escape code
|
||||
fn hex_to_ansi(hex_color: &str) -> String {
|
||||
let hex = hex_color.trim_start_matches('#');
|
||||
|
||||
if hex.len() == 6 {
|
||||
if let (Ok(r), Ok(g), Ok(b)) = (
|
||||
u8::from_str_radix(&hex[0..2], 16),
|
||||
u8::from_str_radix(&hex[2..4], 16),
|
||||
u8::from_str_radix(&hex[4..6], 16),
|
||||
) {
|
||||
return format!("\x1b[38;2;{};{};{}m", r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
"\x1b[38;2;255;255;255m".to_string()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), FutriixError> {
|
||||
// Инициализация логирования в файл
|
||||
log_to_file("Starting Futriix server");
|
||||
|
||||
// Вывод приветственного сообщения с цветом #00bfff перед загрузкой конфигурации
|
||||
let color_code = hex_to_ansi("#00bfff");
|
||||
println!(); // Добавляем пустую строку перед фразой
|
||||
print_colored("Futriix Database Server", &color_code);
|
||||
print_colored("futriix 3i²(by 26.11.2025)", &color_code);
|
||||
println!(); // Добавляем пустую строку после фразы
|
||||
|
||||
// Парсим аргументы командной строки
|
||||
let args = parse_args();
|
||||
let config_path = args.config;
|
||||
|
||||
let message = format!("Loading configuration from: {}", config_path);
|
||||
println!("{}", message);
|
||||
log_to_file(&message);
|
||||
|
||||
// Создание и запуск сервера
|
||||
match server::FutriixServer::new(&config_path).await {
|
||||
Ok(server) => {
|
||||
log_to_file("Server created successfully");
|
||||
if let Err(e) = server.run().await {
|
||||
let error_message = format!("Server error: {}", e);
|
||||
eprintln!("{}", error_message);
|
||||
log_to_file(&error_message);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_message = format!("Failed to create server: {}", e);
|
||||
eprintln!("{}", error_message);
|
||||
log_to_file(&error_message);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
log_to_file("Futriix server stopped");
|
||||
Ok(())
|
||||
}
|
||||
328
src/server/mod.rs
Executable file
328
src/server/mod.rs
Executable file
@ -0,0 +1,328 @@
|
||||
// src/server/mod.rs
|
||||
//! Сервер Futriix - документо-ориентированная БД с wait-free архитектурой
|
||||
//!
|
||||
//! Основной модуль сервера, реализующий wait-free доступ к данным,
|
||||
//! синхронную master-master репликацию и поддержку HTTP/HTTPS.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::common::config::Config;
|
||||
use crate::lua_shell::LuaShell;
|
||||
|
||||
// Импортируем подмодули
|
||||
pub mod database;
|
||||
pub mod lua_engine;
|
||||
pub mod http;
|
||||
pub mod sharding; // Объединенный модуль шардинга и репликации
|
||||
pub mod csv_import_export; // Модуль для CSV импорта/экспорта
|
||||
|
||||
/// Функция для логирования в файл
|
||||
fn log_to_file(message: &str) {
|
||||
if let Ok(mut file) = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("futriix.log")
|
||||
{
|
||||
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
|
||||
let log_message = format!("[{}] {}\n", timestamp, message);
|
||||
let _ = file.write_all(log_message.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
/// Функция для вывода текста с ANSI цветом
|
||||
#[allow(dead_code)]
|
||||
fn print_colored(text: &str, ansi_color: &str) {
|
||||
println!("{}{}\x1b[0m", ansi_color, text);
|
||||
}
|
||||
|
||||
/// Конвертация HEX цвета в ANSI escape code
|
||||
fn hex_to_ansi(hex_color: &str) -> String {
|
||||
let hex = hex_color.trim_start_matches('#');
|
||||
|
||||
if hex.len() == 6 {
|
||||
if let (Ok(r), Ok(g), Ok(b)) = (
|
||||
u8::from_str_radix(&hex[0..2], 16),
|
||||
u8::from_str_radix(&hex[2..4], 16),
|
||||
u8::from_str_radix(&hex[4..6], 16),
|
||||
) {
|
||||
return format!("\x1b[38;2;{};{};{}m", r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
"\x1b[38;2;255;255;255m".to_string()
|
||||
}
|
||||
|
||||
/// Основный сервер Futriix с wait-free архитектураой
|
||||
pub struct FutriixServer {
|
||||
config: Config,
|
||||
database: Arc<database::Database>,
|
||||
lua_engine: lua_engine::LuaEngine,
|
||||
sharding_manager: Arc<sharding::ShardingManager>, // Объединенный менеджер
|
||||
http_enabled: bool,
|
||||
csv_manager: Arc<csv_import_export::CsvManager>,
|
||||
}
|
||||
|
||||
impl FutriixServer {
|
||||
/// Создание нового сервера с wait-free архитектурой
|
||||
pub async fn new(config_path: &str) -> Result<Self> {
|
||||
// Загрузка конфигурации
|
||||
let config = Config::load(config_path)?;
|
||||
|
||||
// Инициализация компонентов с wait-free подходами
|
||||
let database = Arc::new(database::Database::new());
|
||||
let lua_engine = lua_engine::LuaEngine::new()?;
|
||||
|
||||
// Инициализация объединенного менеджера шардинга и репликации
|
||||
let sharding_manager = Arc::new(sharding::ShardingManager::new(
|
||||
160, // virtual_nodes_per_node
|
||||
config.replication.enabled,
|
||||
));
|
||||
|
||||
// Инициализация менеджера CSV
|
||||
let csv_manager = Arc::new(csv_import_export::CsvManager::new(
|
||||
database.clone(),
|
||||
config.csv.clone(),
|
||||
));
|
||||
|
||||
// Регистрация функций БД в Lua
|
||||
lua_engine.register_db_functions(database.clone(), sharding_manager.clone())?;
|
||||
|
||||
// Инициализация базы данных
|
||||
FutriixServer::initialize_database(database.clone())?;
|
||||
|
||||
// Проверяем, включен ли HTTP режим (теперь учитываем новые директивы)
|
||||
let http_enabled = (config.server.http_port.is_some() && config.server.http) ||
|
||||
(config.server.https_port.is_some() && config.server.https);
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
database,
|
||||
lua_engine,
|
||||
sharding_manager,
|
||||
http_enabled,
|
||||
csv_manager,
|
||||
})
|
||||
}
|
||||
|
||||
/// Инициализация базы данных с wait-free структурами
|
||||
fn initialize_database(db: Arc<database::Database>) -> Result<()> {
|
||||
// Создаем системные коллекции с wait-free доступом
|
||||
let _system_collection = db.get_collection("_system");
|
||||
let _users_collection = db.get_collection("_users");
|
||||
let _logs_collection = db.get_collection("_logs");
|
||||
let _procedures_collection = db.get_collection("_procedures");
|
||||
let _triggers_collection = db.get_collection("_triggers");
|
||||
let _csv_imports_collection = db.get_collection("_csv_imports");
|
||||
|
||||
// Создаем директорию для бэкапов
|
||||
let backup_dir = "/futriix/backups";
|
||||
if let Err(e) = std::fs::create_dir_all(backup_dir) {
|
||||
// Используем текущую директорию как запасной вариант
|
||||
let current_backup_dir = "./futriix_backups";
|
||||
if let Err(e2) = std::fs::create_dir_all(current_backup_dir) {
|
||||
eprintln!("Warning: Failed to create backup directory '{}': {}", backup_dir, e);
|
||||
eprintln!("Warning: Also failed to create fallback directory '{}': {}", current_backup_dir, e2);
|
||||
} else {
|
||||
println!("Backup directory created at: {}", current_backup_dir);
|
||||
}
|
||||
} else {
|
||||
println!("Backup directory created at: {}", backup_dir);
|
||||
}
|
||||
|
||||
// Создаем директорию для CSV файлов
|
||||
let csv_dir = "/futriix/csv";
|
||||
if let Err(e) = std::fs::create_dir_all(csv_dir) {
|
||||
// Используем текущую директорию как запасной вариант
|
||||
let current_csv_dir = "./futriix_csv";
|
||||
if let Err(e2) = std::fs::create_dir_all(current_csv_dir) {
|
||||
eprintln!("Warning: Failed to create CSV directory '{}': {}", csv_dir, e);
|
||||
eprintln!("Warning: Also failed to create fallback directory '{}': {}", current_csv_dir, e2);
|
||||
} else {
|
||||
println!("CSV directory created at: {}", current_csv_dir);
|
||||
}
|
||||
} else {
|
||||
println!("CSV directory created at: {}", csv_dir);
|
||||
}
|
||||
|
||||
// Создаем директорию для статических файлов
|
||||
let static_dir = "static";
|
||||
if let Err(e) = std::fs::create_dir_all(static_dir) {
|
||||
eprintln!("Warning: Failed to create static files directory '{}': {}", static_dir, e);
|
||||
} else {
|
||||
println!("Static files directory created at: {}", static_dir);
|
||||
}
|
||||
|
||||
// СОЗДАЕМ ПРОСТОЙ INDEX.HTML ДЛЯ ТЕСТИРОВАНИЯ
|
||||
let index_html_content = r#"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Futriix Database Server</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { color: #00bfff; }
|
||||
.status { padding: 10px; background: #f0f0f0; border-radius: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Futriix Database Server</h1>
|
||||
<div class="status">
|
||||
<p>Server is running successfully!</p>
|
||||
<p>This is a test page to verify HTTP server functionality.</p>
|
||||
<p>Current time: <span id="time"></span></p>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('time').textContent = new Date().toLocaleString();
|
||||
</script>
|
||||
</body>
|
||||
</html>"#;
|
||||
|
||||
if let Err(e) = std::fs::write("static/index.html", index_html_content) {
|
||||
eprintln!("Warning: Failed to create index.html: {}", e);
|
||||
} else {
|
||||
println!("Created test index.html in static directory");
|
||||
}
|
||||
|
||||
let message = "Database initialized with system collections";
|
||||
println!("{}", message);
|
||||
log_to_file(message);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Запуск сервера с wait-free архитектурой
|
||||
pub async fn run(&self) -> Result<()> {
|
||||
// Определяем режим работы и имя кластера
|
||||
let cluster_name = &self.config.cluster.name;
|
||||
|
||||
println!("Mode: cluster (cluster: '{}')", cluster_name);
|
||||
|
||||
log_to_file("Futriix Database Server started");
|
||||
log_to_file(&format!("Mode: cluster (cluster: '{}')", cluster_name));
|
||||
|
||||
// Запуск HTTP/HTTPS серверов в отдельных задачах, если настроены
|
||||
if self.http_enabled {
|
||||
// ЗАПУСКАЕМ СЕРВЕРЫ В ФОНОВЫХ ЗАДАЧАХ, НЕ БЛОКИРУЯ ОСНОВНОЙ ПОТОК
|
||||
self.start_http_servers_in_background().await?;
|
||||
} else {
|
||||
println!("HTTP/HTTPS servers disabled in configuration");
|
||||
}
|
||||
|
||||
// Добавляем пустую строку после информации о серверах
|
||||
println!();
|
||||
|
||||
let mut lua_shell = LuaShell::new(
|
||||
self.lua_engine.clone(),
|
||||
self.database.clone(),
|
||||
self.sharding_manager.clone(),
|
||||
self.csv_manager.clone(),
|
||||
);
|
||||
|
||||
// Запуск интерактивной оболочки - ЭТО ОСНОВНОЙ ПОТОК ВЫПОЛНЕНИЯ
|
||||
lua_shell.run().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Запуск HTTP/HTTPS серверов в фоновых задачах (не блокирующий)
|
||||
async fn start_http_servers_in_background(&self) -> Result<()> {
|
||||
let static_config = self::http::StaticFilesConfig::default();
|
||||
let acl_config = self::http::AclConfig {
|
||||
enabled: self.config.acl.enabled,
|
||||
allowed_ips: self.config.acl.allowed_ips.clone(),
|
||||
denied_ips: self.config.acl.denied_ips.clone(),
|
||||
};
|
||||
|
||||
// Запуск HTTP сервера, если настроен и включен
|
||||
if let Some(http_port) = self.config.server.http_port {
|
||||
if self.config.server.http {
|
||||
let http_addr = format!("{}:{}", self.config.server.host, http_port);
|
||||
let http_config = self::http::HttpConfig {
|
||||
enabled: true,
|
||||
port: http_port,
|
||||
http2_enabled: self.config.server.http2_enabled.unwrap_or(false),
|
||||
};
|
||||
|
||||
let db_clone = self.database.clone();
|
||||
let static_config_clone = static_config.clone();
|
||||
let acl_config_clone = acl_config.clone();
|
||||
|
||||
// ЗАПУСКАЕМ В ФОНОВОЙ ЗАДАЧЕ БЕЗ ОЖИДАНИЯ
|
||||
tokio::spawn(async move {
|
||||
println!("Starting HTTP server on {}...", http_addr);
|
||||
match self::http::start_http_server(&http_addr, db_clone, static_config_clone, http_config, acl_config_clone).await {
|
||||
Ok(_) => {
|
||||
let message = format!("HTTP server started on {}", http_addr);
|
||||
println!("{}", message);
|
||||
log_to_file(&message);
|
||||
}
|
||||
Err(e) => {
|
||||
let message = format!("Failed to start HTTP server: {}", e);
|
||||
eprintln!("{}", message);
|
||||
log_to_file(&message);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
println!("HTTP server disabled in configuration");
|
||||
}
|
||||
}
|
||||
|
||||
// Запуск HTTPS сервера, если настроен и включен
|
||||
if let Some(https_port) = self.config.server.https_port {
|
||||
if self.config.server.https && self.config.tls.enabled {
|
||||
let https_addr = format!("{}:{}", self.config.server.host, https_port);
|
||||
let tls_config = self::http::TlsConfig {
|
||||
enabled: self.config.tls.enabled,
|
||||
cert_path: self.config.tls.cert_path.clone(),
|
||||
key_path: self.config.tls.key_path.clone(),
|
||||
};
|
||||
|
||||
let db_clone = self.database.clone();
|
||||
let static_config_clone = static_config.clone();
|
||||
let acl_config_clone = acl_config.clone();
|
||||
|
||||
// ЗАПУСКАЕМ В ФОНОВОЙ ЗАДАЧЕ БЕЗ ОЖИДАНИЯ
|
||||
tokio::spawn(async move {
|
||||
println!("Starting HTTPS server on {}...", https_addr);
|
||||
match self::http::start_https_server(&https_addr, db_clone, static_config_clone, tls_config, acl_config_clone).await {
|
||||
Ok(_) => {
|
||||
let message = format!("HTTPS server started on {}", https_addr);
|
||||
println!("{}", message);
|
||||
log_to_file(&message);
|
||||
}
|
||||
Err(e) => {
|
||||
let message = format!("Failed to start HTTPS server: {}", e);
|
||||
eprintln!("{}", message);
|
||||
log_to_file(&message);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if !self.config.tls.enabled {
|
||||
println!("HTTPS disabled: TLS not enabled in configuration");
|
||||
} else {
|
||||
println!("HTTPS server disabled in configuration");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Получение менеджера шардинга (для тестов и расширений)
|
||||
#[allow(dead_code)]
|
||||
pub fn get_sharding_manager(&self) -> Arc<sharding::ShardingManager> {
|
||||
self.sharding_manager.clone()
|
||||
}
|
||||
|
||||
/// Получение менеджера CSV (для тестов и расширений)
|
||||
#[allow(dead_code)]
|
||||
pub fn get_csv_manager(&self) -> Arc<csv_import_export::CsvManager> {
|
||||
self.csv_manager.clone()
|
||||
}
|
||||
}
|
||||
602
src/server/sharding.rs
Executable file
602
src/server/sharding.rs
Executable file
@ -0,0 +1,602 @@
|
||||
// src/server/sharding.rs
|
||||
//! Модуль шардинга с консистентным хэшированием и Raft протоколом
|
||||
//!
|
||||
//! Объединяет функционал шардинга и репликации с wait-free архитектурой
|
||||
//! и реализацией Raft консенсуса для работы в production.
|
||||
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{interval, Duration};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use siphasher::sip::SipHasher13;
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::common::protocol;
|
||||
|
||||
/// Состояния узла в Raft протоколе
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum RaftState {
|
||||
Follower,
|
||||
Candidate,
|
||||
Leader,
|
||||
}
|
||||
|
||||
/// Информация о Raft узле
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RaftNode {
|
||||
pub node_id: String,
|
||||
pub address: String,
|
||||
pub state: RaftState,
|
||||
pub term: u64,
|
||||
pub voted_for: Option<String>,
|
||||
pub last_heartbeat: i64,
|
||||
}
|
||||
|
||||
/// Информация о шард-узле с Raft
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ShardNode {
|
||||
pub node_id: String,
|
||||
pub address: String,
|
||||
pub capacity: u64,
|
||||
pub used: u64,
|
||||
pub collections: Vec<String>,
|
||||
pub raft_info: RaftNode,
|
||||
}
|
||||
|
||||
/// Состояние шардинга для коллекции
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CollectionSharding {
|
||||
pub shard_key: String,
|
||||
pub virtual_nodes: usize,
|
||||
pub ring: BTreeMap<u64, String>, // consistent hash ring
|
||||
}
|
||||
|
||||
/// События репликации
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum ReplicationEvent {
|
||||
Command(protocol::Command),
|
||||
SyncRequest,
|
||||
Heartbeat,
|
||||
RaftVoteRequest { term: u64, candidate_id: String },
|
||||
RaftVoteResponse { term: u64, vote_granted: bool },
|
||||
RaftAppendEntries { term: u64, leader_id: String },
|
||||
}
|
||||
|
||||
/// Менеджер шардинга и репликации с Raft
|
||||
#[derive(Clone)]
|
||||
pub struct ShardingManager {
|
||||
// Шардинг компоненты
|
||||
nodes: Arc<DashMap<String, ShardNode>>, // Lock-free хранение узлов
|
||||
collections: Arc<DashMap<String, CollectionSharding>>, // Lock-free хранение коллекций
|
||||
virtual_nodes_per_node: usize,
|
||||
|
||||
// Raft компоненты
|
||||
current_term: Arc<AtomicU64>, // Текущий терм Raft
|
||||
voted_for: Arc<DashMap<u64, String>>, // Голоса за термы
|
||||
is_leader: Arc<AtomicBool>, // Флаг лидера
|
||||
cluster_formed: Arc<AtomicBool>, // Флаг сформированности кластера
|
||||
|
||||
// Репликация компоненты
|
||||
replication_tx: Arc<mpsc::Sender<ReplicationEvent>>,
|
||||
sequence_number: Arc<AtomicU64>,
|
||||
replication_enabled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ShardingManager {
|
||||
/// Создание нового менеджера шардинга и репликации
|
||||
pub fn new(virtual_nodes_per_node: usize, replication_enabled: bool) -> Self {
|
||||
let (tx, rx) = mpsc::channel(1000); // Убрал ненужный mut
|
||||
|
||||
let manager = Self {
|
||||
nodes: Arc::new(DashMap::new()),
|
||||
collections: Arc::new(DashMap::new()),
|
||||
virtual_nodes_per_node,
|
||||
current_term: Arc::new(AtomicU64::new(0)),
|
||||
voted_for: Arc::new(DashMap::new()),
|
||||
is_leader: Arc::new(AtomicBool::new(false)),
|
||||
cluster_formed: Arc::new(AtomicBool::new(false)),
|
||||
replication_tx: Arc::new(tx),
|
||||
sequence_number: Arc::new(AtomicU64::new(0)),
|
||||
replication_enabled: Arc::new(AtomicBool::new(replication_enabled)),
|
||||
};
|
||||
|
||||
// Запуск фоновой задачи обработки репликации и Raft
|
||||
let manager_clone = manager.clone();
|
||||
tokio::spawn(async move {
|
||||
manager_clone.run_replication_loop(rx).await;
|
||||
});
|
||||
|
||||
manager
|
||||
}
|
||||
|
||||
/// Фоновая задача обработки репликации и Raft
|
||||
async fn run_replication_loop(self, mut rx: mpsc::Receiver<ReplicationEvent>) {
|
||||
let mut heartbeat_interval = interval(Duration::from_millis(1000));
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(event) = rx.recv() => {
|
||||
self.handle_replication_event(event).await;
|
||||
}
|
||||
_ = heartbeat_interval.tick() => {
|
||||
if self.is_leader.load(Ordering::SeqCst) && self.replication_enabled.load(Ordering::SeqCst) {
|
||||
let _ = self.send_heartbeat().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Обработка событий репликации
|
||||
async fn handle_replication_event(&self, event: ReplicationEvent) {
|
||||
if !self.replication_enabled.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
match event {
|
||||
ReplicationEvent::Command(cmd) => {
|
||||
self.replicate_command(cmd).await;
|
||||
}
|
||||
ReplicationEvent::SyncRequest => {
|
||||
self.sync_with_nodes().await;
|
||||
}
|
||||
ReplicationEvent::Heartbeat => {
|
||||
let _ = self.send_heartbeat().await;
|
||||
}
|
||||
ReplicationEvent::RaftVoteRequest { term, candidate_id } => {
|
||||
self.handle_vote_request(term, candidate_id).await;
|
||||
}
|
||||
ReplicationEvent::RaftVoteResponse { term, vote_granted } => {
|
||||
self.handle_vote_response(term, vote_granted).await;
|
||||
}
|
||||
ReplicationEvent::RaftAppendEntries { term, leader_id } => {
|
||||
self.handle_append_entries(term, leader_id).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Репликация команды на другие узлы
|
||||
async fn replicate_command(&self, command: protocol::Command) {
|
||||
let sequence = self.sequence_number.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
for entry in self.nodes.iter() {
|
||||
let node = entry.value();
|
||||
if node.raft_info.state != RaftState::Leader {
|
||||
let node_addr = node.address.clone();
|
||||
let cmd_clone = command.clone();
|
||||
let seq_clone = sequence;
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::send_command_to_node(&node_addr, &cmd_clone, seq_clone).await {
|
||||
eprintln!("Failed to replicate to {}: {}", node_addr, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Отправка команды на удаленный узел
|
||||
async fn send_command_to_node(node: &str, command: &protocol::Command, sequence: u64) -> Result<()> {
|
||||
let mut stream = match tokio::net::TcpStream::connect(node).await {
|
||||
Ok(stream) => stream,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to connect to {}: {}", node, e);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let message = protocol::ReplicationMessage {
|
||||
sequence,
|
||||
command: command.clone(),
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
};
|
||||
|
||||
let bytes = protocol::serialize(&message)?;
|
||||
|
||||
if let Err(e) = stream.write_all(&bytes).await {
|
||||
eprintln!("Failed to send command to {}: {}", node, e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Синхронизация с другими узлами
|
||||
async fn sync_with_nodes(&self) {
|
||||
println!("Starting sync with {} nodes", self.nodes.len());
|
||||
for entry in self.nodes.iter() {
|
||||
let node_addr = entry.value().address.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::sync_with_node(&node_addr).await {
|
||||
eprintln!("Failed to sync with {}: {}", node_addr, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Синхронизация с удаленным узлом
|
||||
async fn sync_with_node(_node: &str) -> Result<()> {
|
||||
// TODO: Реализовать wait-free синхронизацию данных
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Отправка heartbeat
|
||||
async fn send_heartbeat(&self) -> Result<()> {
|
||||
for entry in self.nodes.iter() {
|
||||
let node = entry.value();
|
||||
if node.raft_info.state == RaftState::Follower {
|
||||
let node_addr = node.address.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::send_heartbeat_to_node(&node_addr).await {
|
||||
eprintln!("Heartbeat failed for {}: {}", node_addr, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Отправка heartbeat на удаленный узел
|
||||
async fn send_heartbeat_to_node(node: &str) -> Result<()> {
|
||||
let mut stream = match tokio::net::TcpStream::connect(node).await {
|
||||
Ok(stream) => stream,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to connect to {} for heartbeat: {}", node, e);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let heartbeat = protocol::ReplicationMessage {
|
||||
sequence: 0,
|
||||
command: protocol::Command::CallProcedure { name: "heartbeat".to_string() },
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
};
|
||||
|
||||
let bytes = protocol::serialize(&heartbeat)?;
|
||||
|
||||
if let Err(e) = stream.write_all(&bytes).await {
|
||||
eprintln!("Failed to send heartbeat to {}: {}", node, e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Raft методы
|
||||
/// Обработка запроса голоса
|
||||
async fn handle_vote_request(&self, term: u64, candidate_id: String) {
|
||||
let current_term = self.current_term.load(Ordering::SeqCst);
|
||||
|
||||
if term > current_term {
|
||||
self.current_term.store(term, Ordering::SeqCst);
|
||||
self.voted_for.insert(term, candidate_id.clone());
|
||||
// TODO: Отправить положительный ответ
|
||||
}
|
||||
// TODO: Отправить отрицательный ответ если условия не выполнены
|
||||
}
|
||||
|
||||
/// Обработка ответа голоса
|
||||
async fn handle_vote_response(&self, term: u64, vote_granted: bool) {
|
||||
if vote_granted && term == self.current_term.load(Ordering::SeqCst) {
|
||||
// TODO: Подсчитать голоса и перейти в лидеры при большинстве
|
||||
}
|
||||
}
|
||||
|
||||
/// Обработка AppendEntries RPC
|
||||
async fn handle_append_entries(&self, term: u64, leader_id: String) {
|
||||
let current_term = self.current_term.load(Ordering::SeqCst);
|
||||
|
||||
if term >= current_term {
|
||||
self.current_term.store(term, Ordering::SeqCst);
|
||||
self.is_leader.store(false, Ordering::SeqCst);
|
||||
// TODO: Обновить состояние follower
|
||||
}
|
||||
}
|
||||
|
||||
// Шардинг методы
|
||||
/// Добавление шард-узла с Raft информацией
|
||||
pub fn add_node(&self, node_id: String, address: String, capacity: u64) -> Result<()> {
|
||||
let raft_node = RaftNode {
|
||||
node_id: node_id.clone(),
|
||||
address: address.clone(),
|
||||
state: RaftState::Follower,
|
||||
term: 0,
|
||||
voted_for: None,
|
||||
last_heartbeat: chrono::Utc::now().timestamp(),
|
||||
};
|
||||
|
||||
let node = ShardNode {
|
||||
node_id: node_id.clone(),
|
||||
address,
|
||||
capacity,
|
||||
used: 0,
|
||||
collections: Vec::new(),
|
||||
raft_info: raft_node,
|
||||
};
|
||||
|
||||
self.nodes.insert(node_id, node);
|
||||
|
||||
// Проверяем сформированность кластера (минимум 2 узла для шардинга)
|
||||
if self.nodes.len() >= 2 {
|
||||
self.cluster_formed.store(true, Ordering::SeqCst);
|
||||
println!("Cluster formed with {} nodes", self.nodes.len());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Удаление шард-узла
|
||||
pub fn remove_node(&self, node_id: &str) -> Result<()> {
|
||||
self.nodes.remove(node_id);
|
||||
|
||||
// Проверяем сформированность кластера после удаления
|
||||
if self.nodes.len() < 2 {
|
||||
self.cluster_formed.store(false, Ordering::SeqCst);
|
||||
self.is_leader.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Настройка шардинга для коллекции
|
||||
pub fn setup_collection_sharding(&self, collection: &str, shard_key: &str) -> Result<()> {
|
||||
// Проверка наличия кластера перед настройкой шардинга
|
||||
if !self.cluster_formed.load(Ordering::SeqCst) {
|
||||
return Err(crate::common::FutriixError::DatabaseError(
|
||||
"Cannot setup sharding: cluster not formed. Need at least 2 nodes.".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
let sharding = CollectionSharding {
|
||||
shard_key: shard_key.to_string(),
|
||||
virtual_nodes: self.virtual_nodes_per_node,
|
||||
ring: BTreeMap::new(),
|
||||
};
|
||||
|
||||
self.collections.insert(collection.to_string(), sharding);
|
||||
self.rebuild_ring(collection)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Перестроение хэш-ринга для коллекции
|
||||
fn rebuild_ring(&self, collection: &str) -> Result<()> {
|
||||
if let Some(mut sharding) = self.collections.get_mut(collection) {
|
||||
sharding.ring.clear();
|
||||
|
||||
for entry in self.nodes.iter() {
|
||||
let node_id = entry.key();
|
||||
for i in 0..sharding.virtual_nodes {
|
||||
let key = format!("{}-{}", node_id, i);
|
||||
let hash = self.hash_key(&key);
|
||||
sharding.ring.insert(hash, node_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Хэширование ключа
|
||||
fn hash_key(&self, key: &str) -> u64 {
|
||||
let mut hasher = SipHasher13::new();
|
||||
key.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Поиск узла для ключа
|
||||
pub fn find_node_for_key(&self, collection: &str, key_value: &str) -> Result<Option<String>> {
|
||||
// Проверка наличия кластера перед поиском узла
|
||||
if !self.cluster_formed.load(Ordering::SeqCst) {
|
||||
return Err(crate::common::FutriixError::DatabaseError(
|
||||
"Cannot find node: cluster not formed. Need at least 2 nodes.".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(sharding) = self.collections.get(collection) {
|
||||
let key_hash = self.hash_key(key_value);
|
||||
|
||||
// Поиск в хэш-ринге (консистентное хэширование)
|
||||
let mut range = sharding.ring.range(key_hash..);
|
||||
if let Some((_, node_id)) = range.next() {
|
||||
return Ok(Some(node_id.clone()));
|
||||
}
|
||||
|
||||
// Если не найдено в верхней части ринга, берем первый узел
|
||||
if let Some((_, node_id)) = sharding.ring.iter().next() {
|
||||
return Ok(Some(node_id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Миграция шарда
|
||||
pub fn migrate_shard(&self, collection: &str, from_node: &str, to_node: &str, shard_key: &str) -> Result<()> {
|
||||
// Проверка наличия кластера перед миграцией
|
||||
if !self.cluster_formed.load(Ordering::SeqCst) {
|
||||
return Err(crate::common::FutriixError::DatabaseError(
|
||||
"Cannot migrate shard: cluster not formed. Need at least 2 nodes.".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
println!("Migrating shard for collection '{}' from {} to {} with key {}",
|
||||
collection, from_node, to_node, shard_key);
|
||||
|
||||
self.rebuild_ring(collection)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ребалансировка кластера
|
||||
pub fn rebalance_cluster(&self) -> Result<()> {
|
||||
// Проверка наличия кластера перед ребалансировкой
|
||||
if !self.cluster_formed.load(Ordering::SeqCst) {
|
||||
return Err(crate::common::FutriixError::DatabaseError(
|
||||
"Cannot rebalance cluster: cluster not formed. Need at least 2 nodes.".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
println!("Rebalancing cluster with {} nodes", self.nodes.len());
|
||||
|
||||
// Перестраиваем все хэш-ринги
|
||||
for mut entry in self.collections.iter_mut() {
|
||||
let sharding = entry.value_mut();
|
||||
sharding.ring.clear();
|
||||
|
||||
for node_entry in self.nodes.iter() {
|
||||
let node_id = node_entry.key();
|
||||
for i in 0..sharding.virtual_nodes {
|
||||
let key = format!("{}-{}", node_id, i);
|
||||
let hash = self.hash_key(&key);
|
||||
sharding.ring.insert(hash, node_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ребалансировка узлов кластера
|
||||
self.rebalance_nodes()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ребалансировка узлов кластера
|
||||
fn rebalance_nodes(&self) -> Result<()> {
|
||||
println!("Rebalancing nodes in cluster...");
|
||||
|
||||
// Рассчитываем среднюю загрузку
|
||||
let total_capacity: u64 = self.nodes.iter().map(|entry| entry.value().capacity).sum();
|
||||
let total_used: u64 = self.nodes.iter().map(|entry| entry.value().used).sum();
|
||||
let avg_usage = if total_capacity > 0 { total_used as f64 / total_capacity as f64 } else { 0.0 };
|
||||
|
||||
println!("Cluster usage: {:.2}% ({} / {})", avg_usage * 100.0, total_used, total_capacity);
|
||||
|
||||
// TODO: Реализовать алгоритм ребалансировки узлов
|
||||
// - Определить перегруженные и недогруженные узлы
|
||||
// - Перераспределить данные между узлами
|
||||
// - Обновить метаданные шардинга
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Получение статуса кластера
|
||||
pub fn get_cluster_status(&self) -> Result<protocol::ClusterStatus> {
|
||||
let mut cluster_nodes = Vec::new();
|
||||
let mut total_capacity = 0;
|
||||
let mut total_used = 0;
|
||||
let mut raft_nodes = Vec::new();
|
||||
|
||||
for entry in self.nodes.iter() {
|
||||
let node = entry.value();
|
||||
total_capacity += node.capacity;
|
||||
total_used += node.used;
|
||||
|
||||
cluster_nodes.push(protocol::ShardInfo {
|
||||
node_id: node.node_id.clone(),
|
||||
address: node.address.clone(),
|
||||
capacity: node.capacity,
|
||||
used: node.used,
|
||||
collections: node.collections.clone(),
|
||||
});
|
||||
|
||||
raft_nodes.push(protocol::RaftNodeInfo {
|
||||
node_id: node.node_id.clone(),
|
||||
address: node.address.clone(),
|
||||
state: match node.raft_info.state {
|
||||
RaftState::Leader => "leader".to_string(),
|
||||
RaftState::Follower => "follower".to_string(),
|
||||
RaftState::Candidate => "candidate".to_string(),
|
||||
},
|
||||
term: node.raft_info.term,
|
||||
last_heartbeat: node.raft_info.last_heartbeat,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(protocol::ClusterStatus {
|
||||
nodes: cluster_nodes,
|
||||
total_capacity,
|
||||
total_used,
|
||||
rebalance_needed: false,
|
||||
cluster_formed: self.cluster_formed.load(Ordering::SeqCst),
|
||||
leader_exists: self.is_leader.load(Ordering::SeqCst),
|
||||
raft_nodes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Получение списка Raft узлов
|
||||
pub fn get_raft_nodes(&self) -> Vec<RaftNode> {
|
||||
self.nodes.iter()
|
||||
.map(|entry| entry.value().raft_info.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Проверка сформированности кластера
|
||||
pub fn is_cluster_formed(&self) -> bool {
|
||||
self.cluster_formed.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Raft выборы - начало кампании
|
||||
pub fn start_election(&self) -> Result<()> {
|
||||
if !self.cluster_formed.load(Ordering::SeqCst) {
|
||||
return Err(crate::common::FutriixError::DatabaseError(
|
||||
"Cluster not formed. Need at least 2 nodes.".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
let new_term = self.current_term.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
println!("Starting election for term {}", new_term);
|
||||
|
||||
// Переход в состояние candidate
|
||||
self.is_leader.store(false, Ordering::SeqCst);
|
||||
|
||||
// TODO: Реализовать полную логику Raft выборов
|
||||
// - Отправка RequestVote RPC на другие узлы
|
||||
// - Сбор голосов
|
||||
// - Переход в состояние Leader при получении большинства
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Отправка команды на репликацию
|
||||
pub async fn replicate(&self, command: protocol::Command) -> Result<()> {
|
||||
if !self.replication_enabled.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.replication_tx.send(ReplicationEvent::Command(command)).await
|
||||
.map_err(|e| crate::common::FutriixError::ReplicationError(e.to_string()))
|
||||
}
|
||||
|
||||
/// Запрос синхронизации с другими узлами
|
||||
pub async fn request_sync(&self) -> Result<()> {
|
||||
if !self.replication_enabled.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.replication_tx.send(ReplicationEvent::SyncRequest).await
|
||||
.map_err(|e| crate::common::FutriixError::ReplicationError(e.to_string()))
|
||||
}
|
||||
|
||||
/// Получение списка узлов репликации
|
||||
pub fn get_nodes(&self) -> Vec<ShardNode> {
|
||||
self.nodes.iter()
|
||||
.map(|entry| entry.value().clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Получение текущего номера последовательности
|
||||
pub fn get_sequence_number(&self) -> u64 {
|
||||
self.sequence_number.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Проверка, включена ли репликация
|
||||
pub fn is_replication_enabled(&self) -> bool {
|
||||
self.replication_enabled.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Получение информации об узле
|
||||
pub fn get_node(&self, node_id: &str) -> Option<ShardNode> {
|
||||
self.nodes.get(node_id).map(|entry| entry.value().clone())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user