ea 0.1.0 → 0.1.4
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/CLAUDE.md +125 -0
- data/Rakefile +12 -4
- data/TODO.next/00-publish-blocking-bugs.md +74 -0
- data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
- data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
- data/TODO.next/03-slim-lutaml-uml.md +79 -0
- data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
- data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
- data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
- data/TODO.next/07-transformer-registry-in-factory.md +20 -0
- data/TODO.next/08-connector-type-registry.md +27 -0
- data/TODO.next/09-element-renderer-registry.md +29 -0
- data/TODO.next/10-connector-renderer-lsp.md +18 -0
- data/TODO.next/11-consolidate-style-knowledge.md +33 -0
- data/TODO.next/12-data-driven-from-db-row.md +24 -0
- data/TODO.next/13-extract-duplicated-methods.md +17 -0
- data/TODO.next/14-remove-dead-code.md +10 -0
- data/TODO.next/15-narrow-exception-handling.md +39 -0
- data/TODO.next/16-repository-indexes.md +28 -0
- data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
- data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
- data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
- data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
- data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
- data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
- data/TODO.next/23-cleanup-idallocator.md +41 -0
- data/TODO.next/24-tighten-parity-specs.md +42 -0
- data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
- data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
- data/TODO.next/27-extract-cardinality-module.md +68 -0
- data/TODO.next/28-extract-xml-sanitizer.md +51 -0
- data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
- data/TODO.next/30-struct-return-for-association-end.md +37 -0
- data/TODO.next/31-idallocator-specs.md +27 -0
- data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
- data/TODO.next/33-normalize-lower-cleanup.md +30 -0
- data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
- data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
- data/TODO.next/36-wire-interface-realization.md +50 -0
- data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
- data/config/diagram_styles.yml +200 -0
- data/config/model_transformations.yml +266 -0
- data/config/qea_schema.yml +1024 -0
- data/docs/ea_to_uml_type_mapping.md +89 -0
- data/docs/xmi_qea_conversion_capabilities.md +99 -0
- data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
- data/examples/lur/basic.lur +0 -0
- data/examples/lur/test-output.lur +0 -0
- data/examples/lur/test.lur +0 -0
- data/examples/lur_basic_usage.rb +221 -0
- data/examples/lur_cli_workflow.rb +263 -0
- data/examples/lur_statistics.rb +326 -0
- data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
- data/examples/qea/ArcGISWorkspace_template.qea +0 -0
- data/examples/qea/README_qea_parser.adoc +230 -0
- data/examples/qea/UmlModel_template.qea +0 -0
- data/examples/qea/basic.qea +0 -0
- data/examples/qea/simple.qea +0 -0
- data/examples/qea/simple_example.qea +0 -0
- data/examples/qea/test.qea +0 -0
- data/examples/qea_standalone_query.rb +73 -0
- data/examples/qea_to_repository.rb +51 -0
- data/examples/smoke_test_real_qea.rb +81 -0
- data/exe/ea +7 -0
- data/lib/ea/cli/app.rb +72 -0
- data/lib/ea/cli/command/base.rb +80 -0
- data/lib/ea/cli/command/convert.rb +62 -0
- data/lib/ea/cli/command/diagrams.rb +81 -0
- data/lib/ea/cli/command/list.rb +61 -0
- data/lib/ea/cli/command/parse.rb +29 -0
- data/lib/ea/cli/command/stats.rb +20 -0
- data/lib/ea/cli/command/validate.rb +41 -0
- data/lib/ea/cli/command.rb +15 -0
- data/lib/ea/cli/error.rb +34 -0
- data/lib/ea/cli/output/formatter.rb +34 -0
- data/lib/ea/cli/output/json_formatter.rb +20 -0
- data/lib/ea/cli/output/table_formatter.rb +42 -0
- data/lib/ea/cli/output/yaml_formatter.rb +20 -0
- data/lib/ea/cli/output.rb +56 -0
- data/lib/ea/cli.rb +17 -0
- data/lib/ea/diagram/configuration.rb +379 -0
- data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
- data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
- data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
- data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
- data/lib/ea/diagram/element_renderers.rb +43 -0
- data/lib/ea/diagram/extractor.rb +560 -0
- data/lib/ea/diagram/layout_engine.rb +170 -0
- data/lib/ea/diagram/path_builder.rb +202 -0
- data/lib/ea/diagram/style_parser.rb +42 -0
- data/lib/ea/diagram/style_resolver.rb +276 -0
- data/lib/ea/diagram/svg_renderer.rb +274 -0
- data/lib/ea/diagram/util.rb +73 -0
- data/lib/ea/diagram.rb +47 -0
- data/lib/ea/qea/benchmark.rb +210 -0
- data/lib/ea/qea/database.rb +308 -0
- data/lib/ea/qea/factory/association_builder.rb +203 -0
- data/lib/ea/qea/factory/association_transformer.rb +91 -0
- data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
- data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
- data/lib/ea/qea/factory/base_transformer.rb +177 -0
- data/lib/ea/qea/factory/class_transformer.rb +116 -0
- data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
- data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
- data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
- data/lib/ea/qea/factory/document_builder.rb +283 -0
- data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
- data/lib/ea/qea/factory/enum_transformer.rb +74 -0
- data/lib/ea/qea/factory/generalization_builder.rb +227 -0
- data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
- data/lib/ea/qea/factory/instance_transformer.rb +68 -0
- data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
- data/lib/ea/qea/factory/operation_transformer.rb +66 -0
- data/lib/ea/qea/factory/package_transformer.rb +145 -0
- data/lib/ea/qea/factory/reference_resolver.rb +99 -0
- data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
- data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
- data/lib/ea/qea/factory/transformer_registry.rb +80 -0
- data/lib/ea/qea/factory.rb +37 -0
- data/lib/ea/qea/file_detector.rb +178 -0
- data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
- data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
- data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
- data/lib/ea/qea/infrastructure.rb +12 -0
- data/lib/ea/qea/models/base_model.rb +59 -0
- data/lib/ea/qea/models/ea_attribute.rb +109 -0
- data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
- data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
- data/lib/ea/qea/models/ea_connector.rb +160 -0
- data/lib/ea/qea/models/ea_connector_type.rb +60 -0
- data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
- data/lib/ea/qea/models/ea_datatype.rb +104 -0
- data/lib/ea/qea/models/ea_diagram.rb +115 -0
- data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
- data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
- data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
- data/lib/ea/qea/models/ea_document.rb +63 -0
- data/lib/ea/qea/models/ea_object.rb +223 -0
- data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
- data/lib/ea/qea/models/ea_object_property.rb +87 -0
- data/lib/ea/qea/models/ea_object_type.rb +73 -0
- data/lib/ea/qea/models/ea_operation.rb +127 -0
- data/lib/ea/qea/models/ea_operation_param.rb +76 -0
- data/lib/ea/qea/models/ea_package.rb +78 -0
- data/lib/ea/qea/models/ea_script.rb +62 -0
- data/lib/ea/qea/models/ea_status_type.rb +66 -0
- data/lib/ea/qea/models/ea_stereotype.rb +57 -0
- data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
- data/lib/ea/qea/models/ea_xref.rb +165 -0
- data/lib/ea/qea/models.rb +35 -0
- data/lib/ea/qea/repositories/base_repository.rb +225 -0
- data/lib/ea/qea/repositories/object_repository.rb +219 -0
- data/lib/ea/qea/repositories.rb +10 -0
- data/lib/ea/qea/services/configuration.rb +211 -0
- data/lib/ea/qea/services/database_loader.rb +191 -0
- data/lib/ea/qea/services.rb +10 -0
- data/lib/ea/qea/validation/association_validator.rb +73 -0
- data/lib/ea/qea/validation/attribute_validator.rb +91 -0
- data/lib/ea/qea/validation/base_validator.rb +331 -0
- data/lib/ea/qea/validation/class_validator.rb +121 -0
- data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
- data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
- data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
- data/lib/ea/qea/validation/database.rb +16 -0
- data/lib/ea/qea/validation/diagram_validator.rb +112 -0
- data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
- data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
- data/lib/ea/qea/validation/formatters.rb +12 -0
- data/lib/ea/qea/validation/operation_validator.rb +71 -0
- data/lib/ea/qea/validation/package_validator.rb +111 -0
- data/lib/ea/qea/validation/validation_engine.rb +387 -0
- data/lib/ea/qea/validation/validation_message.rb +144 -0
- data/lib/ea/qea/validation/validation_result.rb +210 -0
- data/lib/ea/qea/validation/validator_registry.rb +134 -0
- data/lib/ea/qea/validation.rb +28 -0
- data/lib/ea/qea/verification/comparison_result.rb +264 -0
- data/lib/ea/qea/verification/document_normalizer.rb +169 -0
- data/lib/ea/qea/verification/document_verifier.rb +322 -0
- data/lib/ea/qea/verification/element_comparator.rb +277 -0
- data/lib/ea/qea/verification/structure_matcher.rb +287 -0
- data/lib/ea/qea/verification.rb +14 -0
- data/lib/ea/qea.rb +185 -0
- data/lib/ea/transformations/configuration.rb +333 -0
- data/lib/ea/transformations/format_registry.rb +366 -0
- data/lib/ea/transformations/parsers/base_parser.rb +482 -0
- data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
- data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
- data/lib/ea/transformations/transformation_engine.rb +390 -0
- data/lib/ea/transformations.rb +85 -0
- data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
- data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
- data/lib/ea/transformers/qea_to_xmi.rb +29 -0
- data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
- data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
- data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
- data/lib/ea/transformers/uml_to_xmi.rb +16 -0
- data/lib/ea/transformers.rb +34 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
- data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
- data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
- data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
- data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
- data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
- data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
- data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
- data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
- data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
- data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
- data/lib/ea/xmi/lookup_service.rb +89 -0
- data/lib/ea/xmi/parser.rb +919 -0
- data/lib/ea/xmi.rb +35 -0
- data/lib/ea.rb +10 -1
- metadata +382 -9
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Ea
|
|
7
|
+
module Qea
|
|
8
|
+
module Services
|
|
9
|
+
# Configuration service loads and provides configuration from YAML file.
|
|
10
|
+
#
|
|
11
|
+
# This service uses lutaml-model for YAML parsing and provides access to
|
|
12
|
+
# QEA schema configuration including table definitions, type mappings,
|
|
13
|
+
# and transformation rules.
|
|
14
|
+
#
|
|
15
|
+
# @example Load configuration
|
|
16
|
+
# config = Configuration.load
|
|
17
|
+
# tables = config.enabled_tables
|
|
18
|
+
#
|
|
19
|
+
# @example Get table configuration
|
|
20
|
+
# table_cfg = config.table_config_for("t_object")
|
|
21
|
+
class Configuration < Lutaml::Model::Serializable
|
|
22
|
+
# Column definition model
|
|
23
|
+
class ColumnDefinition < Lutaml::Model::Serializable
|
|
24
|
+
attribute :name, :string
|
|
25
|
+
attribute :type, :string
|
|
26
|
+
attribute :primary, :boolean, default: -> { false }
|
|
27
|
+
attribute :nullable, :boolean, default: -> { true }
|
|
28
|
+
attribute :boolean, :boolean, default: -> { false }
|
|
29
|
+
attribute :default, :string
|
|
30
|
+
|
|
31
|
+
yaml do
|
|
32
|
+
map "name", to: :name
|
|
33
|
+
map "type", to: :type
|
|
34
|
+
map "primary", to: :primary
|
|
35
|
+
map "nullable", to: :nullable
|
|
36
|
+
map "boolean", to: :boolean
|
|
37
|
+
map "default", to: :default
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Table definition model
|
|
42
|
+
class TableDefinition < Lutaml::Model::Serializable
|
|
43
|
+
attribute :table_name, :string
|
|
44
|
+
attribute :enabled, :boolean, default: -> { true }
|
|
45
|
+
attribute :primary_key, :string
|
|
46
|
+
attribute :collection_name, :string
|
|
47
|
+
attribute :description, :string
|
|
48
|
+
attribute :columns, ColumnDefinition, collection: true
|
|
49
|
+
|
|
50
|
+
yaml do
|
|
51
|
+
map "table_name", to: :table_name
|
|
52
|
+
map "enabled", to: :enabled
|
|
53
|
+
map "primary_key", to: :primary_key
|
|
54
|
+
map "collection_name", to: :collection_name
|
|
55
|
+
map "description", to: :description
|
|
56
|
+
map "columns", to: :columns
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get column definition by name
|
|
60
|
+
#
|
|
61
|
+
# @param column_name [String] The column name
|
|
62
|
+
# @return [ColumnDefinition, nil] The column definition or nil
|
|
63
|
+
def column_for(column_name)
|
|
64
|
+
columns&.find { |col| col.name == column_name }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if a column is boolean type
|
|
68
|
+
#
|
|
69
|
+
# @param column_name [String] The column name
|
|
70
|
+
# @return [Boolean] true if column should be treated as boolean
|
|
71
|
+
def boolean_column?(column_name)
|
|
72
|
+
col = column_for(column_name)
|
|
73
|
+
col&.boolean == true
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Null handling configuration model
|
|
78
|
+
class NullHandling < Lutaml::Model::Serializable
|
|
79
|
+
attribute :strategy, :string
|
|
80
|
+
attribute :empty_string_as_null, :boolean, default: -> { true }
|
|
81
|
+
attribute :zero_as_null, :boolean, default: -> { false }
|
|
82
|
+
|
|
83
|
+
yaml do
|
|
84
|
+
map "strategy", to: :strategy
|
|
85
|
+
map "empty_string_as_null", to: :empty_string_as_null
|
|
86
|
+
map "zero_as_null", to: :zero_as_null
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
attribute :version, :string
|
|
91
|
+
attribute :description, :string
|
|
92
|
+
attribute :type_mappings, :string, collection: true
|
|
93
|
+
attribute :boolean_fields, :string, collection: true
|
|
94
|
+
attribute :null_handling, NullHandling
|
|
95
|
+
attribute :tables, TableDefinition, collection: true
|
|
96
|
+
|
|
97
|
+
yaml do
|
|
98
|
+
map "version", to: :version
|
|
99
|
+
map "description", to: :description
|
|
100
|
+
map "type_mappings", to: :type_mappings
|
|
101
|
+
map "boolean_fields", to: :boolean_fields
|
|
102
|
+
map "null_handling", to: :null_handling
|
|
103
|
+
map "tables", to: :tables
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class << self
|
|
107
|
+
# Load configuration from YAML file
|
|
108
|
+
#
|
|
109
|
+
# @param config_path [String, nil] Path to configuration file
|
|
110
|
+
# Defaults to config/qea_schema.yml
|
|
111
|
+
# @return [Configuration] The loaded configuration
|
|
112
|
+
# @raise [Errno::ENOENT] if config file not found
|
|
113
|
+
# @raise [Lutaml::Model::Error] if YAML is invalid
|
|
114
|
+
def load(config_path = nil)
|
|
115
|
+
config_path ||= default_config_path
|
|
116
|
+
|
|
117
|
+
unless File.exist?(config_path)
|
|
118
|
+
raise Errno::ENOENT,
|
|
119
|
+
"Configuration file not found: #{config_path}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
yaml_content = File.read(config_path)
|
|
123
|
+
from_yaml(yaml_content)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Get default configuration file path
|
|
127
|
+
#
|
|
128
|
+
# @return [String] Path to default config file
|
|
129
|
+
def default_config_path
|
|
130
|
+
File.expand_path("../../../../config/qea_schema.yml", __dir__)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Get list of enabled tables
|
|
135
|
+
#
|
|
136
|
+
# @return [Array<TableDefinition>] Array of enabled table definitions
|
|
137
|
+
def enabled_tables
|
|
138
|
+
tables&.select(&:enabled) || []
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Get table configuration by table name
|
|
142
|
+
#
|
|
143
|
+
# @param table_name [String] The table name
|
|
144
|
+
# @return [TableDefinition, nil] The table definition
|
|
145
|
+
# or nil if not found
|
|
146
|
+
def table_config_for(table_name)
|
|
147
|
+
tables&.find { |t| t.table_name == table_name }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Check if a table is enabled
|
|
151
|
+
#
|
|
152
|
+
# @param table_name [String] The table name
|
|
153
|
+
# @return [Boolean] true if table is enabled
|
|
154
|
+
def table_enabled?(table_name)
|
|
155
|
+
table = table_config_for(table_name)
|
|
156
|
+
table&.enabled == true
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Get all enabled table names
|
|
160
|
+
#
|
|
161
|
+
# @return [Array<String>] Array of enabled table names
|
|
162
|
+
def enabled_table_names
|
|
163
|
+
enabled_tables.map(&:table_name)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Check if a field should be treated as boolean
|
|
167
|
+
#
|
|
168
|
+
# @param field_name [String] The field name
|
|
169
|
+
# @return [Boolean] true if field is in boolean_fields list
|
|
170
|
+
def boolean_field?(field_name)
|
|
171
|
+
boolean_fields&.include?(field_name) || false
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Get primary key for a table
|
|
175
|
+
#
|
|
176
|
+
# @param table_name [String] The table name
|
|
177
|
+
# @return [String, nil] The primary key column name
|
|
178
|
+
def primary_key_for(table_name)
|
|
179
|
+
table = table_config_for(table_name)
|
|
180
|
+
table&.primary_key
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Get collection name for a table
|
|
184
|
+
#
|
|
185
|
+
# @param table_name [String] The table name
|
|
186
|
+
# @return [String, nil] The collection name
|
|
187
|
+
def collection_name_for(table_name)
|
|
188
|
+
table = table_config_for(table_name)
|
|
189
|
+
table&.collection_name
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Convert empty strings to nil based on configuration
|
|
193
|
+
#
|
|
194
|
+
# @param value [String, nil] The value to convert
|
|
195
|
+
# @return [String, nil] The converted value
|
|
196
|
+
def convert_empty_string(value)
|
|
197
|
+
return value unless null_handling&.empty_string_as_null
|
|
198
|
+
|
|
199
|
+
value.nil? || value.empty? ? nil : value
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Check if zero should be treated as null
|
|
203
|
+
#
|
|
204
|
+
# @return [Boolean] true if zero should be converted to nil
|
|
205
|
+
def zero_as_null?
|
|
206
|
+
null_handling&.zero_as_null == true
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Services
|
|
6
|
+
# DatabaseLoader orchestrates loading all EA tables into a Database
|
|
7
|
+
#
|
|
8
|
+
# This service loads all enabled tables from the QEA database,
|
|
9
|
+
# converts database rows to model instances, and populates a
|
|
10
|
+
# Database container with the results.
|
|
11
|
+
#
|
|
12
|
+
# @example Load a database
|
|
13
|
+
# loader = DatabaseLoader.new("model.qea")
|
|
14
|
+
# loader.on_progress do |table, current, total|
|
|
15
|
+
# puts "Loading #{table}: #{current}/#{total}"
|
|
16
|
+
# end
|
|
17
|
+
# database = loader.load
|
|
18
|
+
#
|
|
19
|
+
# @example Load a single table
|
|
20
|
+
# loader = DatabaseLoader.new("model.qea")
|
|
21
|
+
# objects = loader.load_table("t_object")
|
|
22
|
+
class DatabaseLoader
|
|
23
|
+
# @return [String] Path to QEA file
|
|
24
|
+
attr_reader :qea_path
|
|
25
|
+
|
|
26
|
+
# @return [Configuration] Configuration instance
|
|
27
|
+
attr_reader :config
|
|
28
|
+
|
|
29
|
+
# Table name to model class mapping
|
|
30
|
+
MODEL_CLASSES = {
|
|
31
|
+
"t_object" => Models::EaObject,
|
|
32
|
+
"t_attribute" => Models::EaAttribute,
|
|
33
|
+
"t_operation" => Models::EaOperation,
|
|
34
|
+
"t_operationparams" => Models::EaOperationParam,
|
|
35
|
+
"t_connector" => Models::EaConnector,
|
|
36
|
+
"t_package" => Models::EaPackage,
|
|
37
|
+
"t_diagram" => Models::EaDiagram,
|
|
38
|
+
"t_diagramobjects" => Models::EaDiagramObject,
|
|
39
|
+
"t_diagramlinks" => Models::EaDiagramLink,
|
|
40
|
+
"t_objectconstraint" => Models::EaObjectConstraint,
|
|
41
|
+
"t_taggedvalue" => Models::EaTaggedValue,
|
|
42
|
+
"t_objectproperties" => Models::EaObjectProperty,
|
|
43
|
+
"t_attributetag" => Models::EaAttributeTag,
|
|
44
|
+
"t_xref" => Models::EaXref,
|
|
45
|
+
"t_document" => Models::EaDocument,
|
|
46
|
+
"t_script" => Models::EaScript,
|
|
47
|
+
"t_stereotypes" => Models::EaStereotype,
|
|
48
|
+
"t_datatypes" => Models::EaDatatype,
|
|
49
|
+
"t_constrainttypes" => Models::EaConstraintType,
|
|
50
|
+
"t_connectortypes" => Models::EaConnectorType,
|
|
51
|
+
"t_diagramtypes" => Models::EaDiagramType,
|
|
52
|
+
"t_objecttypes" => Models::EaObjectType,
|
|
53
|
+
"t_statustypes" => Models::EaStatusType,
|
|
54
|
+
"t_complexitytypes" => Models::EaComplexityType,
|
|
55
|
+
}.freeze
|
|
56
|
+
|
|
57
|
+
# Initialize loader
|
|
58
|
+
#
|
|
59
|
+
# @param qea_path [String] Path to QEA file
|
|
60
|
+
# @param config [Configuration, nil] Optional configuration
|
|
61
|
+
def initialize(qea_path, config = nil)
|
|
62
|
+
@qea_path = qea_path
|
|
63
|
+
@config = config || Configuration.load
|
|
64
|
+
@progress_callback = nil
|
|
65
|
+
@connection = Infrastructure::DatabaseConnection.new(qea_path)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Set progress callback
|
|
69
|
+
#
|
|
70
|
+
# @yield [table_name, current, total] Progress information
|
|
71
|
+
# @yieldparam table_name [String] Current table being loaded
|
|
72
|
+
# @yieldparam current [Integer] Records loaded so far
|
|
73
|
+
# @yieldparam total [Integer] Total records in table
|
|
74
|
+
# @return [self]
|
|
75
|
+
def on_progress(&block)
|
|
76
|
+
@progress_callback = block
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Load entire database
|
|
81
|
+
#
|
|
82
|
+
# @return [Database] Populated database instance
|
|
83
|
+
# @raise [Errno::ENOENT] if QEA file not found
|
|
84
|
+
# @raise [SQLite3::Exception] if database access fails
|
|
85
|
+
def load
|
|
86
|
+
# Connect but don't use with_connection - we need to keep it open
|
|
87
|
+
@connection.connect unless @connection.connected?
|
|
88
|
+
db = @connection.connection
|
|
89
|
+
|
|
90
|
+
database = Database.new(@qea_path, db)
|
|
91
|
+
|
|
92
|
+
@config.enabled_tables.each do |table_def|
|
|
93
|
+
table_name = table_def.table_name
|
|
94
|
+
collection_name = table_def.collection_name
|
|
95
|
+
|
|
96
|
+
records = load_table_records(db, table_name)
|
|
97
|
+
database.add_collection(collection_name, records)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
database.freeze
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Load a single table
|
|
104
|
+
#
|
|
105
|
+
# @param table_name [String] Table name to load
|
|
106
|
+
# @return [Array] Array of model instances
|
|
107
|
+
# @raise [ArgumentError] if table not configured or not enabled
|
|
108
|
+
def load_table(table_name) # rubocop:disable Metrics/MethodLength
|
|
109
|
+
table_def = @config.table_config_for(table_name)
|
|
110
|
+
unless table_def
|
|
111
|
+
raise ArgumentError,
|
|
112
|
+
"Table #{table_name} not configured"
|
|
113
|
+
end
|
|
114
|
+
unless table_def.enabled
|
|
115
|
+
raise ArgumentError,
|
|
116
|
+
"Table #{table_name} not enabled"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
@connection.with_connection do |db|
|
|
120
|
+
load_table_records(db, table_name)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Get quick statistics without full loading
|
|
125
|
+
#
|
|
126
|
+
# @return [Hash<String, Integer>] Table names to record counts
|
|
127
|
+
def quick_stats
|
|
128
|
+
stats = {}
|
|
129
|
+
|
|
130
|
+
@connection.with_connection do |db|
|
|
131
|
+
@config.enabled_tables.each do |table_def|
|
|
132
|
+
reader = Infrastructure::TableReader.new(db, table_def.table_name)
|
|
133
|
+
stats[table_def.collection_name] = reader.count
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
stats
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# Load records for a table
|
|
143
|
+
#
|
|
144
|
+
# @param db [SQLite3::Database] Database connection
|
|
145
|
+
# @param table_name [String] Table name
|
|
146
|
+
# @return [Array] Array of model instances
|
|
147
|
+
def load_table_records(db, table_name) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
148
|
+
model_class = MODEL_CLASSES[table_name]
|
|
149
|
+
unless model_class
|
|
150
|
+
raise ArgumentError, "No model class for table #{table_name}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
reader = Infrastructure::TableReader.new(db, table_name)
|
|
154
|
+
total = reader.count
|
|
155
|
+
records = []
|
|
156
|
+
|
|
157
|
+
rows = reader.all
|
|
158
|
+
rows.each_with_index do |row, index|
|
|
159
|
+
record = model_class.from_db_row(row)
|
|
160
|
+
records << record if record
|
|
161
|
+
if @progress_callback
|
|
162
|
+
report_progress(table_name, index + 1,
|
|
163
|
+
total)
|
|
164
|
+
end
|
|
165
|
+
rescue ArgumentError, TypeError, EncodingError => e
|
|
166
|
+
warn "Error loading record from #{table_name}: #{e.message}"
|
|
167
|
+
rescue StandardError => e
|
|
168
|
+
# Catch-all for record-level failures: skip bad records,
|
|
169
|
+
# don't abort the entire load. Connection-level errors propagate.
|
|
170
|
+
warn "Unexpected error loading record from #{table_name}: #{e.message}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
records
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Report progress to callback
|
|
177
|
+
#
|
|
178
|
+
# @param table_name [String] Current table
|
|
179
|
+
# @param current [Integer] Current record count
|
|
180
|
+
# @param total [Integer] Total records
|
|
181
|
+
def report_progress(table_name, current, total)
|
|
182
|
+
@progress_callback.call(table_name, current, total)
|
|
183
|
+
rescue StandardError => e
|
|
184
|
+
# Progress callbacks are user-supplied; isolate their failures from
|
|
185
|
+
# the load pipeline. The callback's contract is "best effort".
|
|
186
|
+
warn "Error in progress callback: #{e.message}"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Validation
|
|
6
|
+
# Validates association/connector references
|
|
7
|
+
class AssociationValidator < BaseValidator
|
|
8
|
+
def validate
|
|
9
|
+
validate_object_references
|
|
10
|
+
validate_connector_endpoints
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def validate_object_references
|
|
16
|
+
connectors.select(&:association?).each do |conn|
|
|
17
|
+
validate_start_object(conn)
|
|
18
|
+
validate_end_object(conn)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate_start_object(connector) # rubocop:disable Metrics/MethodLength
|
|
23
|
+
return if reference_exists?("t_object", "ea_object_id",
|
|
24
|
+
connector.start_object_id)
|
|
25
|
+
|
|
26
|
+
result.add_error(
|
|
27
|
+
category: :missing_reference,
|
|
28
|
+
entity_type: :association,
|
|
29
|
+
entity_id: connector.connector_id.to_s,
|
|
30
|
+
entity_name: connector.name || "Unnamed",
|
|
31
|
+
field: "start_object_id",
|
|
32
|
+
reference: connector.start_object_id.to_s,
|
|
33
|
+
message: "Start object #{connector.start_object_id} does not exist",
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_end_object(connector) # rubocop:disable Metrics/MethodLength
|
|
38
|
+
return if reference_exists?("t_object", "ea_object_id",
|
|
39
|
+
connector.end_object_id)
|
|
40
|
+
|
|
41
|
+
result.add_error(
|
|
42
|
+
category: :missing_reference,
|
|
43
|
+
entity_type: :association,
|
|
44
|
+
entity_id: connector.connector_id.to_s,
|
|
45
|
+
entity_name: connector.name || "Unnamed",
|
|
46
|
+
field: "end_object_id",
|
|
47
|
+
reference: connector.end_object_id.to_s,
|
|
48
|
+
message: "End object #{connector.end_object_id} does not exist",
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def validate_connector_endpoints # rubocop:disable Metrics/MethodLength
|
|
53
|
+
connectors.each do |conn|
|
|
54
|
+
# Validate both endpoints exist
|
|
55
|
+
unless conn.start_object_id && conn.end_object_id
|
|
56
|
+
result.add_error(
|
|
57
|
+
category: :invalid_data,
|
|
58
|
+
entity_type: :connector,
|
|
59
|
+
entity_id: conn.connector_id.to_s,
|
|
60
|
+
entity_name: conn.name || "Unnamed",
|
|
61
|
+
message: "Connector missing start or end object reference",
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def connectors
|
|
68
|
+
@connectors ||= context[:connectors] || []
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Validation
|
|
6
|
+
# Validates attribute references and structure
|
|
7
|
+
class AttributeValidator < BaseValidator
|
|
8
|
+
def validate
|
|
9
|
+
validate_parent_object_references
|
|
10
|
+
validate_type_references
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def validate_parent_object_references # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
16
|
+
attributes.each do |attr|
|
|
17
|
+
unless reference_exists?("t_object", "ea_object_id",
|
|
18
|
+
attr.ea_object_id)
|
|
19
|
+
parent = database&.objects&.all&.find do |o|
|
|
20
|
+
o.ea_object_id == attr.ea_object_id
|
|
21
|
+
end
|
|
22
|
+
attr_location = if parent
|
|
23
|
+
"#{resolve_class_path(
|
|
24
|
+
parent.ea_object_id,
|
|
25
|
+
parent.name,
|
|
26
|
+
)}::#{attr.name}"
|
|
27
|
+
else
|
|
28
|
+
"Unknown::#{attr.name} " \
|
|
29
|
+
"(attribute_id: #{attr.id})"
|
|
30
|
+
end
|
|
31
|
+
result.add_error(
|
|
32
|
+
category: :missing_reference,
|
|
33
|
+
entity_type: :attribute,
|
|
34
|
+
entity_id: attr.id.to_s,
|
|
35
|
+
entity_name: attr.name,
|
|
36
|
+
field: "ea_object_id",
|
|
37
|
+
reference: attr.ea_object_id.to_s,
|
|
38
|
+
message: "Parent object #{attr.ea_object_id} does not exist",
|
|
39
|
+
location: attr_location,
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validate_type_references # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
46
|
+
attributes.each do |attr|
|
|
47
|
+
next unless attr.classifier && !attr.classifier.empty?
|
|
48
|
+
|
|
49
|
+
# Classifier can be either an object ID or a primitive type name
|
|
50
|
+
next if primitive_type?(attr.classifier)
|
|
51
|
+
next if classifier_exists?(attr.classifier)
|
|
52
|
+
|
|
53
|
+
parent = database&.objects&.all&.find do |o|
|
|
54
|
+
o.ea_object_id == attr.ea_object_id
|
|
55
|
+
end
|
|
56
|
+
attr_location = if parent
|
|
57
|
+
"#{resolve_class_path(parent.ea_object_id,
|
|
58
|
+
parent.name)}::#{attr.name}"
|
|
59
|
+
else
|
|
60
|
+
"Unknown::#{attr.name} (attribute_id: #{attr.id})"
|
|
61
|
+
end
|
|
62
|
+
result.add_warning(
|
|
63
|
+
category: :missing_reference,
|
|
64
|
+
entity_type: :attribute,
|
|
65
|
+
entity_id: attr.id.to_s,
|
|
66
|
+
entity_name: "#{parent&.name}.#{attr.name}",
|
|
67
|
+
field: "classifier",
|
|
68
|
+
reference: attr.classifier,
|
|
69
|
+
message: "Classifier '#{attr.classifier}' not found",
|
|
70
|
+
location: attr_location,
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def attributes
|
|
76
|
+
@attributes ||= context[:attributes] || []
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def classifier_exists?(classifier_id)
|
|
80
|
+
return false unless database
|
|
81
|
+
|
|
82
|
+
# Classifier field contains object_id, not name
|
|
83
|
+
database.objects.all.any? do |o|
|
|
84
|
+
o.ea_object_id.to_s == classifier_id.to_s
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|