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,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Infrastructure
|
|
6
|
+
# SchemaReader reads database schema information from a
|
|
7
|
+
# QEA SQLite database.
|
|
8
|
+
#
|
|
9
|
+
# This class is responsible for introspecting the database schema,
|
|
10
|
+
# including table names, column definitions, and metadata.
|
|
11
|
+
#
|
|
12
|
+
# @example Read schema information
|
|
13
|
+
# reader = SchemaReader.new(db_connection)
|
|
14
|
+
# tables = reader.tables
|
|
15
|
+
# columns = reader.columns("t_object")
|
|
16
|
+
class SchemaReader
|
|
17
|
+
attr_reader :database
|
|
18
|
+
|
|
19
|
+
# Initialize a new schema reader
|
|
20
|
+
#
|
|
21
|
+
# @param database [SQLite3::Database] The database connection
|
|
22
|
+
# @raise [ArgumentError] if database is nil
|
|
23
|
+
def initialize(database)
|
|
24
|
+
raise ArgumentError, "database cannot be nil" if database.nil?
|
|
25
|
+
|
|
26
|
+
@database = database
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get list of all table names in the database
|
|
30
|
+
#
|
|
31
|
+
# @param exclude_system [Boolean]
|
|
32
|
+
# Exclude SQLite system tables (default: true)
|
|
33
|
+
# @return [Array<String>] List of table names
|
|
34
|
+
def tables(exclude_system: true)
|
|
35
|
+
query = "SELECT name FROM sqlite_master WHERE type='table'"
|
|
36
|
+
query += " AND name NOT LIKE 'sqlite_%'" if exclude_system
|
|
37
|
+
query += " ORDER BY name"
|
|
38
|
+
|
|
39
|
+
@database.execute(query).map { |row| row["name"] }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get column information for a specific table
|
|
43
|
+
#
|
|
44
|
+
# @param table_name [String] The table name
|
|
45
|
+
# @return [Array<Hash>] Array of column information hashes
|
|
46
|
+
# Each hash contains: name, type, notnull, dflt_value, pk
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# columns = reader.columns("t_object")
|
|
50
|
+
# # => [
|
|
51
|
+
# # {"cid"=>0, "name"=>"Object_ID", "type"=>"INTEGER",
|
|
52
|
+
# # "notnull"=>1, "dflt_value"=>nil, "pk"=>1},
|
|
53
|
+
# # ...
|
|
54
|
+
# # ]
|
|
55
|
+
def columns(table_name)
|
|
56
|
+
@database.execute("PRAGMA table_info(#{table_name})")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get just column names for a specific table
|
|
60
|
+
#
|
|
61
|
+
# @param table_name [String] The table name
|
|
62
|
+
# @return [Array<String>] List of column names
|
|
63
|
+
def column_names(table_name)
|
|
64
|
+
columns(table_name).map { |col| col["name"] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if a table exists in the database
|
|
68
|
+
#
|
|
69
|
+
# @param table_name [String] The table name to check
|
|
70
|
+
# @return [Boolean] true if table exists
|
|
71
|
+
def table_exists?(table_name)
|
|
72
|
+
result = @database.execute(
|
|
73
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
74
|
+
table_name,
|
|
75
|
+
)
|
|
76
|
+
!result.empty?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get primary key column name for a table
|
|
80
|
+
#
|
|
81
|
+
# @param table_name [String] The table name
|
|
82
|
+
# @return [String, nil] The primary key column name,
|
|
83
|
+
# or nil if no primary key
|
|
84
|
+
def primary_key(table_name)
|
|
85
|
+
pk_column = columns(table_name).find { |col| col["pk"] == 1 }
|
|
86
|
+
pk_column&.fetch("name", nil)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Get table schema as CREATE TABLE statement
|
|
90
|
+
#
|
|
91
|
+
# @param table_name [String] The table name
|
|
92
|
+
# @return [String, nil] The CREATE TABLE SQL statement,
|
|
93
|
+
# or nil if table doesn't exist
|
|
94
|
+
def table_schema(table_name)
|
|
95
|
+
result = @database.execute(
|
|
96
|
+
"SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
|
|
97
|
+
table_name,
|
|
98
|
+
)
|
|
99
|
+
result.first&.fetch("sql", nil)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get index information for a table
|
|
103
|
+
#
|
|
104
|
+
# @param table_name [String] The table name
|
|
105
|
+
# @return [Array<Hash>] Array of index information
|
|
106
|
+
def indexes(table_name)
|
|
107
|
+
@database.execute(
|
|
108
|
+
"SELECT name, sql FROM sqlite_master " \
|
|
109
|
+
"WHERE type='index' AND tbl_name=?",
|
|
110
|
+
table_name,
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Get row count for a table
|
|
115
|
+
#
|
|
116
|
+
# @param table_name [String] The table name
|
|
117
|
+
# @return [Integer] Number of rows in the table
|
|
118
|
+
def row_count(table_name)
|
|
119
|
+
result = @database.execute(
|
|
120
|
+
"SELECT COUNT(*) as count FROM #{table_name}",
|
|
121
|
+
)
|
|
122
|
+
result.first["count"]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get schema statistics for all tables
|
|
126
|
+
#
|
|
127
|
+
# @return [Hash] Hash mapping table names to row counts
|
|
128
|
+
def statistics
|
|
129
|
+
tables.to_h do |table_name|
|
|
130
|
+
[table_name, row_count(table_name)]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Infrastructure
|
|
6
|
+
# TableReader reads data from a single table in a QEA SQLite database.
|
|
7
|
+
#
|
|
8
|
+
# This class provides methods to query and retrieve records from
|
|
9
|
+
# database tables with filtering and counting capabilities.
|
|
10
|
+
#
|
|
11
|
+
# @example Read all records from a table
|
|
12
|
+
# reader = TableReader.new(db_connection, "t_object")
|
|
13
|
+
# objects = reader.all
|
|
14
|
+
#
|
|
15
|
+
# @example Read with filtering
|
|
16
|
+
# reader = TableReader.new(db_connection, "t_object")
|
|
17
|
+
# classes = reader.where("Object_Type = ?", "Class")
|
|
18
|
+
class TableReader
|
|
19
|
+
attr_reader :database, :table_name
|
|
20
|
+
|
|
21
|
+
# Initialize a new table reader
|
|
22
|
+
#
|
|
23
|
+
# @param database [SQLite3::Database] The database connection
|
|
24
|
+
# @param table_name [String] The table name to read from
|
|
25
|
+
# @raise [ArgumentError] if database or table_name is nil
|
|
26
|
+
def initialize(database, table_name)
|
|
27
|
+
raise ArgumentError, "database cannot be nil" if database.nil?
|
|
28
|
+
|
|
29
|
+
if table_name.nil? || table_name.empty?
|
|
30
|
+
raise ArgumentError,
|
|
31
|
+
"table_name cannot be nil or empty"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@database = database
|
|
35
|
+
@table_name = table_name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Read all records from the table
|
|
39
|
+
#
|
|
40
|
+
# @param limit [Integer, nil] Maximum number of records to return
|
|
41
|
+
# (optional)
|
|
42
|
+
# @param offset [Integer, nil] Number of records to skip (optional)
|
|
43
|
+
# @return [Array<Hash>] Array of record hashes
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# reader.all
|
|
47
|
+
# # => [{"Object_ID"=>1, "Name"=>"MyClass", ...}, ...]
|
|
48
|
+
#
|
|
49
|
+
# @example With limit
|
|
50
|
+
# reader.all(limit: 10)
|
|
51
|
+
# # => Returns first 10 records
|
|
52
|
+
def all(limit: nil, offset: nil) # rubocop:disable Metrics/MethodLength
|
|
53
|
+
query = "SELECT * FROM #{@table_name}"
|
|
54
|
+
params = []
|
|
55
|
+
|
|
56
|
+
if limit
|
|
57
|
+
query += " LIMIT ?"
|
|
58
|
+
params << limit
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if offset
|
|
62
|
+
query += " OFFSET ?"
|
|
63
|
+
params << offset
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@database.execute(query, params)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Read records matching a WHERE clause
|
|
70
|
+
#
|
|
71
|
+
# @param conditions [String] The WHERE clause
|
|
72
|
+
# (without the WHERE keyword)
|
|
73
|
+
# @param values [Array] Values for parameterized query placeholders
|
|
74
|
+
# @param limit [Integer, nil] Maximum number of records
|
|
75
|
+
# to return (optional)
|
|
76
|
+
# @param offset [Integer, nil] Number of records to skip (optional)
|
|
77
|
+
# @return [Array<Hash>] Array of matching record hashes
|
|
78
|
+
#
|
|
79
|
+
# @example Simple filter
|
|
80
|
+
# reader.where("Object_Type = ?", "Class")
|
|
81
|
+
#
|
|
82
|
+
# @example Multiple conditions
|
|
83
|
+
# reader.where("Object_Type = ? AND Package_ID = ?", "Class", 5)
|
|
84
|
+
#
|
|
85
|
+
# @example With limit
|
|
86
|
+
# reader.where("Object_Type = ?", "Class", limit: 10)
|
|
87
|
+
def where(conditions, *values, limit: nil, offset: nil) # rubocop:disable Metrics/MethodLength
|
|
88
|
+
query = "SELECT * FROM #{@table_name} WHERE #{conditions}"
|
|
89
|
+
params = values.flatten
|
|
90
|
+
|
|
91
|
+
if limit
|
|
92
|
+
query += " LIMIT ?"
|
|
93
|
+
params << limit
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if offset
|
|
97
|
+
query += " OFFSET ?"
|
|
98
|
+
params << offset
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@database.execute(query, params)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Count all records in the table
|
|
105
|
+
#
|
|
106
|
+
# @return [Integer] Total number of records
|
|
107
|
+
def count
|
|
108
|
+
result = @database.execute(
|
|
109
|
+
"SELECT COUNT(*) as count FROM #{@table_name}",
|
|
110
|
+
)
|
|
111
|
+
result.first["count"]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Count records matching a WHERE clause
|
|
115
|
+
#
|
|
116
|
+
# @param conditions [String] The WHERE clause
|
|
117
|
+
# (without the WHERE keyword)
|
|
118
|
+
# @param values [Array] Values for parameterized query placeholders
|
|
119
|
+
# @return [Integer] Number of matching records
|
|
120
|
+
#
|
|
121
|
+
# @example
|
|
122
|
+
# reader.count_where("Object_Type = ?", "Class")
|
|
123
|
+
# # => 42
|
|
124
|
+
def count_where(conditions, *values)
|
|
125
|
+
query = "SELECT COUNT(*) as count FROM #{@table_name} " \
|
|
126
|
+
"WHERE #{conditions}"
|
|
127
|
+
result = @database.execute(query, values.flatten)
|
|
128
|
+
result.first["count"]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Find a single record by primary key value
|
|
132
|
+
#
|
|
133
|
+
# @param primary_key_column [String] The primary key column name
|
|
134
|
+
# @param value [Object] The primary key value to search for
|
|
135
|
+
# @return [Hash, nil] The matching record hash, or nil if not found
|
|
136
|
+
#
|
|
137
|
+
# @example
|
|
138
|
+
# reader.find_by_pk("Object_ID", 123)
|
|
139
|
+
# # => {"Object_ID"=>123, "Name"=>"MyClass", ...}
|
|
140
|
+
def find_by_pk(primary_key_column, value)
|
|
141
|
+
query = "SELECT * FROM #{@table_name} " \
|
|
142
|
+
"WHERE #{primary_key_column} = ? LIMIT 1"
|
|
143
|
+
result = @database.execute(query, [value])
|
|
144
|
+
result.first
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Find first record matching a WHERE clause
|
|
148
|
+
#
|
|
149
|
+
# @param conditions [String] The WHERE clause
|
|
150
|
+
# (without the WHERE keyword)
|
|
151
|
+
# @param values [Array] Values for parameterized query placeholders
|
|
152
|
+
# @return [Hash, nil] The first matching record hash,
|
|
153
|
+
# or nil if not found
|
|
154
|
+
#
|
|
155
|
+
# @example
|
|
156
|
+
# reader.find_first("Name = ?", "MyClass")
|
|
157
|
+
# # => {"Object_ID"=>123, "Name"=>"MyClass", ...}
|
|
158
|
+
def find_first(conditions, *values)
|
|
159
|
+
query = "SELECT * FROM #{@table_name} WHERE #{conditions} LIMIT 1"
|
|
160
|
+
result = @database.execute(query, values.flatten)
|
|
161
|
+
result.first
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Execute a custom SQL query on this table
|
|
165
|
+
#
|
|
166
|
+
# @param sql [String] The SQL query (should reference the table)
|
|
167
|
+
# @param params [Array] Parameters for the query
|
|
168
|
+
# @return [Array<Hash>] Query results
|
|
169
|
+
#
|
|
170
|
+
# @example
|
|
171
|
+
# reader.execute_query(
|
|
172
|
+
# "SELECT Name, COUNT(*) as count FROM #{reader.table_name}
|
|
173
|
+
# GROUP BY Name"
|
|
174
|
+
# )
|
|
175
|
+
def execute_query(sql, params = [])
|
|
176
|
+
@database.execute(sql, params)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Check if any records match the given conditions
|
|
180
|
+
#
|
|
181
|
+
# @param conditions [String] The WHERE clause
|
|
182
|
+
# (without the WHERE keyword)
|
|
183
|
+
# @param values [Array] Values for parameterized query placeholders
|
|
184
|
+
# @return [Boolean] true if at least one record matches
|
|
185
|
+
#
|
|
186
|
+
# @example
|
|
187
|
+
# reader.exists?("Name = ?", "MyClass")
|
|
188
|
+
# # => true or false
|
|
189
|
+
def exists?(conditions, *values)
|
|
190
|
+
count_where(conditions, *values).positive?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Read records with custom column selection
|
|
194
|
+
#
|
|
195
|
+
# @param columns [Array<String>] Column names to select
|
|
196
|
+
# @param conditions [String, nil] Optional WHERE clause
|
|
197
|
+
# @param values [Array] Values for parameterized query placeholders
|
|
198
|
+
# @param limit [Integer, nil] Maximum number of records to return
|
|
199
|
+
# @return [Array<Hash>] Array of record hashes with selected columns
|
|
200
|
+
#
|
|
201
|
+
# @example
|
|
202
|
+
# reader.select(["Object_ID", "Name"], "Object_Type = ?", "Class")
|
|
203
|
+
# # => [{"Object_ID"=>1, "Name"=>"Class1"}, ...]
|
|
204
|
+
def select(columns, conditions = nil, *values, limit: nil) # rubocop:disable Metrics/MethodLength
|
|
205
|
+
column_list = columns.join(", ")
|
|
206
|
+
query = "SELECT #{column_list} FROM #{@table_name}"
|
|
207
|
+
|
|
208
|
+
params = []
|
|
209
|
+
if conditions
|
|
210
|
+
query += " WHERE #{conditions}"
|
|
211
|
+
params = values.flatten
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
if limit
|
|
215
|
+
query += " LIMIT ?"
|
|
216
|
+
params << limit
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
@database.execute(query, params)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Infrastructure
|
|
6
|
+
autoload :DatabaseConnection,
|
|
7
|
+
"ea/qea/infrastructure/database_connection"
|
|
8
|
+
autoload :SchemaReader, "ea/qea/infrastructure/schema_reader"
|
|
9
|
+
autoload :TableReader, "ea/qea/infrastructure/table_reader"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Qea
|
|
7
|
+
module Models
|
|
8
|
+
class BaseModel < Lutaml::Model::Serializable
|
|
9
|
+
def self.primary_key_column
|
|
10
|
+
raise NotImplementedError,
|
|
11
|
+
"#{self} must implement .primary_key_column"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.table_name
|
|
15
|
+
raise NotImplementedError,
|
|
16
|
+
"#{self} must implement .table_name"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def primary_key
|
|
20
|
+
public_send(self.class.primary_key_column)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Stable sort key used by transformers that need to emit children in
|
|
24
|
+
# EA's tree-position order. Subclasses with a different field name
|
|
25
|
+
# (e.g., `pos` on t_attribute rows) override this.
|
|
26
|
+
# @return [Integer]
|
|
27
|
+
def sort_position
|
|
28
|
+
0
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Map of EA database column names to Ruby attribute names.
|
|
32
|
+
# Subclasses override this for non-trivial mappings.
|
|
33
|
+
# @return [Hash<String, Symbol>]
|
|
34
|
+
def self.column_map
|
|
35
|
+
{}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Create instance from database row hash.
|
|
39
|
+
# Uses column_map for explicit mappings, falls back to lowercase.
|
|
40
|
+
# @param row [Hash] database row with string keys
|
|
41
|
+
# @return [BaseModel, nil] new instance or nil
|
|
42
|
+
def self.from_db_row(row)
|
|
43
|
+
return nil if row.nil?
|
|
44
|
+
|
|
45
|
+
mapping = column_map
|
|
46
|
+
attrs = row.transform_keys do |key|
|
|
47
|
+
if mapping.key?(key)
|
|
48
|
+
mapping[key]
|
|
49
|
+
else
|
|
50
|
+
key.to_s.downcase.to_sym
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
new(attrs)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Models
|
|
6
|
+
# Represents an attribute from the t_attribute table in EA database
|
|
7
|
+
# This represents class attributes/properties
|
|
8
|
+
class EaAttribute < BaseModel
|
|
9
|
+
attribute :ea_object_id, Lutaml::Model::Type::Integer
|
|
10
|
+
attribute :name, Lutaml::Model::Type::String
|
|
11
|
+
attribute :scope, Lutaml::Model::Type::String
|
|
12
|
+
attribute :stereotype, Lutaml::Model::Type::String
|
|
13
|
+
attribute :containment, Lutaml::Model::Type::String
|
|
14
|
+
attribute :isstatic, Lutaml::Model::Type::Integer
|
|
15
|
+
attribute :iscollection, Lutaml::Model::Type::Integer
|
|
16
|
+
attribute :isordered, Lutaml::Model::Type::Integer
|
|
17
|
+
attribute :allowduplicates, Lutaml::Model::Type::Integer
|
|
18
|
+
attribute :lowerbound, Lutaml::Model::Type::String
|
|
19
|
+
attribute :upperbound, Lutaml::Model::Type::String
|
|
20
|
+
attribute :container, Lutaml::Model::Type::String
|
|
21
|
+
attribute :notes, Lutaml::Model::Type::String
|
|
22
|
+
attribute :derived, Lutaml::Model::Type::String
|
|
23
|
+
attribute :id, Lutaml::Model::Type::Integer
|
|
24
|
+
attribute :pos, Lutaml::Model::Type::Integer
|
|
25
|
+
attribute :genoption, Lutaml::Model::Type::String
|
|
26
|
+
attribute :length, Lutaml::Model::Type::Integer
|
|
27
|
+
attribute :precision, Lutaml::Model::Type::Integer
|
|
28
|
+
attribute :scale, Lutaml::Model::Type::Integer
|
|
29
|
+
attribute :const, Lutaml::Model::Type::Integer
|
|
30
|
+
attribute :style, Lutaml::Model::Type::String
|
|
31
|
+
attribute :classifier, Lutaml::Model::Type::String
|
|
32
|
+
attribute :default, Lutaml::Model::Type::String
|
|
33
|
+
attribute :type, Lutaml::Model::Type::String
|
|
34
|
+
attribute :ea_guid, Lutaml::Model::Type::String
|
|
35
|
+
attribute :styleex, Lutaml::Model::Type::String
|
|
36
|
+
|
|
37
|
+
def self.primary_key_column
|
|
38
|
+
:id
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.table_name
|
|
42
|
+
"t_attribute"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
COLUMN_MAP = {
|
|
47
|
+
"Object_ID" => :ea_object_id,
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
def self.column_map
|
|
51
|
+
COLUMN_MAP
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if attribute is static
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def static?
|
|
57
|
+
isstatic == 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check if attribute is a collection
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def collection?
|
|
63
|
+
iscollection == 1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Check if attribute is ordered
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
def ordered?
|
|
69
|
+
isordered == 1
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check if attribute allows duplicates
|
|
73
|
+
# @return [Boolean]
|
|
74
|
+
def allow_duplicates?
|
|
75
|
+
allowduplicates == 1
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if attribute is constant
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def constant?
|
|
81
|
+
const == 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Check if attribute is public
|
|
85
|
+
# @return [Boolean]
|
|
86
|
+
def public?
|
|
87
|
+
scope&.downcase == "public"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Check if attribute is private
|
|
91
|
+
# @return [Boolean]
|
|
92
|
+
def private?
|
|
93
|
+
scope&.downcase == "private"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Check if attribute is protected
|
|
97
|
+
# @return [Boolean]
|
|
98
|
+
def protected?
|
|
99
|
+
scope&.downcase == "protected"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [Integer] pos for attribute ordering within parent
|
|
103
|
+
def sort_position
|
|
104
|
+
pos || 0
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Models
|
|
6
|
+
# EA Attribute Tag model
|
|
7
|
+
#
|
|
8
|
+
# Represents attribute-level tags (custom key-value metadata)
|
|
9
|
+
# in the t_attributetag table. These are metadata properties
|
|
10
|
+
# specific to GML/XML Schema encoding for attributes.
|
|
11
|
+
#
|
|
12
|
+
# @example Create from database row
|
|
13
|
+
# row = {
|
|
14
|
+
# "PropertyID" => 1,
|
|
15
|
+
# "ElementID" => 367,
|
|
16
|
+
# "Property" => "isMetadata",
|
|
17
|
+
# "VALUE" => "false",
|
|
18
|
+
# "NOTES" => nil,
|
|
19
|
+
# "ea_guid" => "{GUID}"
|
|
20
|
+
# }
|
|
21
|
+
# tag = EaAttributeTag.from_db_row(row)
|
|
22
|
+
class EaAttributeTag < BaseModel
|
|
23
|
+
attribute :property_id, :integer
|
|
24
|
+
attribute :element_id, :integer
|
|
25
|
+
attribute :property, :string
|
|
26
|
+
attribute :value, :string
|
|
27
|
+
attribute :notes, :string
|
|
28
|
+
attribute :ea_guid, :string
|
|
29
|
+
|
|
30
|
+
# @return [Symbol] Primary key column name
|
|
31
|
+
def self.primary_key_column
|
|
32
|
+
:property_id
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [String] Database table name
|
|
36
|
+
def self.table_name
|
|
37
|
+
"t_attributetag"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
COLUMN_MAP = {
|
|
42
|
+
"PropertyID" => :property_id,
|
|
43
|
+
"ElementID" => :element_id,
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
def self.column_map
|
|
47
|
+
COLUMN_MAP
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get property name
|
|
51
|
+
#
|
|
52
|
+
# @return [String, nil] Property name
|
|
53
|
+
def name
|
|
54
|
+
property
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get property value as string
|
|
58
|
+
#
|
|
59
|
+
# @return [String, nil] Property value
|
|
60
|
+
def property_value
|
|
61
|
+
value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Parse boolean value
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean, nil] Boolean value if parseable, nil otherwise
|
|
67
|
+
def boolean_value
|
|
68
|
+
return nil if value.nil?
|
|
69
|
+
|
|
70
|
+
case value.downcase
|
|
71
|
+
when "true", "1", "yes"
|
|
72
|
+
true
|
|
73
|
+
when "false", "0", "no"
|
|
74
|
+
false
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if property is boolean type
|
|
79
|
+
#
|
|
80
|
+
# @return [Boolean] true if value is boolean
|
|
81
|
+
def boolean?
|
|
82
|
+
!boolean_value.nil?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Parse integer value
|
|
86
|
+
#
|
|
87
|
+
# @return [Integer, nil] Integer value if parseable, nil otherwise
|
|
88
|
+
def integer_value
|
|
89
|
+
return nil if value.nil?
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
Integer(value)
|
|
93
|
+
rescue StandardError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|