legion-data 1.6.18 → 1.6.20
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/CHANGELOG.md +21 -0
- data/Gemfile +1 -0
- data/legion-data.gemspec +2 -2
- data/lib/legion/data/archival/policy.rb +7 -1
- data/lib/legion/data/archival.rb +27 -4
- data/lib/legion/data/archiver.rb +103 -51
- data/lib/legion/data/audit_record.rb +8 -5
- data/lib/legion/data/connection.rb +88 -17
- data/lib/legion/data/encryption/key_provider.rb +9 -2
- data/lib/legion/data/encryption/sequel_plugin.rb +126 -12
- data/lib/legion/data/event_store.rb +29 -10
- data/lib/legion/data/extract/handlers/base.rb +7 -1
- data/lib/legion/data/extract/handlers/csv.rb +1 -0
- data/lib/legion/data/extract/handlers/docx.rb +3 -1
- data/lib/legion/data/extract/handlers/html.rb +3 -1
- data/lib/legion/data/extract/handlers/json.rb +1 -0
- data/lib/legion/data/extract/handlers/jsonl.rb +1 -0
- data/lib/legion/data/extract/handlers/markdown.rb +1 -0
- data/lib/legion/data/extract/handlers/pdf.rb +3 -1
- data/lib/legion/data/extract/handlers/pptx.rb +3 -1
- data/lib/legion/data/extract/handlers/text.rb +1 -0
- data/lib/legion/data/extract/handlers/vtt.rb +1 -0
- data/lib/legion/data/extract/handlers/xlsx.rb +3 -1
- data/lib/legion/data/extract.rb +7 -0
- data/lib/legion/data/helper.rb +16 -6
- data/lib/legion/data/local.rb +62 -5
- data/lib/legion/data/migration.rb +6 -1
- data/lib/legion/data/migrations/044_expand_memory_traces.rb +4 -1
- data/lib/legion/data/model.rb +8 -4
- data/lib/legion/data/models/audit_log.rb +5 -1
- data/lib/legion/data/models/audit_record.rb +5 -1
- data/lib/legion/data/models/function.rb +5 -1
- data/lib/legion/data/models/node.rb +6 -2
- data/lib/legion/data/partition_manager.rb +15 -19
- data/lib/legion/data/retention.rb +31 -2
- data/lib/legion/data/rls.rb +8 -2
- data/lib/legion/data/settings.rb +5 -1
- data/lib/legion/data/spool.rb +69 -6
- data/lib/legion/data/storage_tiers.rb +16 -3
- data/lib/legion/data/vector.rb +9 -5
- data/lib/legion/data/version.rb +1 -1
- data/lib/legion/data.rb +39 -12
- metadata +5 -5
|
@@ -25,9 +25,11 @@ module Legion
|
|
|
25
25
|
end
|
|
26
26
|
text = sheets.join("\n\n")
|
|
27
27
|
{ text: text, metadata: { sheets: workbook.worksheets.size } }
|
|
28
|
-
rescue LoadError
|
|
28
|
+
rescue LoadError => e
|
|
29
|
+
handle_exception(e, level: :warn, handled: true, operation: :extract_xlsx, gem: gem_name)
|
|
29
30
|
{ text: nil, error: :gem_not_installed, gem: gem_name }
|
|
30
31
|
rescue StandardError => e
|
|
32
|
+
handle_exception(e, level: :warn, handled: true, operation: :extract_xlsx)
|
|
31
33
|
{ text: nil, error: e.message }
|
|
32
34
|
end
|
|
33
35
|
end
|
data/lib/legion/data/extract.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
3
4
|
require_relative 'extract/type_detector'
|
|
4
5
|
require_relative 'extract/handlers/base'
|
|
5
6
|
|
|
@@ -7,6 +8,8 @@ module Legion
|
|
|
7
8
|
module Data
|
|
8
9
|
module Extract
|
|
9
10
|
class << self
|
|
11
|
+
include Legion::Logging::Helper
|
|
12
|
+
|
|
10
13
|
def extract(source, type: :auto)
|
|
11
14
|
detected_type = type == :auto ? TypeDetector.detect(source) : type&.to_sym
|
|
12
15
|
return { success: false, text: nil, error: :unknown_type } unless detected_type
|
|
@@ -19,13 +22,17 @@ module Legion
|
|
|
19
22
|
gem: handler.gem_name, type: detected_type }
|
|
20
23
|
end
|
|
21
24
|
|
|
25
|
+
log.info "Extract starting type=#{detected_type} handler=#{handler.name}"
|
|
22
26
|
result = handler.extract(source)
|
|
23
27
|
if result[:text]
|
|
28
|
+
log.info "Extract succeeded type=#{detected_type}"
|
|
24
29
|
{ success: true, text: result[:text], metadata: result[:metadata], type: detected_type }
|
|
25
30
|
else
|
|
31
|
+
log.warn "Extract failed type=#{detected_type} error=#{result[:error]}"
|
|
26
32
|
{ success: false, text: nil, error: result[:error], type: detected_type }
|
|
27
33
|
end
|
|
28
34
|
rescue StandardError => e
|
|
35
|
+
handle_exception(e, level: :error, handled: true, operation: :extract, type: detected_type)
|
|
29
36
|
{ success: false, text: nil, error: e.message, type: detected_type }
|
|
30
37
|
end
|
|
31
38
|
|
data/lib/legion/data/helper.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Helper
|
|
8
|
+
include Legion::Logging::Helper
|
|
9
|
+
|
|
6
10
|
def data_path
|
|
7
11
|
@data_path ||= "#{full_path}/data"
|
|
8
12
|
end
|
|
@@ -39,7 +43,8 @@ module Legion
|
|
|
39
43
|
|
|
40
44
|
def data_adapter
|
|
41
45
|
Legion::Data::Connection.adapter
|
|
42
|
-
rescue StandardError
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
handle_exception(e, level: :warn, handled: true, operation: :data_adapter)
|
|
43
48
|
:unknown
|
|
44
49
|
end
|
|
45
50
|
|
|
@@ -47,7 +52,8 @@ module Legion
|
|
|
47
52
|
return {} unless data_connected?
|
|
48
53
|
|
|
49
54
|
Legion::Data::Connection.pool_stats
|
|
50
|
-
rescue StandardError
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
handle_exception(e, level: :warn, handled: true, operation: :data_pool_stats)
|
|
51
57
|
{}
|
|
52
58
|
end
|
|
53
59
|
|
|
@@ -55,7 +61,8 @@ module Legion
|
|
|
55
61
|
return {} unless data_connected?
|
|
56
62
|
|
|
57
63
|
Legion::Data.stats
|
|
58
|
-
rescue StandardError
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
handle_exception(e, level: :warn, handled: true, operation: :data_stats)
|
|
59
66
|
{}
|
|
60
67
|
end
|
|
61
68
|
|
|
@@ -63,7 +70,8 @@ module Legion
|
|
|
63
70
|
return {} unless local_data_connected?
|
|
64
71
|
|
|
65
72
|
Legion::Data::Local.stats
|
|
66
|
-
rescue StandardError
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
handle_exception(e, level: :warn, handled: true, operation: :local_data_stats)
|
|
67
75
|
{}
|
|
68
76
|
end
|
|
69
77
|
|
|
@@ -71,13 +79,15 @@ module Legion
|
|
|
71
79
|
|
|
72
80
|
def data_can_read?(table_name)
|
|
73
81
|
Legion::Data.can_read?(table_name)
|
|
74
|
-
rescue StandardError
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
handle_exception(e, level: :warn, handled: true, operation: :data_can_read, table: table_name)
|
|
75
84
|
false
|
|
76
85
|
end
|
|
77
86
|
|
|
78
87
|
def data_can_write?(table_name)
|
|
79
88
|
Legion::Data.can_write?(table_name)
|
|
80
|
-
rescue StandardError
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
handle_exception(e, level: :warn, handled: true, operation: :data_can_write, table: table_name)
|
|
81
91
|
false
|
|
82
92
|
end
|
|
83
93
|
end
|
data/lib/legion/data/local.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'legion/logging/helper'
|
|
5
|
+
|
|
3
6
|
require 'sequel'
|
|
4
7
|
require 'sequel/extensions/migration'
|
|
5
8
|
|
|
@@ -7,12 +10,19 @@ module Legion
|
|
|
7
10
|
module Data
|
|
8
11
|
module Local
|
|
9
12
|
class << self
|
|
13
|
+
include Legion::Logging::Helper
|
|
14
|
+
|
|
10
15
|
attr_reader :connection, :db_path
|
|
11
16
|
|
|
12
17
|
def setup(database: nil, **)
|
|
13
18
|
return if @connected
|
|
14
19
|
|
|
15
20
|
db_file = database || local_settings[:database] || 'legionio_local.db'
|
|
21
|
+
unless File.absolute_path?(db_file)
|
|
22
|
+
base_dir = File.expand_path('~/.legionio')
|
|
23
|
+
FileUtils.mkdir_p(base_dir)
|
|
24
|
+
db_file = File.join(base_dir, db_file)
|
|
25
|
+
end
|
|
16
26
|
@db_path = db_file
|
|
17
27
|
|
|
18
28
|
sqlite_defaults = Legion::Data::Connection::ADAPTER_DEFAULTS.fetch(:sqlite, {})
|
|
@@ -26,14 +36,24 @@ module Legion
|
|
|
26
36
|
if local_settings[:query_log]
|
|
27
37
|
log_path = File.join(Legion::Data::Connection::QUERY_LOG_DIR, 'data-local-query.log')
|
|
28
38
|
@query_file_logger = Legion::Data::Connection::QueryFileLogger.new(log_path)
|
|
29
|
-
opts[:logger]
|
|
30
|
-
opts[:sql_log_level]
|
|
39
|
+
opts[:logger] = @query_file_logger
|
|
40
|
+
opts[:sql_log_level] = :debug
|
|
41
|
+
elsif data[:log] && defined?(Legion::Logging)
|
|
42
|
+
opts[:logger] = build_local_logger
|
|
43
|
+
opts[:sql_log_level] = resolved_sql_log_level
|
|
44
|
+
opts[:log_warn_duration] = resolved_log_warn_duration
|
|
31
45
|
end
|
|
32
46
|
|
|
33
47
|
@connection = ::Sequel.connect(opts)
|
|
48
|
+
@connection.run('PRAGMA journal_mode=WAL')
|
|
49
|
+
@connection.run('PRAGMA busy_timeout=30000')
|
|
50
|
+
@connection.run('PRAGMA synchronous=NORMAL')
|
|
34
51
|
@connected = true
|
|
35
52
|
run_migrations
|
|
36
|
-
|
|
53
|
+
log.info "Legion::Data::Local connected to #{db_file} (WAL mode, 30s busy_timeout)"
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
handle_exception(e, level: :error, handled: false, operation: :local_setup, database: db_file)
|
|
56
|
+
raise
|
|
37
57
|
end
|
|
38
58
|
|
|
39
59
|
def shutdown
|
|
@@ -82,7 +102,8 @@ module Legion
|
|
|
82
102
|
wal_autocheckpoint cache_size busy_timeout].each do |pragma|
|
|
83
103
|
val = begin
|
|
84
104
|
@connection.fetch("PRAGMA #{pragma}").single_value
|
|
85
|
-
rescue StandardError
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
handle_exception(e, level: :warn, handled: true, operation: :local_stats_pragma, pragma: pragma)
|
|
86
107
|
nil
|
|
87
108
|
end
|
|
88
109
|
stats[pragma.to_sym] = val unless val.nil?
|
|
@@ -92,6 +113,7 @@ module Legion
|
|
|
92
113
|
|
|
93
114
|
stats
|
|
94
115
|
rescue StandardError => e
|
|
116
|
+
handle_exception(e, level: :warn, handled: true, operation: :local_stats, database: @db_path)
|
|
95
117
|
{ connected: connected?, error: e.message }
|
|
96
118
|
end
|
|
97
119
|
|
|
@@ -119,7 +141,7 @@ module Legion
|
|
|
119
141
|
table = :"schema_migrations_#{name}"
|
|
120
142
|
::Sequel::TimestampMigrator.new(@connection, path, table: table).run
|
|
121
143
|
rescue StandardError => e
|
|
122
|
-
|
|
144
|
+
handle_exception(e, level: :warn, handled: true, operation: :local_migration, name: name, path: path)
|
|
123
145
|
end
|
|
124
146
|
|
|
125
147
|
def local_settings
|
|
@@ -127,6 +149,41 @@ module Legion
|
|
|
127
149
|
|
|
128
150
|
Legion::Settings[:data]&.dig(:local) || {}
|
|
129
151
|
end
|
|
152
|
+
|
|
153
|
+
def build_local_logger
|
|
154
|
+
tagged = if defined?(Legion::Logging::TaggedLogger) && respond_to?(:tagged_logger_settings, true)
|
|
155
|
+
Legion::Logging::TaggedLogger.new(
|
|
156
|
+
segments: %w[data local],
|
|
157
|
+
**send(:tagged_logger_settings)
|
|
158
|
+
)
|
|
159
|
+
else
|
|
160
|
+
Legion::Data::Connection::SegmentedTaggedLogger.new(segments: %w[data local])
|
|
161
|
+
end
|
|
162
|
+
Legion::Data::Connection::SlowQueryLogger.new(tagged)
|
|
163
|
+
rescue StandardError => e
|
|
164
|
+
if respond_to?(:handle_exception, true)
|
|
165
|
+
handle_exception(e, level: :warn, handled: true, operation: :build_local_logger)
|
|
166
|
+
else
|
|
167
|
+
log.warn("build_local_logger failed: #{e.class}: #{e.message}")
|
|
168
|
+
end
|
|
169
|
+
Legion::Data::Connection::SlowQueryLogger.new(
|
|
170
|
+
Legion::Data::Connection::SegmentedTaggedLogger.new(segments: %w[data local], logger: log)
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def resolved_sql_log_level
|
|
175
|
+
(local_settings[:sql_log_level] || Legion::Settings[:data][:sql_log_level] || 'debug').to_sym
|
|
176
|
+
rescue StandardError => e
|
|
177
|
+
handle_exception(e, level: :warn, handled: true, operation: :resolved_sql_log_level)
|
|
178
|
+
:debug
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def resolved_log_warn_duration
|
|
182
|
+
local_settings[:log_warn_duration] || Legion::Settings[:data][:log_warn_duration]
|
|
183
|
+
rescue StandardError => e
|
|
184
|
+
handle_exception(e, level: :warn, handled: true, operation: :resolved_log_warn_duration)
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
130
187
|
end
|
|
131
188
|
end
|
|
132
189
|
end
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
require 'sequel/extensions/migration'
|
|
4
6
|
|
|
5
7
|
module Legion
|
|
6
8
|
module Data
|
|
7
9
|
module Migration
|
|
8
10
|
class << self
|
|
11
|
+
include Legion::Logging::Helper
|
|
12
|
+
|
|
9
13
|
def migrate(connection = Legion::Data.connection, path = "#{__dir__}/migrations", **)
|
|
10
14
|
Legion::Settings[:data][:migrations][:version] = Sequel::Migrator.run(connection, path, **)
|
|
11
|
-
|
|
15
|
+
log.info("Legion::Data::Migration ran successfully to version #{Legion::Settings[:data][:migrations][:version]}")
|
|
12
16
|
Legion::Settings[:data][:migrations][:ran] = true
|
|
13
17
|
rescue Sequel::DatabaseError => e
|
|
18
|
+
handle_exception(e, level: :error, handled: false, operation: :migrate, path: path)
|
|
14
19
|
if e.message.include?('InsufficientPrivilege') || e.message.include?('permission denied')
|
|
15
20
|
raise Sequel::DatabaseError,
|
|
16
21
|
"#{e.message}\n Hint: the database user lacks CREATE on schema public " \
|
|
@@ -29,7 +29,10 @@ Sequel.migration do
|
|
|
29
29
|
|
|
30
30
|
indexes = begin
|
|
31
31
|
db.indexes(:memory_traces).keys
|
|
32
|
-
rescue StandardError
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
if defined?(Legion::Data) && Legion::Data.respond_to?(:handle_exception)
|
|
34
|
+
Legion::Data.handle_exception(e, level: :warn, handled: true, operation: :migration_044_indexes)
|
|
35
|
+
end
|
|
33
36
|
[]
|
|
34
37
|
end
|
|
35
38
|
|
data/lib/legion/data/model.rb
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Models
|
|
6
8
|
class << self
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
7
11
|
attr_reader :loaded_models
|
|
8
12
|
|
|
9
13
|
def models
|
|
@@ -13,7 +17,7 @@ module Legion
|
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def load
|
|
16
|
-
|
|
20
|
+
log.info 'Loading Legion::Data::Models'
|
|
17
21
|
@loaded_models ||= []
|
|
18
22
|
require_sequel_models(models)
|
|
19
23
|
Legion::Settings[:data][:models][:loaded] = true
|
|
@@ -25,13 +29,13 @@ module Legion
|
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def load_sequel_model(model)
|
|
28
|
-
|
|
32
|
+
log.debug("Trying to load #{model}.rb")
|
|
29
33
|
require_relative "models/#{model}"
|
|
30
34
|
@loaded_models << model
|
|
31
|
-
|
|
35
|
+
log.debug("Successfully loaded #{model}")
|
|
32
36
|
model
|
|
33
37
|
rescue LoadError => e
|
|
34
|
-
|
|
38
|
+
handle_exception(e, level: :fatal, operation: :load_sequel_model, model: model)
|
|
35
39
|
raise e unless Legion::Settings[:data][:models][:continue_on_fail]
|
|
36
40
|
end
|
|
37
41
|
end
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Model
|
|
6
8
|
class AuditLog < Sequel::Model(:audit_log)
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
7
11
|
VALID_EVENT_TYPES = %w[runner_execution lifecycle_transition].freeze
|
|
8
12
|
VALID_STATUSES = %w[success failure denied].freeze
|
|
9
13
|
|
|
@@ -18,7 +22,7 @@ module Legion
|
|
|
18
22
|
|
|
19
23
|
Legion::JSON.load(detail)
|
|
20
24
|
rescue StandardError => e
|
|
21
|
-
|
|
25
|
+
handle_exception(e, level: :warn, handled: true, operation: :parsed_detail, id: self[:id])
|
|
22
26
|
nil
|
|
23
27
|
end
|
|
24
28
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Model
|
|
6
8
|
class AuditRecord < Sequel::Model(:audit_records)
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
7
11
|
# Enforce append-only semantics at the application layer.
|
|
8
12
|
# PostgreSQL enforces this at the DB layer via rules (migration 058);
|
|
9
13
|
# the application guard covers SQLite and MySQL.
|
|
@@ -21,7 +25,7 @@ module Legion
|
|
|
21
25
|
|
|
22
26
|
Legion::JSON.load(metadata)
|
|
23
27
|
rescue StandardError => e
|
|
24
|
-
|
|
28
|
+
handle_exception(e, level: :warn, handled: true, operation: :parsed_metadata, id: self[:id])
|
|
25
29
|
{}
|
|
26
30
|
end
|
|
27
31
|
end
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Model
|
|
6
8
|
class Function < Sequel::Model
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
7
11
|
many_to_one :runner
|
|
8
12
|
one_to_many :trigger_relationships, class: 'Legion::Data::Model::Relationship', key: :trigger_id
|
|
9
13
|
one_to_many :action_relationships, class: 'Legion::Data::Model::Relationship', key: :action_id
|
|
@@ -13,7 +17,7 @@ module Legion
|
|
|
13
17
|
|
|
14
18
|
::JSON.parse(embedding)
|
|
15
19
|
rescue ::JSON::ParserError => e
|
|
16
|
-
|
|
20
|
+
handle_exception(e, level: :debug, handled: true, operation: :embedding_vector, id: self[:id])
|
|
17
21
|
nil
|
|
18
22
|
end
|
|
19
23
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Model
|
|
6
8
|
class Node < Sequel::Model
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
|
|
7
11
|
# one_to_many :task_log
|
|
8
12
|
|
|
9
13
|
def parsed_metrics
|
|
@@ -11,7 +15,7 @@ module Legion
|
|
|
11
15
|
|
|
12
16
|
Legion::JSON.load(metrics)
|
|
13
17
|
rescue StandardError => e
|
|
14
|
-
|
|
18
|
+
handle_exception(e, level: :debug, handled: true, operation: :parsed_metrics, id: self[:id])
|
|
15
19
|
nil
|
|
16
20
|
end
|
|
17
21
|
|
|
@@ -20,7 +24,7 @@ module Legion
|
|
|
20
24
|
|
|
21
25
|
Legion::JSON.load(hosted_worker_ids)
|
|
22
26
|
rescue StandardError => e
|
|
23
|
-
|
|
27
|
+
handle_exception(e, level: :debug, handled: true, operation: :parsed_hosted_worker_ids, id: self[:id])
|
|
24
28
|
[]
|
|
25
29
|
end
|
|
26
30
|
end
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module PartitionManager
|
|
6
8
|
NOT_POSTGRES = { skipped: true, reason: 'not_postgres' }.freeze
|
|
7
9
|
|
|
8
10
|
class << self
|
|
11
|
+
include Legion::Logging::Helper
|
|
12
|
+
|
|
9
13
|
def ensure_partitions(table:, months_ahead: 3)
|
|
10
14
|
return NOT_POSTGRES unless postgres?
|
|
11
15
|
|
|
@@ -28,16 +32,17 @@ module Legion
|
|
|
28
32
|
after_count = partition_names_for(table).size
|
|
29
33
|
|
|
30
34
|
if after_count > before_count
|
|
31
|
-
|
|
35
|
+
log.info("Created partition #{partition}")
|
|
32
36
|
created << partition
|
|
33
37
|
else
|
|
34
38
|
existing << partition
|
|
35
39
|
end
|
|
36
40
|
end
|
|
37
41
|
|
|
42
|
+
log.info "PartitionManager ensure_partitions table=#{table} created=#{created.size} existing=#{existing.size}"
|
|
38
43
|
{ created: created, existing: existing }
|
|
39
44
|
rescue StandardError => e
|
|
40
|
-
|
|
45
|
+
handle_exception(e, level: :warn, handled: true, operation: :ensure_partitions, table: table, months_ahead: months_ahead)
|
|
41
46
|
{ created: [], existing: [], error: e.message }
|
|
42
47
|
end
|
|
43
48
|
|
|
@@ -54,16 +59,17 @@ module Legion
|
|
|
54
59
|
|
|
55
60
|
if part_date < cutoff
|
|
56
61
|
Legion::Data.connection.run("DROP TABLE #{part}")
|
|
57
|
-
|
|
62
|
+
log.info("Dropped partition #{part}")
|
|
58
63
|
dropped << part
|
|
59
64
|
else
|
|
60
65
|
retained << part
|
|
61
66
|
end
|
|
62
67
|
end
|
|
63
68
|
|
|
69
|
+
log.info "PartitionManager drop_old_partitions table=#{table} dropped=#{dropped.size} retained=#{retained.size}"
|
|
64
70
|
{ dropped: dropped, retained: retained }
|
|
65
71
|
rescue StandardError => e
|
|
66
|
-
|
|
72
|
+
handle_exception(e, level: :warn, handled: true, operation: :drop_old_partitions, table: table, retention_months: retention_months)
|
|
67
73
|
{ dropped: [], retained: [], error: e.message }
|
|
68
74
|
end
|
|
69
75
|
|
|
@@ -80,12 +86,14 @@ module Legion
|
|
|
80
86
|
ORDER BY c.relname
|
|
81
87
|
SQL
|
|
82
88
|
|
|
83
|
-
Legion::Data.connection.fetch(sql).map do |row|
|
|
89
|
+
partitions = Legion::Data.connection.fetch(sql).map do |row|
|
|
84
90
|
from_val, to_val = parse_bound(row[:bound])
|
|
85
91
|
{ name: row[:name], from: from_val, to: to_val }
|
|
86
92
|
end
|
|
93
|
+
log.info "PartitionManager list_partitions table=#{table} count=#{partitions.size}"
|
|
94
|
+
partitions
|
|
87
95
|
rescue StandardError => e
|
|
88
|
-
|
|
96
|
+
handle_exception(e, level: :warn, handled: true, operation: :list_partitions, table: table)
|
|
89
97
|
[]
|
|
90
98
|
end
|
|
91
99
|
|
|
@@ -95,18 +103,6 @@ module Legion
|
|
|
95
103
|
Legion::Data::Connection.adapter == :postgres
|
|
96
104
|
end
|
|
97
105
|
|
|
98
|
-
def logging?
|
|
99
|
-
defined?(Legion::Logging)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def log_info(msg)
|
|
103
|
-
Legion::Logging.info(msg)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def log_warn(msg)
|
|
107
|
-
Legion::Logging.warn(msg)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
106
|
def partition_name(table, date)
|
|
111
107
|
"#{table}_y#{date.strftime('%Y')}m#{date.strftime('%m')}"
|
|
112
108
|
end
|
|
@@ -136,7 +132,7 @@ module Legion
|
|
|
136
132
|
|
|
137
133
|
Legion::Data.connection.fetch(sql).map { |row| row[:name] }
|
|
138
134
|
rescue StandardError => e
|
|
139
|
-
|
|
135
|
+
handle_exception(e, level: :warn, handled: true, operation: :partition_names_for, table: table)
|
|
140
136
|
[]
|
|
141
137
|
end
|
|
142
138
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
3
4
|
require_relative 'archival/policy'
|
|
4
5
|
|
|
5
6
|
module Legion
|
|
@@ -9,6 +10,8 @@ module Legion
|
|
|
9
10
|
DEFAULT_ARCHIVE_AFTER_DAYS = 90
|
|
10
11
|
|
|
11
12
|
class << self
|
|
13
|
+
include Legion::Logging::Helper
|
|
14
|
+
|
|
12
15
|
def archive_old_records(table:, date_column: nil, archive_after_days: DEFAULT_ARCHIVE_AFTER_DAYS)
|
|
13
16
|
db = Legion::Data.connection
|
|
14
17
|
return { archived: 0, table: table } unless db
|
|
@@ -29,8 +32,19 @@ module Legion
|
|
|
29
32
|
end
|
|
30
33
|
end
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
log.info "Archived #{count} row(s) from #{table}" if count.positive?
|
|
33
36
|
{ archived: count, table: table }
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
handle_exception(
|
|
39
|
+
e,
|
|
40
|
+
level: :error,
|
|
41
|
+
handled: false,
|
|
42
|
+
operation: :archive_old_records,
|
|
43
|
+
table: table,
|
|
44
|
+
date_column: date_column,
|
|
45
|
+
archive_after_days: archive_after_days
|
|
46
|
+
)
|
|
47
|
+
raise
|
|
34
48
|
end
|
|
35
49
|
|
|
36
50
|
def purge_expired_records(table:, date_column: nil, retention_years: DEFAULT_RETENTION_YEARS)
|
|
@@ -43,9 +57,20 @@ module Legion
|
|
|
43
57
|
expired = db[archive_table].where(Sequel.identifier(date_column) < cutoff)
|
|
44
58
|
count = expired.count
|
|
45
59
|
expired.delete if count.positive?
|
|
46
|
-
|
|
60
|
+
log.info "Purged #{count} expired row(s) from #{archive_table}" if count.positive?
|
|
47
61
|
|
|
48
62
|
{ purged: count, table: table }
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
handle_exception(
|
|
65
|
+
e,
|
|
66
|
+
level: :error,
|
|
67
|
+
handled: false,
|
|
68
|
+
operation: :purge_expired_records,
|
|
69
|
+
table: table,
|
|
70
|
+
date_column: date_column,
|
|
71
|
+
retention_years: retention_years
|
|
72
|
+
)
|
|
73
|
+
raise
|
|
49
74
|
end
|
|
50
75
|
|
|
51
76
|
def retention_status(table:, date_column: nil)
|
|
@@ -67,6 +92,9 @@ module Legion
|
|
|
67
92
|
oldest_active: oldest_active,
|
|
68
93
|
oldest_archived: oldest_archived
|
|
69
94
|
}
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
handle_exception(e, level: :warn, handled: false, operation: :retention_status, table: table, date_column: date_column)
|
|
97
|
+
raise
|
|
70
98
|
end
|
|
71
99
|
|
|
72
100
|
def archive_table_name(table)
|
|
@@ -90,6 +118,7 @@ module Legion
|
|
|
90
118
|
|
|
91
119
|
source_schema = db.schema(source_table).to_h
|
|
92
120
|
|
|
121
|
+
log.info "Creating archive table #{archive_table} from #{source_table}"
|
|
93
122
|
db.create_table(archive_table) do
|
|
94
123
|
source_schema.each do |col_name, col_info|
|
|
95
124
|
column col_name, col_info[:db_type]
|
data/lib/legion/data/rls.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Rls
|
|
8
|
+
extend Legion::Logging::Helper
|
|
9
|
+
|
|
6
10
|
RLS_TABLES = %i[
|
|
7
11
|
tasks digital_workers audit_log memory_traces extensions
|
|
8
12
|
functions runners nodes settings value_metrics
|
|
@@ -14,7 +18,8 @@ module Legion
|
|
|
14
18
|
return false unless Legion::Settings[:data][:connected]
|
|
15
19
|
|
|
16
20
|
Legion::Data.connection.adapter_scheme == :postgres
|
|
17
|
-
rescue StandardError
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
handle_exception(e, level: :warn, handled: true, operation: :rls_enabled)
|
|
18
23
|
false
|
|
19
24
|
end
|
|
20
25
|
|
|
@@ -30,7 +35,8 @@ module Legion
|
|
|
30
35
|
return nil unless rls_enabled?
|
|
31
36
|
|
|
32
37
|
Legion::Data.connection.fetch('SHOW app.current_tenant').first&.values&.first
|
|
33
|
-
rescue Sequel::DatabaseError
|
|
38
|
+
rescue Sequel::DatabaseError => e
|
|
39
|
+
handle_exception(e, level: :warn, handled: true, operation: :current_tenant)
|
|
34
40
|
nil
|
|
35
41
|
end
|
|
36
42
|
|
data/lib/legion/data/settings.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Data
|
|
5
7
|
module Settings
|
|
8
|
+
extend Legion::Logging::Helper
|
|
9
|
+
|
|
6
10
|
CREDS = {
|
|
7
11
|
sqlite: {
|
|
8
12
|
database: 'legionio.db'
|
|
@@ -130,5 +134,5 @@ end
|
|
|
130
134
|
begin
|
|
131
135
|
Legion::Settings.merge_settings('data', Legion::Data::Settings.default) if Legion.const_defined?('Settings', false)
|
|
132
136
|
rescue StandardError => e
|
|
133
|
-
Legion::
|
|
137
|
+
Legion::Data::Settings.handle_exception(e, level: :fatal, operation: :merge_settings)
|
|
134
138
|
end
|