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.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. 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