legion-data 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +7 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/ci.yml +20 -2
- data/.rubocop.yml +4 -0
- data/CHANGELOG.md +30 -0
- data/CLAUDE.md +78 -12
- data/lib/legion/data/connection.rb +286 -9
- data/lib/legion/data/local.rb +50 -1
- data/lib/legion/data/settings.rb +54 -28
- data/lib/legion/data/version.rb +1 -1
- data/lib/legion/data.rb +41 -15
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d7162dcd4c845698a6c47a4a39f5c70b9da51a5bac097ec9f631934d31716ac
|
|
4
|
+
data.tar.gz: 13af3da2d55afaec4c54ad77ba97d23d7ebaa35789b22bbb9f6cbe1049751191
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a67d01180e7ec8df32339a2e7bfe3a28f972176d2ac921fbaebc78c3194d098e9d11ddc01fb84db65cda53aaa42ee488c928d68583cecacdefe78eefbe313ba
|
|
7
|
+
data.tar.gz: 7d4f7e00c415d7af0975d575617221d0c010fb3cff4d4c1fae7c4562ee817fd135236ba7b466048fddd8b1c5d06eb84029a3da61c6f900055fab225861efe453
|
data/.github/CODEOWNERS
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: bundler
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
day: monday
|
|
8
|
+
open-pull-requests-limit: 5
|
|
9
|
+
labels:
|
|
10
|
+
- "type:dependencies"
|
|
11
|
+
- package-ecosystem: github-actions
|
|
12
|
+
directory: /
|
|
13
|
+
schedule:
|
|
14
|
+
interval: weekly
|
|
15
|
+
day: monday
|
|
16
|
+
open-pull-requests-limit: 5
|
|
17
|
+
labels:
|
|
18
|
+
- "type:dependencies"
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -3,14 +3,32 @@ on:
|
|
|
3
3
|
push:
|
|
4
4
|
branches: [main]
|
|
5
5
|
pull_request:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: '0 9 * * 1'
|
|
6
8
|
|
|
7
9
|
jobs:
|
|
8
10
|
ci:
|
|
9
11
|
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
10
12
|
|
|
13
|
+
lint:
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/lint-patterns.yml@main
|
|
15
|
+
|
|
16
|
+
security:
|
|
17
|
+
uses: LegionIO/.github/.github/workflows/security-scan.yml@main
|
|
18
|
+
|
|
19
|
+
version-changelog:
|
|
20
|
+
uses: LegionIO/.github/.github/workflows/version-changelog.yml@main
|
|
21
|
+
|
|
22
|
+
dependency-review:
|
|
23
|
+
uses: LegionIO/.github/.github/workflows/dependency-review.yml@main
|
|
24
|
+
|
|
25
|
+
stale:
|
|
26
|
+
if: github.event_name == 'schedule'
|
|
27
|
+
uses: LegionIO/.github/.github/workflows/stale.yml@main
|
|
28
|
+
|
|
11
29
|
release:
|
|
12
|
-
needs: ci
|
|
30
|
+
needs: [ci, lint]
|
|
13
31
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
14
32
|
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
15
33
|
secrets:
|
|
16
|
-
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
|
34
|
+
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Legion::Data Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0] - 2026-03-25
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Connection pool starvation**: `max_connections`, `pool_timeout`, `preconnect`, and all other Sequel options were never forwarded to `Sequel.connect` — pool was stuck at Sequel's default of 4 connections regardless of settings. 5+ second "slow queries" in daemon logs were actually pool wait time (5s `pool_timeout`) + fast query (~19ms). Now all configured options flow through properly.
|
|
7
|
+
- **Local DB had same issue**: `Legion::Data::Local.setup` used bare `Sequel.sqlite(path)` with no options. Now forwards SQLite adapter options (`timeout`, `readonly`, `disable_dqs`) via `Sequel.connect`.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **Flat settings structure**: all connection settings now live directly on `data.*` instead of nested `data.connection.*` or `data.adapter_opts.*`. Users configure `data.max_connections`, `data.pool_timeout`, `data.connect_timeout`, etc. regardless of adapter — legion-data figures out which options apply.
|
|
11
|
+
- Default `max_connections` raised from 10 to 25 (was never applied before anyway)
|
|
12
|
+
- Default `preconnect` set to `'concurrently'` (warm pool at boot)
|
|
13
|
+
- Default `pool_timeout` remains 5s (now actually enforced)
|
|
14
|
+
- Per-adapter defaults applied at connection time via `ADAPTER_DEFAULTS`: sqlite (`timeout: 5000`, `readonly: false`, `disable_dqs: true`), postgres (`connect_timeout: 20`, `sslmode: 'disable'`), mysql2 (`connect_timeout: 120`, `encoding: 'utf8mb4'`)
|
|
15
|
+
- Adapter-specific settings (`connect_timeout`, `read_timeout`, `write_timeout`, `encoding`, `sql_mode`, `sslmode`, `sslrootcert`, `search_path`, `timeout`, `readonly`, `disable_dqs`) default to nil in settings and resolve to adapter built-in defaults — only forwarded when the current adapter supports them
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `GENERIC_KEYS`, `ADAPTER_KEYS`, `ADAPTER_DEFAULTS` constants on `Connection` for option whitelisting and defaults
|
|
19
|
+
- Connection health extensions (non-SQLite only): `connection_validator` (pings idle connections, default timeout 600s) and `connection_expiration` (retires old connections, default timeout 14400s) — both enabled by default via `data.connection_validation` and `data.connection_expiration`
|
|
20
|
+
- `Legion::Data::Connection.stats` — comprehensive connection metrics: pool stats (type, size, available, in_use, waiting), tuning snapshot, and adapter-specific database stats (postgres: `pg_stat_activity`, `pg_database_size`, server settings; sqlite: PRAGMAs, file size; mysql: `information_schema`, `SHOW STATUS`)
|
|
21
|
+
- `Legion::Data::Connection.pool_stats` — works across all Sequel pool types (`timed_queue`, `threaded`, `single`, sharded variants)
|
|
22
|
+
- `Legion::Data::Local.stats` — local SQLite metrics: PRAGMAs, file size, database size, registered migrations
|
|
23
|
+
- `Legion::Data.stats` — combined `{ shared: Connection.stats, local: Local.stats }` for `/api/stats` endpoint
|
|
24
|
+
- `data.query_log` flag (default `false`): when enabled, pipes ALL SQL queries to `~/.legionio/logs/data-shared-query.log` (shared) or `data-local-query.log` (local) via dedicated `QueryFileLogger` — isolated from the main `Legion::Logging` domain so debug query floods don't pollute application logs
|
|
25
|
+
- `Legion::Data::Connection::QueryFileLogger` — thread-safe file-based logger with timestamped entries, used by both shared and local query log modes
|
|
26
|
+
- `Legion::Data::Connection::SlowQueryLogger` — wraps tagged `Legion::Logging::Logger`, prefixes warn-level messages with `[slow-query]`
|
|
27
|
+
- `data.local.query_log` flag (default `false`): same as above but for the local SQLite connection
|
|
28
|
+
- **StaticCache infrastructure** for lookup models: `Legion::Data.setup_static_cache` applies `Sequel::Plugins::StaticCache` to `Extension`, `Runner`, `Function` — loads entire tables into frozen in-memory hashes for zero-DB-hit reads. Enabled via `data.cache.static_cache: true` (default `false`).
|
|
29
|
+
- `Legion::Data.reload_static_cache` — refreshes in-memory static cache after hot-loading new extensions
|
|
30
|
+
- **External cache infrastructure**: `Legion::Data.setup_external_cache` applies `Sequel::Plugins::Caching` to `Relationship` (ttl 10s), `Node` (ttl 10s), `Setting` (ttl configurable) via `Legion::Cache` backend. Activates when `data.cache.auto_enable` is true and `Legion::Cache` is loaded.
|
|
31
|
+
- `data.cache.static_cache` setting (default `false`)
|
|
32
|
+
|
|
3
33
|
## [1.5.3] - 2026-03-25
|
|
4
34
|
|
|
5
35
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
Manages persistent database storage for the LegionIO framework. Supports SQLite (default), MySQL, and PostgreSQL via Sequel ORM. Provides automatic schema migrations and data models for extensions, functions, runners, nodes, tasks, settings, digital workers, task relationships, Apollo shared knowledge tables (PostgreSQL only), tenants, webhooks, audit log, and archive tables. Also provides a parallel local SQLite database (`Legion::Data::Local`) for agentic cognitive state persistence.
|
|
9
9
|
|
|
10
10
|
**GitHub**: https://github.com/LegionIO/legion-data
|
|
11
|
-
**Version**: 1.
|
|
11
|
+
**Version**: 1.6.0
|
|
12
12
|
**License**: Apache-2.0
|
|
13
13
|
|
|
14
14
|
## Supported Databases
|
|
@@ -28,13 +28,22 @@ Legion::Data (singleton module)
|
|
|
28
28
|
├── .setup # Connect, migrate, load models, setup cache, setup local
|
|
29
29
|
├── .connection # Sequel database handle (shared/central)
|
|
30
30
|
├── .local # Legion::Data::Local accessor
|
|
31
|
+
├── .stats # Combined { shared: Connection.stats, local: Local.stats }
|
|
32
|
+
├── .reload_static_cache # Refresh in-memory StaticCache after hot-loading extensions
|
|
31
33
|
├── .shutdown # Close both connections
|
|
32
34
|
│
|
|
33
35
|
├── Connection # Sequel database connection management (shared)
|
|
34
36
|
│ ├── .adapter # Reads from settings (sqlite, mysql2, postgres)
|
|
35
37
|
│ ├── .setup # Establish connection (dev_mode fallback to SQLite if network DB unreachable)
|
|
36
38
|
│ ├── .sequel # Raw Sequel::Database accessor
|
|
37
|
-
│
|
|
39
|
+
│ ├── .stats # Pool metrics, tuning snapshot, adapter-specific DB stats
|
|
40
|
+
│ ├── .pool_stats # Connection pool usage (size, available, in_use, waiting)
|
|
41
|
+
│ ├── .shutdown # Close connection
|
|
42
|
+
│ ├── GENERIC_KEYS # Pool options forwarded to Sequel (:max_connections, :pool_timeout, etc.)
|
|
43
|
+
│ ├── ADAPTER_KEYS # Per-adapter option whitelists (sqlite, postgres, mysql2)
|
|
44
|
+
│ ├── ADAPTER_DEFAULTS # Built-in defaults per adapter when user hasn't set a value
|
|
45
|
+
│ ├── SlowQueryLogger # Wraps Legion::Logging with [slow-query] prefix for Sequel warn
|
|
46
|
+
│ └── QueryFileLogger # Thread-safe file logger for query_log mode (~/.legionio/logs/)
|
|
38
47
|
│
|
|
39
48
|
├── Local # Local SQLite database for agentic cognitive state
|
|
40
49
|
│ ├── .setup # Lazy init — creates legionio_local.db on first access
|
|
@@ -43,6 +52,7 @@ Legion::Data (singleton module)
|
|
|
43
52
|
│ ├── .db_path # Path to the local SQLite file
|
|
44
53
|
│ ├── .model(:table) # Create Sequel::Model bound to local connection
|
|
45
54
|
│ ├── .register_migrations(name:, path:) # Extensions register their migration dirs
|
|
55
|
+
│ ├── .stats # Local SQLite metrics (PRAGMAs, file size, registered migrations)
|
|
46
56
|
│ ├── .shutdown # Close local connection
|
|
47
57
|
│ └── .reset! # Clear all state (testing)
|
|
48
58
|
│
|
|
@@ -105,12 +115,16 @@ Legion::Data (singleton module)
|
|
|
105
115
|
### Key Design Patterns
|
|
106
116
|
|
|
107
117
|
- **Two-Database Architecture**: Shared (MySQL/PG/SQLite) for control plane data + Local (always SQLite) for agentic cognitive state. Two files, always separate, no cross-database joins.
|
|
108
|
-
- **Adapter-Driven**: `Connection.adapter` reads from settings;
|
|
118
|
+
- **Adapter-Driven**: `Connection.adapter` reads from settings; all adapters (including SQLite) use `Sequel.connect` so all options flow through uniformly
|
|
119
|
+
- **Flat Settings**: all connection/pool/adapter options live directly on `data.*` — legion-data resolves which options apply to the current adapter via `ADAPTER_KEYS` whitelists
|
|
120
|
+
- **Per-Adapter Defaults**: `ADAPTER_DEFAULTS` provides built-in defaults (e.g., sqlite timeout 5000, postgres connect_timeout 20) when user hasn't set a value; nil in settings means "use adapter default"
|
|
109
121
|
- **Dev Mode Fallback**: When `dev_mode: true` and network DB unreachable, shared connection falls back to SQLite (`legionio.db`) with warning log
|
|
122
|
+
- **Connection Health**: `connection_validator` (pings idle connections) and `connection_expiration` (retires old connections) extensions auto-enabled for non-SQLite adapters
|
|
110
123
|
- **Cross-DB Migrations**: Shared migrations use IntegerMigrator (Sequel DSL), local migrations use TimestampMigrator (per-extension registration)
|
|
111
124
|
- **Auto-Migration**: Runs Sequel migrations on startup (`auto_migrate: true` by default)
|
|
112
125
|
- **Sequel ORM**: Shared models are `Sequel::Model` subclasses (inherit global connection). Local models use `Legion::Data::Local.model(:table)` (explicit connection binding).
|
|
113
|
-
- **
|
|
126
|
+
- **Two-Tier Caching**: StaticCache (in-process frozen hash, no external deps) for lookup models (Extension, Runner, Function) + external Caching plugin (via `Legion::Cache` — Redis/Memcached/Memory) for dynamic models (Relationship, Node, Setting). Both disabled by default.
|
|
127
|
+
- **Query Log Isolation**: `query_log` flag pipes all SQL to dedicated files (`~/.legionio/logs/data-shared-query.log`, `data-local-query.log`) via `QueryFileLogger` — completely isolated from the `Legion::Logging` domain
|
|
114
128
|
- **Cryptographic Erasure**: Deleting `legionio_local.db` is a hard guarantee — no residual data. Used by `lex-privatecore`.
|
|
115
129
|
- **CLI Executable**: Ships with `legionio_migrate` executable in `exe/` for running database migrations standalone
|
|
116
130
|
|
|
@@ -123,14 +137,40 @@ Legion::Data (singleton module)
|
|
|
123
137
|
"dev_mode": false,
|
|
124
138
|
"dev_fallback": true,
|
|
125
139
|
"connect_on_start": true,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
|
|
141
|
+
"max_connections": 25,
|
|
142
|
+
"pool_timeout": 5,
|
|
143
|
+
"preconnect": "concurrently",
|
|
144
|
+
"single_threaded": false,
|
|
145
|
+
"test": true,
|
|
146
|
+
"name": null,
|
|
147
|
+
|
|
148
|
+
"log": false,
|
|
149
|
+
"query_log": false,
|
|
150
|
+
"log_connection_info": false,
|
|
151
|
+
"log_warn_duration": 1,
|
|
152
|
+
"sql_log_level": "debug",
|
|
153
|
+
|
|
154
|
+
"connection_validation": true,
|
|
155
|
+
"connection_validation_timeout": 600,
|
|
156
|
+
"connection_expiration": true,
|
|
157
|
+
"connection_expiration_timeout": 14400,
|
|
158
|
+
|
|
159
|
+
"connect_timeout": null,
|
|
160
|
+
"read_timeout": null,
|
|
161
|
+
"write_timeout": null,
|
|
162
|
+
"encoding": null,
|
|
163
|
+
"sql_mode": null,
|
|
164
|
+
"sslmode": null,
|
|
165
|
+
"sslrootcert": null,
|
|
166
|
+
"search_path": null,
|
|
167
|
+
"timeout": null,
|
|
168
|
+
"readonly": null,
|
|
169
|
+
"disable_dqs": null,
|
|
170
|
+
|
|
171
|
+
"read_replica_url": null,
|
|
172
|
+
"replicas": [],
|
|
173
|
+
|
|
134
174
|
"creds": {
|
|
135
175
|
"database": "legionio.db"
|
|
136
176
|
},
|
|
@@ -147,6 +187,7 @@ Legion::Data (singleton module)
|
|
|
147
187
|
"local": {
|
|
148
188
|
"enabled": true,
|
|
149
189
|
"database": "legionio_local.db",
|
|
190
|
+
"query_log": false,
|
|
150
191
|
"migrations": {
|
|
151
192
|
"auto_migrate": true
|
|
152
193
|
}
|
|
@@ -154,11 +195,36 @@ Legion::Data (singleton module)
|
|
|
154
195
|
"cache": {
|
|
155
196
|
"connected": false,
|
|
156
197
|
"auto_enable": false,
|
|
198
|
+
"static_cache": false,
|
|
157
199
|
"ttl": 60
|
|
200
|
+
},
|
|
201
|
+
"archival": {
|
|
202
|
+
"retention_days": 90,
|
|
203
|
+
"batch_size": 1000,
|
|
204
|
+
"storage_backend": null
|
|
158
205
|
}
|
|
159
206
|
}
|
|
160
207
|
```
|
|
161
208
|
|
|
209
|
+
Settings are **flat** — all pool, logging, health, and adapter-specific options live directly on `data.*`. Adapter-specific options (e.g., `connect_timeout`, `encoding`, `sslmode`) default to `null` and resolve to per-adapter built-in defaults at connection time:
|
|
210
|
+
|
|
211
|
+
| Adapter | Applied Options | Defaults |
|
|
212
|
+
|---------|----------------|----------|
|
|
213
|
+
| sqlite | `timeout`, `readonly`, `disable_dqs` | `timeout: 5000`, `readonly: false`, `disable_dqs: true` |
|
|
214
|
+
| postgres | `connect_timeout`, `sslmode`, `sslrootcert`, `search_path` | `connect_timeout: 20`, `sslmode: "disable"` |
|
|
215
|
+
| mysql2 | `connect_timeout`, `read_timeout`, `write_timeout`, `encoding`, `sql_mode` | `connect_timeout: 120`, `encoding: "utf8mb4"` |
|
|
216
|
+
|
|
217
|
+
### Caching
|
|
218
|
+
|
|
219
|
+
Two independent caching tiers, both disabled by default:
|
|
220
|
+
|
|
221
|
+
| Tier | Setting | Models | Backend | Use Case |
|
|
222
|
+
|------|---------|--------|---------|----------|
|
|
223
|
+
| **StaticCache** | `data.cache.static_cache: true` | Extension, Runner, Function | In-process frozen Ruby hash | Zero-DB-hit reads for lookup tables. No external deps. Call `Legion::Data.reload_static_cache` after hot-loading extensions. |
|
|
224
|
+
| **External Cache** | `data.cache.auto_enable: true` + `Legion::Cache` loaded | Relationship (10s), Node (10s), Setting (ttl) | `Legion::Cache` (Redis/Memcached/Memory) | Cross-process cache sharing for dynamic models. Requires `legion-cache` gem connected. |
|
|
225
|
+
|
|
226
|
+
For thousands of agents, enable `static_cache` first — biggest impact, zero dependencies. External cache only adds value when you need cross-process sharing via Redis/Memcached.
|
|
227
|
+
|
|
162
228
|
Per-adapter credential defaults are defined in `Settings::CREDS`:
|
|
163
229
|
- **sqlite**: `{ database: "legionio.db" }`
|
|
164
230
|
- **mysql2**: `{ username: "legion", password: "legion", database: "legionio", host: "127.0.0.1", port: 3306 }`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
3
4
|
require 'sequel'
|
|
4
5
|
|
|
5
6
|
module Legion
|
|
@@ -7,6 +8,22 @@ module Legion
|
|
|
7
8
|
module Connection
|
|
8
9
|
ADAPTERS = %i[sqlite mysql2 postgres].freeze
|
|
9
10
|
|
|
11
|
+
GENERIC_KEYS = %i[max_connections pool_timeout preconnect single_threaded test name].freeze
|
|
12
|
+
|
|
13
|
+
ADAPTER_KEYS = {
|
|
14
|
+
sqlite: %i[timeout readonly disable_dqs],
|
|
15
|
+
postgres: %i[connect_timeout sslmode sslrootcert search_path],
|
|
16
|
+
mysql2: %i[connect_timeout read_timeout write_timeout encoding sql_mode]
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
ADAPTER_DEFAULTS = {
|
|
20
|
+
sqlite: { timeout: 5000, readonly: false, disable_dqs: true },
|
|
21
|
+
postgres: { connect_timeout: 20, sslmode: 'disable' },
|
|
22
|
+
mysql2: { connect_timeout: 120, encoding: 'utf8mb4' }
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
QUERY_LOG_DIR = File.expand_path('~/.legionio/logs').freeze
|
|
26
|
+
|
|
10
27
|
# Wraps a tagged Legion::Logging::Logger for Sequel's logger interface.
|
|
11
28
|
# Prefixes warn-level messages with [slow-query] since Sequel uses warn
|
|
12
29
|
# for queries exceeding log_warn_duration.
|
|
@@ -32,6 +49,52 @@ module Legion
|
|
|
32
49
|
end
|
|
33
50
|
end
|
|
34
51
|
|
|
52
|
+
# File-based query logger that writes all SQL to a dedicated log file.
|
|
53
|
+
# Isolated from the main Legion::Logging domain.
|
|
54
|
+
class QueryFileLogger
|
|
55
|
+
attr_reader :path
|
|
56
|
+
|
|
57
|
+
def initialize(path)
|
|
58
|
+
@path = path
|
|
59
|
+
dir = File.dirname(path)
|
|
60
|
+
FileUtils.mkdir_p(dir)
|
|
61
|
+
FileUtils.chmod(0o700, dir) if File.directory?(dir)
|
|
62
|
+
@file = File.open(path, File::WRONLY | File::APPEND | File::CREAT, 0o600)
|
|
63
|
+
@file.sync = true
|
|
64
|
+
@mutex = Mutex.new
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def debug(message)
|
|
68
|
+
write('DEBUG', message)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def info(message)
|
|
72
|
+
write('INFO', message)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def warn(message)
|
|
76
|
+
write('WARN', message)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def error(message)
|
|
80
|
+
write('ERROR', message)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def close
|
|
84
|
+
@mutex.synchronize { @file.close unless @file.closed? }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def write(level, message)
|
|
90
|
+
@mutex.synchronize do
|
|
91
|
+
@file.puts "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S.%L')}] #{level} #{message}"
|
|
92
|
+
end
|
|
93
|
+
rescue IOError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
35
98
|
class << self
|
|
36
99
|
attr_accessor :sequel
|
|
37
100
|
|
|
@@ -40,11 +103,12 @@ module Legion
|
|
|
40
103
|
end
|
|
41
104
|
|
|
42
105
|
def setup
|
|
106
|
+
opts = sequel_opts
|
|
43
107
|
@sequel = if adapter == :sqlite
|
|
44
|
-
::Sequel.sqlite
|
|
108
|
+
::Sequel.connect(opts.merge(adapter: :sqlite, database: sqlite_path))
|
|
45
109
|
else
|
|
46
110
|
begin
|
|
47
|
-
::Sequel.connect(adapter: adapter, **creds_builder)
|
|
111
|
+
::Sequel.connect(opts.merge(adapter: adapter, **creds_builder))
|
|
48
112
|
rescue StandardError => e
|
|
49
113
|
raise unless dev_fallback?
|
|
50
114
|
|
|
@@ -54,7 +118,8 @@ module Legion
|
|
|
54
118
|
)
|
|
55
119
|
end
|
|
56
120
|
@adapter = :sqlite
|
|
57
|
-
|
|
121
|
+
sqlite_opts = sequel_opts
|
|
122
|
+
::Sequel.connect(sqlite_opts.merge(adapter: :sqlite, database: sqlite_path))
|
|
58
123
|
end
|
|
59
124
|
end
|
|
60
125
|
Legion::Settings[:data][:connected] = true
|
|
@@ -70,12 +135,61 @@ module Legion
|
|
|
70
135
|
Legion::Logging.info "Connected to #{adapter}://#{user}@#{host}:#{port}/#{db}"
|
|
71
136
|
end
|
|
72
137
|
end
|
|
73
|
-
|
|
138
|
+
configure_extensions
|
|
74
139
|
connect_with_replicas
|
|
75
140
|
end
|
|
76
141
|
|
|
142
|
+
def stats
|
|
143
|
+
return { connected: false } unless @sequel
|
|
144
|
+
|
|
145
|
+
data = Legion::Settings[:data]
|
|
146
|
+
{
|
|
147
|
+
connected: data[:connected],
|
|
148
|
+
adapter: adapter,
|
|
149
|
+
pool: pool_stats,
|
|
150
|
+
tuning: tuning_stats(data),
|
|
151
|
+
database: database_stats
|
|
152
|
+
}
|
|
153
|
+
rescue StandardError => e
|
|
154
|
+
{ connected: (data[:connected] if data.is_a?(Hash)), adapter: adapter, error: e.message }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def pool_stats
|
|
158
|
+
return {} unless @sequel
|
|
159
|
+
|
|
160
|
+
pool = @sequel.pool
|
|
161
|
+
stats = {
|
|
162
|
+
type: pool.pool_type,
|
|
163
|
+
size: pool.size,
|
|
164
|
+
max_size: pool.respond_to?(:max_size) ? pool.max_size : nil
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
case pool.pool_type
|
|
168
|
+
when :timed_queue, :sharded_timed_queue
|
|
169
|
+
queue_size = pool.instance_variable_get(:@queue)&.size || 0
|
|
170
|
+
stats[:available] = queue_size
|
|
171
|
+
stats[:in_use] = stats[:size] - queue_size
|
|
172
|
+
stats[:waiting] = pool.num_waiting
|
|
173
|
+
when :threaded, :sharded_threaded
|
|
174
|
+
avail = pool.instance_variable_get(:@available_connections)
|
|
175
|
+
stats[:available] = avail&.size || 0
|
|
176
|
+
stats[:in_use] = stats[:size] - stats[:available]
|
|
177
|
+
stats[:waiting] = pool.num_waiting
|
|
178
|
+
when :single, :sharded_single
|
|
179
|
+
stats[:available] = pool.size
|
|
180
|
+
stats[:in_use] = 0
|
|
181
|
+
stats[:waiting] = 0
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
stats.compact
|
|
185
|
+
rescue StandardError
|
|
186
|
+
{}
|
|
187
|
+
end
|
|
188
|
+
|
|
77
189
|
def shutdown
|
|
78
190
|
@sequel&.disconnect
|
|
191
|
+
@query_file_logger&.close
|
|
192
|
+
@query_file_logger = nil
|
|
79
193
|
Legion::Settings[:data][:connected] = false
|
|
80
194
|
Legion::Logging.info 'Legion::Data connection closed' if defined?(Legion::Logging)
|
|
81
195
|
end
|
|
@@ -176,12 +290,175 @@ module Legion
|
|
|
176
290
|
Legion::Settings[:data][:creds][:database] || 'legionio.db'
|
|
177
291
|
end
|
|
178
292
|
|
|
179
|
-
def
|
|
180
|
-
|
|
293
|
+
def sequel_opts
|
|
294
|
+
data = Legion::Settings[:data]
|
|
295
|
+
opts = {}
|
|
181
296
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
297
|
+
# Generic pool options
|
|
298
|
+
GENERIC_KEYS.each do |key|
|
|
299
|
+
val = data[key]
|
|
300
|
+
opts[key] = val unless val.nil?
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Query log mode: all queries to dedicated file, isolated from main domain
|
|
304
|
+
if data[:query_log]
|
|
305
|
+
log_path = File.join(QUERY_LOG_DIR, 'data-shared-query.log')
|
|
306
|
+
@query_file_logger = QueryFileLogger.new(log_path)
|
|
307
|
+
opts[:logger] = @query_file_logger
|
|
308
|
+
opts[:sql_log_level] = :debug
|
|
309
|
+
opts[:log_connection_info] = data[:log_connection_info] || false
|
|
310
|
+
elsif data[:log] && defined?(Legion::Logging)
|
|
311
|
+
# Standard mode: slow-query warnings through Legion::Logging domain
|
|
312
|
+
opts[:logger] = build_data_logger
|
|
313
|
+
opts[:sql_log_level] = data[:sql_log_level]&.to_sym || :debug
|
|
314
|
+
opts[:log_warn_duration] = data[:log_warn_duration]
|
|
315
|
+
opts[:log_connection_info] = data[:log_connection_info] || false
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Adapter-specific: user setting wins, then built-in default, skip if nil
|
|
319
|
+
defaults = ADAPTER_DEFAULTS.fetch(adapter, {})
|
|
320
|
+
ADAPTER_KEYS.fetch(adapter, []).each do |key|
|
|
321
|
+
val = data.key?(key) && !data[key].nil? ? data[key] : defaults[key]
|
|
322
|
+
opts[key] = val unless val.nil?
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
opts
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def tuning_stats(data)
|
|
329
|
+
tuning = {}
|
|
330
|
+
|
|
331
|
+
# Pool tuning
|
|
332
|
+
GENERIC_KEYS.each { |key| tuning[key] = data[key] }
|
|
333
|
+
|
|
334
|
+
# Logging
|
|
335
|
+
tuning[:log] = data[:log]
|
|
336
|
+
tuning[:query_log] = data[:query_log]
|
|
337
|
+
tuning[:query_log_path] = @query_file_logger&.path
|
|
338
|
+
tuning[:log_warn_duration] = data[:log_warn_duration]
|
|
339
|
+
tuning[:sql_log_level] = data[:sql_log_level]
|
|
340
|
+
tuning[:log_connection_info] = data[:log_connection_info]
|
|
341
|
+
|
|
342
|
+
# Connection health
|
|
343
|
+
tuning[:connection_validation] = data[:connection_validation]
|
|
344
|
+
tuning[:connection_validation_timeout] = data[:connection_validation_timeout]
|
|
345
|
+
tuning[:connection_expiration] = data[:connection_expiration]
|
|
346
|
+
tuning[:connection_expiration_timeout] = data[:connection_expiration_timeout]
|
|
347
|
+
|
|
348
|
+
# Adapter-specific (only keys relevant to current adapter)
|
|
349
|
+
defaults = ADAPTER_DEFAULTS.fetch(adapter, {})
|
|
350
|
+
ADAPTER_KEYS.fetch(adapter, []).each do |key|
|
|
351
|
+
tuning[key] = data.key?(key) && !data[key].nil? ? data[key] : defaults[key]
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
tuning
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def database_stats
|
|
358
|
+
case adapter
|
|
359
|
+
when :sqlite then sqlite_stats
|
|
360
|
+
when :postgres then postgres_stats
|
|
361
|
+
when :mysql2 then mysql_stats
|
|
362
|
+
else {}
|
|
363
|
+
end
|
|
364
|
+
rescue StandardError => e
|
|
365
|
+
{ error: e.message }
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def sqlite_stats
|
|
369
|
+
db = @sequel
|
|
370
|
+
stats = {}
|
|
371
|
+
%w[page_size page_count freelist_count journal_mode wal_autocheckpoint
|
|
372
|
+
cache_size busy_timeout].each do |pragma|
|
|
373
|
+
val = begin
|
|
374
|
+
db.fetch("PRAGMA #{pragma}").single_value
|
|
375
|
+
rescue StandardError
|
|
376
|
+
nil
|
|
377
|
+
end
|
|
378
|
+
stats[pragma.to_sym] = val unless val.nil?
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
db_path = Legion::Settings[:data][:creds][:database] || 'legionio.db'
|
|
382
|
+
stats[:file_size] = File.size(db_path) if File.exist?(db_path)
|
|
383
|
+
stats[:database_size_bytes] = (stats[:page_size].to_i * stats[:page_count].to_i) if stats[:page_size] && stats[:page_count]
|
|
384
|
+
stats
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def postgres_stats
|
|
388
|
+
db = @sequel
|
|
389
|
+
stats = {}
|
|
390
|
+
|
|
391
|
+
row = db.fetch('SELECT current_database() AS db, pg_database_size(current_database()) AS size_bytes').first
|
|
392
|
+
stats[:database_name] = row[:db]
|
|
393
|
+
stats[:database_size_bytes] = row[:size_bytes]
|
|
394
|
+
|
|
395
|
+
activity = db.fetch(<<~SQL).first
|
|
396
|
+
SELECT
|
|
397
|
+
count(*) FILTER (WHERE state = 'active') AS active,
|
|
398
|
+
count(*) FILTER (WHERE state = 'idle') AS idle,
|
|
399
|
+
count(*) FILTER (WHERE state = 'idle in transaction') AS idle_in_transaction,
|
|
400
|
+
count(*) AS total
|
|
401
|
+
FROM pg_stat_activity
|
|
402
|
+
WHERE datname = current_database()
|
|
403
|
+
SQL
|
|
404
|
+
stats[:server_connections] = activity
|
|
405
|
+
|
|
406
|
+
settings = db.fetch(<<~SQL).first
|
|
407
|
+
SELECT
|
|
408
|
+
current_setting('max_connections')::int AS max_connections,
|
|
409
|
+
current_setting('shared_buffers') AS shared_buffers,
|
|
410
|
+
current_setting('work_mem') AS work_mem,
|
|
411
|
+
current_setting('server_version') AS server_version
|
|
412
|
+
SQL
|
|
413
|
+
stats[:server] = settings
|
|
414
|
+
|
|
415
|
+
stats
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def mysql_stats
|
|
419
|
+
db = @sequel
|
|
420
|
+
stats = {}
|
|
421
|
+
|
|
422
|
+
size_row = db.fetch(<<~SQL).first
|
|
423
|
+
SELECT SUM(data_length + index_length) AS size_bytes
|
|
424
|
+
FROM information_schema.tables
|
|
425
|
+
WHERE table_schema = DATABASE()
|
|
426
|
+
SQL
|
|
427
|
+
stats[:database_name] = db.fetch('SELECT DATABASE() AS db').single_value
|
|
428
|
+
stats[:database_size_bytes] = size_row[:size_bytes]&.to_i
|
|
429
|
+
|
|
430
|
+
threads = {}
|
|
431
|
+
db.fetch("SHOW STATUS WHERE Variable_name IN ('Threads_connected','Threads_running','Max_used_connections')").each do |row|
|
|
432
|
+
threads[row[:Variable_name].downcase.to_sym] = row[:Value].to_i
|
|
433
|
+
end
|
|
434
|
+
stats[:server_connections] = threads
|
|
435
|
+
|
|
436
|
+
max_conn = db.fetch("SHOW VARIABLES LIKE 'max_connections'").first
|
|
437
|
+
version = db.fetch('SELECT VERSION() AS v').single_value
|
|
438
|
+
stats[:server] = {
|
|
439
|
+
max_connections: max_conn ? max_conn[:Value].to_i : nil,
|
|
440
|
+
server_version: version
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
stats
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def configure_extensions
|
|
447
|
+
return if adapter == :sqlite
|
|
448
|
+
|
|
449
|
+
data = Legion::Settings[:data]
|
|
450
|
+
|
|
451
|
+
if data[:connection_validation] != false
|
|
452
|
+
@sequel.extension(:connection_validator)
|
|
453
|
+
@sequel.pool.connection_validation_timeout = data[:connection_validation_timeout] || 600
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
if data[:connection_expiration] != false
|
|
457
|
+
@sequel.extension(:connection_expiration)
|
|
458
|
+
@sequel.pool.connection_expiration_timeout = data[:connection_expiration_timeout] || 14_400
|
|
459
|
+
end
|
|
460
|
+
rescue StandardError => e
|
|
461
|
+
Legion::Logging.warn "Failed to load connection extensions: #{e.message}" if defined?(Legion::Logging)
|
|
185
462
|
end
|
|
186
463
|
|
|
187
464
|
def build_data_logger
|
data/lib/legion/data/local.rb
CHANGED
|
@@ -14,7 +14,23 @@ module Legion
|
|
|
14
14
|
|
|
15
15
|
db_file = database || local_settings[:database] || 'legionio_local.db'
|
|
16
16
|
@db_path = db_file
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
sqlite_defaults = Legion::Data::Connection::ADAPTER_DEFAULTS.fetch(:sqlite, {})
|
|
19
|
+
data = defined?(Legion::Settings) ? Legion::Settings[:data] : {}
|
|
20
|
+
opts = { adapter: :sqlite, database: db_file }
|
|
21
|
+
Legion::Data::Connection::ADAPTER_KEYS.fetch(:sqlite, []).each do |key|
|
|
22
|
+
val = data.key?(key) && !data[key].nil? ? data[key] : sqlite_defaults[key]
|
|
23
|
+
opts[key] = val unless val.nil?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if local_settings[:query_log]
|
|
27
|
+
log_path = File.join(Legion::Data::Connection::QUERY_LOG_DIR, 'data-local-query.log')
|
|
28
|
+
@query_file_logger = Legion::Data::Connection::QueryFileLogger.new(log_path)
|
|
29
|
+
opts[:logger] = @query_file_logger
|
|
30
|
+
opts[:sql_log_level] = :debug
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@connection = ::Sequel.connect(opts)
|
|
18
34
|
@connected = true
|
|
19
35
|
run_migrations
|
|
20
36
|
Legion::Logging.info "Legion::Data::Local connected to #{db_file}" if defined?(Legion::Logging)
|
|
@@ -22,6 +38,8 @@ module Legion
|
|
|
22
38
|
|
|
23
39
|
def shutdown
|
|
24
40
|
@connection&.disconnect
|
|
41
|
+
@query_file_logger&.close
|
|
42
|
+
@query_file_logger = nil
|
|
25
43
|
@connection = nil
|
|
26
44
|
@connected = false
|
|
27
45
|
end
|
|
@@ -46,6 +64,37 @@ module Legion
|
|
|
46
64
|
::Sequel::Model(connection[table_name])
|
|
47
65
|
end
|
|
48
66
|
|
|
67
|
+
def stats
|
|
68
|
+
return { connected: false } unless connected?
|
|
69
|
+
|
|
70
|
+
stats = {
|
|
71
|
+
connected: true,
|
|
72
|
+
adapter: :sqlite,
|
|
73
|
+
path: @db_path,
|
|
74
|
+
query_log: local_settings[:query_log] || false,
|
|
75
|
+
query_log_path: @query_file_logger&.path,
|
|
76
|
+
registered_migrations: registered_migrations.keys
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
stats[:file_size] = File.size(@db_path) if @db_path && File.exist?(@db_path)
|
|
80
|
+
|
|
81
|
+
%w[page_size page_count freelist_count journal_mode
|
|
82
|
+
wal_autocheckpoint cache_size busy_timeout].each do |pragma|
|
|
83
|
+
val = begin
|
|
84
|
+
@connection.fetch("PRAGMA #{pragma}").single_value
|
|
85
|
+
rescue StandardError
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
stats[pragma.to_sym] = val unless val.nil?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
stats[:database_size_bytes] = stats[:page_size].to_i * stats[:page_count].to_i if stats[:page_size] && stats[:page_count]
|
|
92
|
+
|
|
93
|
+
stats
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
{ connected: connected?, error: e.message }
|
|
96
|
+
end
|
|
97
|
+
|
|
49
98
|
def reset!
|
|
50
99
|
@connection = nil
|
|
51
100
|
@connected = false
|
data/lib/legion/data/settings.rb
CHANGED
|
@@ -25,20 +25,55 @@ module Legion
|
|
|
25
25
|
|
|
26
26
|
def self.default
|
|
27
27
|
{
|
|
28
|
-
adapter:
|
|
29
|
-
connected:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
adapter: 'sqlite',
|
|
29
|
+
connected: false,
|
|
30
|
+
|
|
31
|
+
# Connection pool
|
|
32
|
+
max_connections: 25,
|
|
33
|
+
pool_timeout: 5,
|
|
34
|
+
preconnect: 'concurrently',
|
|
35
|
+
single_threaded: false,
|
|
36
|
+
test: true,
|
|
37
|
+
name: nil,
|
|
38
|
+
|
|
39
|
+
# Logging
|
|
40
|
+
log: false,
|
|
41
|
+
query_log: false,
|
|
42
|
+
log_connection_info: false,
|
|
43
|
+
log_warn_duration: 1,
|
|
44
|
+
sql_log_level: 'debug',
|
|
45
|
+
|
|
46
|
+
# Connection health (network adapters only, ignored for sqlite)
|
|
47
|
+
connection_validation: true,
|
|
48
|
+
connection_validation_timeout: 600,
|
|
49
|
+
connection_expiration: true,
|
|
50
|
+
connection_expiration_timeout: 14_400,
|
|
51
|
+
|
|
52
|
+
# Adapter-specific (nil = use adapter built-in default)
|
|
53
|
+
connect_timeout: nil,
|
|
54
|
+
read_timeout: nil,
|
|
55
|
+
write_timeout: nil,
|
|
56
|
+
encoding: nil,
|
|
57
|
+
sql_mode: nil,
|
|
58
|
+
sslmode: nil,
|
|
59
|
+
sslrootcert: nil,
|
|
60
|
+
search_path: nil,
|
|
61
|
+
timeout: nil,
|
|
62
|
+
readonly: nil,
|
|
63
|
+
disable_dqs: nil,
|
|
64
|
+
|
|
65
|
+
# Grouped settings
|
|
66
|
+
creds: creds,
|
|
67
|
+
cache: cache,
|
|
68
|
+
migrations: migrations,
|
|
69
|
+
models: models,
|
|
70
|
+
local: local,
|
|
71
|
+
dev_mode: false,
|
|
72
|
+
dev_fallback: true,
|
|
73
|
+
connect_on_start: true,
|
|
74
|
+
read_replica_url: nil,
|
|
75
|
+
replicas: [],
|
|
76
|
+
archival: archival
|
|
42
77
|
}
|
|
43
78
|
end
|
|
44
79
|
|
|
@@ -46,6 +81,7 @@ module Legion
|
|
|
46
81
|
{
|
|
47
82
|
enabled: true,
|
|
48
83
|
database: 'legionio_local.db',
|
|
84
|
+
query_log: false,
|
|
49
85
|
migrations: { auto_migrate: true }
|
|
50
86
|
}
|
|
51
87
|
end
|
|
@@ -66,17 +102,6 @@ module Legion
|
|
|
66
102
|
}
|
|
67
103
|
end
|
|
68
104
|
|
|
69
|
-
def self.connection
|
|
70
|
-
{
|
|
71
|
-
log: false,
|
|
72
|
-
log_connection_info: false,
|
|
73
|
-
log_warn_duration: 1,
|
|
74
|
-
sql_log_level: 'debug',
|
|
75
|
-
max_connections: 10,
|
|
76
|
-
preconnect: false
|
|
77
|
-
}
|
|
78
|
-
end
|
|
79
|
-
|
|
80
105
|
def self.creds(adapter = nil)
|
|
81
106
|
adapter = (adapter || :sqlite).to_sym
|
|
82
107
|
CREDS.fetch(adapter, CREDS[:sqlite]).dup
|
|
@@ -92,9 +117,10 @@ module Legion
|
|
|
92
117
|
|
|
93
118
|
def self.cache
|
|
94
119
|
{
|
|
95
|
-
connected:
|
|
96
|
-
auto_enable:
|
|
97
|
-
|
|
120
|
+
connected: false,
|
|
121
|
+
auto_enable: Legion::Settings[:cache][:connected],
|
|
122
|
+
static_cache: false,
|
|
123
|
+
ttl: 60
|
|
98
124
|
}
|
|
99
125
|
end
|
|
100
126
|
end
|
data/lib/legion/data/version.rb
CHANGED
data/lib/legion/data.rb
CHANGED
|
@@ -47,22 +47,48 @@ module Legion
|
|
|
47
47
|
Legion::Data::Local
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def stats
|
|
51
|
+
{
|
|
52
|
+
shared: Legion::Data::Connection.stats,
|
|
53
|
+
local: Legion::Data::Local.stats
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
50
57
|
def setup_cache
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
cache_settings = Legion::Settings[:data][:cache]
|
|
59
|
+
setup_static_cache if cache_settings[:static_cache]
|
|
60
|
+
setup_external_cache if cache_settings[:auto_enable] && defined?(::Legion::Cache)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def setup_static_cache
|
|
64
|
+
[Model::Extension, Model::Runner, Model::Function].each do |model|
|
|
65
|
+
model.plugin :static_cache
|
|
66
|
+
Legion::Logging.debug("StaticCache enabled for #{model}") if defined?(Legion::Logging)
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
Legion::Logging.warn("StaticCache failed for #{model}: #{e.message}") if defined?(Legion::Logging)
|
|
69
|
+
end
|
|
70
|
+
Legion::Logging.info 'Legion::Data static cache loaded' if defined?(Legion::Logging)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def reload_static_cache
|
|
74
|
+
[Model::Extension, Model::Runner, Model::Function].each do |model|
|
|
75
|
+
model.load_cache if model.respond_to?(:load_cache)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def setup_external_cache
|
|
80
|
+
ttl = Legion::Settings[:data][:cache][:ttl] || 60
|
|
81
|
+
{
|
|
82
|
+
Model::Relationship => 10,
|
|
83
|
+
Model::Node => 10,
|
|
84
|
+
Model::Setting => ttl
|
|
85
|
+
}.each do |model, model_ttl|
|
|
86
|
+
model.plugin :caching, ::Legion::Cache, ttl: model_ttl
|
|
87
|
+
Legion::Logging.debug("Caching enabled for #{model} (ttl: #{model_ttl})") if defined?(Legion::Logging)
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
Legion::Logging.warn("Caching failed for #{model}: #{e.message}") if defined?(Legion::Logging)
|
|
90
|
+
end
|
|
91
|
+
Legion::Logging.info 'Legion::Data external cache connected' if defined?(Legion::Logging)
|
|
66
92
|
end
|
|
67
93
|
|
|
68
94
|
def shutdown
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-data
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -75,6 +75,8 @@ extra_rdoc_files:
|
|
|
75
75
|
- LICENSE
|
|
76
76
|
- README.md
|
|
77
77
|
files:
|
|
78
|
+
- ".github/CODEOWNERS"
|
|
79
|
+
- ".github/dependabot.yml"
|
|
78
80
|
- ".github/workflows/ci.yml"
|
|
79
81
|
- ".gitignore"
|
|
80
82
|
- ".rubocop.yml"
|