expressir 2.1.30 → 2.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +98 -0
- data/.github/workflows/links.yml +100 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.github/workflows/validate_schemas.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +244 -39
- data/Gemfile +2 -1
- data/README.adoc +621 -54
- data/docs/Gemfile +12 -0
- data/docs/_config.yml +141 -0
- data/docs/_guides/changes/changes-format.adoc +778 -0
- data/docs/_guides/changes/importing-eengine.adoc +898 -0
- data/docs/_guides/changes/index.adoc +396 -0
- data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
- data/docs/_guides/changes/validating-changes.adoc +681 -0
- data/docs/_guides/cli/benchmark-performance.adoc +834 -0
- data/docs/_guides/cli/coverage-analysis.adoc +921 -0
- data/docs/_guides/cli/format-schemas.adoc +547 -0
- data/docs/_guides/cli/index.adoc +8 -0
- data/docs/_guides/cli/managing-changes.adoc +927 -0
- data/docs/_guides/cli/validate-ascii.adoc +645 -0
- data/docs/_guides/cli/validate-schemas.adoc +534 -0
- data/docs/_guides/index.adoc +165 -0
- data/docs/_guides/ler/creating-packages.adoc +664 -0
- data/docs/_guides/ler/index.adoc +305 -0
- data/docs/_guides/ler/loading-packages.adoc +707 -0
- data/docs/_guides/ler/package-formats.adoc +748 -0
- data/docs/_guides/ler/querying-packages.adoc +826 -0
- data/docs/_guides/ler/validating-packages.adoc +750 -0
- data/docs/_guides/liquid/basic-templates.adoc +813 -0
- data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
- data/docs/_guides/liquid/drops-reference.adoc +829 -0
- data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
- data/docs/_guides/liquid/index.adoc +468 -0
- data/docs/_guides/manifests/creating-manifests.adoc +483 -0
- data/docs/_guides/manifests/index.adoc +307 -0
- data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
- data/docs/_guides/manifests/validating-manifests.adoc +713 -0
- data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
- data/docs/_guides/ruby-api/index.adoc +257 -0
- data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
- data/docs/_guides/ruby-api/search-engine.adoc +609 -0
- data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
- data/docs/_pages/data-model.adoc +665 -0
- data/docs/_pages/express-language.adoc +506 -0
- data/docs/_pages/getting-started.adoc +414 -0
- data/docs/_pages/index.adoc +116 -0
- data/docs/_pages/introduction.adoc +256 -0
- data/docs/_pages/ler-packages.adoc +837 -0
- data/docs/_pages/parsers.adoc +683 -0
- data/docs/_pages/schema-manifests.adoc +431 -0
- data/docs/_references/index.adoc +228 -0
- data/docs/_tutorials/creating-ler-package.adoc +735 -0
- data/docs/_tutorials/documentation-coverage.adoc +795 -0
- data/docs/_tutorials/index.adoc +221 -0
- data/docs/_tutorials/liquid-templates.adoc +806 -0
- data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
- data/docs/_tutorials/querying-schemas.adoc +751 -0
- data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
- data/docs/index.adoc +242 -0
- data/docs/lychee.toml +84 -0
- data/examples/demo_ler_usage.sh +86 -0
- data/examples/ler/README.md +111 -0
- data/examples/ler/simple_example.ler +0 -0
- data/examples/ler/simple_schema.exp +33 -0
- data/examples/ler_build.rb +75 -0
- data/examples/ler_cli.rb +79 -0
- data/examples/ler_demo_complete.rb +276 -0
- data/examples/ler_query.rb +91 -0
- data/examples/ler_query_examples.rb +305 -0
- data/examples/ler_stats.rb +81 -0
- data/examples/phase3_demo.rb +159 -0
- data/examples/query_demo_simple.rb +131 -0
- data/expressir.gemspec +2 -0
- data/lib/expressir/cli.rb +12 -4
- data/lib/expressir/commands/manifest.rb +427 -0
- data/lib/expressir/commands/package.rb +1274 -0
- data/lib/expressir/commands/validate.rb +70 -37
- data/lib/expressir/commands/validate_ascii.rb +607 -0
- data/lib/expressir/commands/validate_load.rb +88 -0
- data/lib/expressir/express/formatter.rb +5 -1
- data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
- data/lib/expressir/express/parser.rb +33 -0
- data/lib/expressir/manifest/resolver.rb +213 -0
- data/lib/expressir/manifest/validator.rb +195 -0
- data/lib/expressir/model/declarations/entity.rb +6 -0
- data/lib/expressir/model/dependency_resolver.rb +270 -0
- data/lib/expressir/model/indexes/entity_index.rb +103 -0
- data/lib/expressir/model/indexes/reference_index.rb +148 -0
- data/lib/expressir/model/indexes/type_index.rb +149 -0
- data/lib/expressir/model/interface_validator.rb +384 -0
- data/lib/expressir/model/repository.rb +400 -5
- data/lib/expressir/model/repository_validator.rb +295 -0
- data/lib/expressir/model/search_engine.rb +525 -0
- data/lib/expressir/model.rb +4 -94
- data/lib/expressir/package/builder.rb +200 -0
- data/lib/expressir/package/metadata.rb +81 -0
- data/lib/expressir/package/reader.rb +165 -0
- data/lib/expressir/schema_manifest.rb +11 -1
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +15 -2
- metadata +114 -4
- data/docs/benchmarking.adoc +0 -107
- data/docs/liquid_drops.adoc +0 -1547
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Querying Schemas
|
|
3
|
+
nav_order: 5
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
== Querying Schemas
|
|
7
|
+
|
|
8
|
+
=== Prerequisites
|
|
9
|
+
|
|
10
|
+
Before starting this tutorial, ensure you have:
|
|
11
|
+
|
|
12
|
+
* Completed link:creating-ler-package.html[Creating LER Packages]
|
|
13
|
+
* Understanding of EXPRESS entities and types
|
|
14
|
+
* A LER package or parsed repository to query
|
|
15
|
+
* Basic Ruby knowledge for API usage
|
|
16
|
+
|
|
17
|
+
=== Learning Objectives
|
|
18
|
+
|
|
19
|
+
By the end of this tutorial, you will be able to:
|
|
20
|
+
|
|
21
|
+
* Use the SearchEngine API to find entities and types
|
|
22
|
+
* Apply pattern matching with wildcards and regex
|
|
23
|
+
* Filter results by schema, type, and category
|
|
24
|
+
* Build complex queries with multiple criteria
|
|
25
|
+
* Extract and analyze schema statistics
|
|
26
|
+
* Find relationships between elements
|
|
27
|
+
|
|
28
|
+
=== What You'll Build
|
|
29
|
+
|
|
30
|
+
You'll create various query utilities to explore and analyze EXPRESS schemas, from simple searches to complex relationship analysis.
|
|
31
|
+
|
|
32
|
+
=== Step 1: Understanding the SearchEngine
|
|
33
|
+
|
|
34
|
+
==== What is SearchEngine?
|
|
35
|
+
|
|
36
|
+
The [`SearchEngine`](../../lib/expressir/model/search_engine.rb:1) provides a powerful API for querying repositories:
|
|
37
|
+
|
|
38
|
+
* **Fast lookups**: Pre-built indexes for instant results
|
|
39
|
+
* **Pattern matching**: Wildcards, regex, exact matches
|
|
40
|
+
* **Type filtering**: Search by element type
|
|
41
|
+
* **Schema scoping**: Limit to specific schemas
|
|
42
|
+
* **Category filtering**: Filter types by category
|
|
43
|
+
|
|
44
|
+
==== Element Types
|
|
45
|
+
|
|
46
|
+
SearchEngine can find these element types:
|
|
47
|
+
|
|
48
|
+
* `entity` - Entity declarations
|
|
49
|
+
* `type` - Type declarations
|
|
50
|
+
* `function` - Function declarations
|
|
51
|
+
* `procedure` - Procedure declarations
|
|
52
|
+
* `rule` - Rule declarations
|
|
53
|
+
* `constant` - Constant declarations
|
|
54
|
+
* `attribute` - Entity attributes
|
|
55
|
+
* `derived_attribute` - Derived attributes
|
|
56
|
+
* `inverse_attribute` - Inverse attributes
|
|
57
|
+
* `parameter` - Function/procedure parameters
|
|
58
|
+
* `variable` - Local variables
|
|
59
|
+
* `where_rule` - WHERE rules
|
|
60
|
+
* `unique_rule` - UNIQUE rules
|
|
61
|
+
|
|
62
|
+
=== Step 2: Basic Queries
|
|
63
|
+
|
|
64
|
+
==== Setup
|
|
65
|
+
|
|
66
|
+
Create a sample LER package first, or use an existing one.
|
|
67
|
+
|
|
68
|
+
Create `basic_queries.rb`:
|
|
69
|
+
|
|
70
|
+
[source,ruby]
|
|
71
|
+
----
|
|
72
|
+
require 'expressir'
|
|
73
|
+
|
|
74
|
+
# Load package or parse schemas
|
|
75
|
+
repo = Expressir::Model::Repository.from_package('catalog.ler')
|
|
76
|
+
|
|
77
|
+
# Create search engine
|
|
78
|
+
search = Expressir::Model::SearchEngine.new(repo)
|
|
79
|
+
|
|
80
|
+
puts "Repository loaded:"
|
|
81
|
+
puts " Schemas: #{repo.schemas.size}"
|
|
82
|
+
puts " Ready to query!"
|
|
83
|
+
----
|
|
84
|
+
|
|
85
|
+
==== List All Entities
|
|
86
|
+
|
|
87
|
+
[source,ruby]
|
|
88
|
+
----
|
|
89
|
+
# List all entities
|
|
90
|
+
entities = search.list(type: 'entity')
|
|
91
|
+
|
|
92
|
+
puts "\nAll entities (#{entities.size}):"
|
|
93
|
+
entities.each do |entity|
|
|
94
|
+
puts " #{entity[:schema]}.#{entity[:id]}"
|
|
95
|
+
end
|
|
96
|
+
----
|
|
97
|
+
|
|
98
|
+
**Output**:
|
|
99
|
+
[source]
|
|
100
|
+
----
|
|
101
|
+
All entities (4):
|
|
102
|
+
base_schema.person
|
|
103
|
+
base_schema.organization
|
|
104
|
+
product_schema.product
|
|
105
|
+
product_schema.product_category
|
|
106
|
+
----
|
|
107
|
+
|
|
108
|
+
==== List All Types
|
|
109
|
+
|
|
110
|
+
[source,ruby]
|
|
111
|
+
----
|
|
112
|
+
# List all types
|
|
113
|
+
types = search.list(type: 'type')
|
|
114
|
+
|
|
115
|
+
puts "\nAll types (#{types.size}):"
|
|
116
|
+
types.each do |type_info|
|
|
117
|
+
category = type_info[:category] || 'simple'
|
|
118
|
+
puts " #{type_info[:schema]}.#{type_info[:id]} [#{category}]"
|
|
119
|
+
end
|
|
120
|
+
----
|
|
121
|
+
|
|
122
|
+
**Output**:
|
|
123
|
+
[source]
|
|
124
|
+
----
|
|
125
|
+
All types (2):
|
|
126
|
+
base_schema.identifier [simple]
|
|
127
|
+
base_schema.label [simple]
|
|
128
|
+
----
|
|
129
|
+
|
|
130
|
+
==== Count Elements
|
|
131
|
+
|
|
132
|
+
[source,ruby]
|
|
133
|
+
----
|
|
134
|
+
# Count different element types
|
|
135
|
+
element_types = ['entity', 'type', 'function', 'constant']
|
|
136
|
+
|
|
137
|
+
puts "\nElement counts:"
|
|
138
|
+
element_types.each do |elem_type|
|
|
139
|
+
count = search.count(type: elem_type)
|
|
140
|
+
puts " #{elem_type}: #{count}"
|
|
141
|
+
end
|
|
142
|
+
----
|
|
143
|
+
|
|
144
|
+
=== Step 3: Pattern Matching
|
|
145
|
+
|
|
146
|
+
==== Simple Name Search
|
|
147
|
+
|
|
148
|
+
[source,ruby]
|
|
149
|
+
----
|
|
150
|
+
# Search by name (case-insensitive by default)
|
|
151
|
+
results = search.search(pattern: 'product')
|
|
152
|
+
|
|
153
|
+
puts "\nMatching 'product':"
|
|
154
|
+
results.each do |result|
|
|
155
|
+
puts " #{result[:type]}: #{result[:path]}"
|
|
156
|
+
end
|
|
157
|
+
----
|
|
158
|
+
|
|
159
|
+
**Output**:
|
|
160
|
+
[source]
|
|
161
|
+
----
|
|
162
|
+
Matching 'product':
|
|
163
|
+
entity: product_schema.product
|
|
164
|
+
entity: product_schema.product_category
|
|
165
|
+
----
|
|
166
|
+
|
|
167
|
+
==== Wildcard Patterns
|
|
168
|
+
|
|
169
|
+
[source,ruby]
|
|
170
|
+
----
|
|
171
|
+
# Prefix matching
|
|
172
|
+
results = search.search(pattern: 'product*', type: 'entity')
|
|
173
|
+
puts "\nEntities starting with 'product':"
|
|
174
|
+
results.each { |r| puts " #{r[:path]}" }
|
|
175
|
+
|
|
176
|
+
# Suffix matching
|
|
177
|
+
results = search.search(pattern: '*_category', type: 'entity')
|
|
178
|
+
puts "\nEntities ending with '_category':"
|
|
179
|
+
results.each { |r| puts " #{r[:path]}" }
|
|
180
|
+
|
|
181
|
+
# Contains
|
|
182
|
+
results = search.search(pattern: '*org*', type: 'entity')
|
|
183
|
+
puts "\nEntities containing 'org':"
|
|
184
|
+
results.each { |r| puts " #{r[:path]}" }
|
|
185
|
+
----
|
|
186
|
+
|
|
187
|
+
==== Qualified Name Search
|
|
188
|
+
|
|
189
|
+
[source,ruby]
|
|
190
|
+
----
|
|
191
|
+
# Search in specific schema
|
|
192
|
+
results = search.search(pattern: 'product_schema.product')
|
|
193
|
+
puts "\nIn product_schema:"
|
|
194
|
+
results.each { |r| puts " #{r[:id]}" }
|
|
195
|
+
|
|
196
|
+
# Wildcard schema
|
|
197
|
+
results = search.search(pattern: '*.product', type: 'entity')
|
|
198
|
+
puts "\nEntity named 'product' in any schema:"
|
|
199
|
+
results.each { |r| puts " #{r[:schema]}.#{r[:id]}" }
|
|
200
|
+
|
|
201
|
+
# Schema wildcard
|
|
202
|
+
results = search.search(pattern: 'product_schema.*', type: 'entity')
|
|
203
|
+
puts "\nAll entities in product_schema:"
|
|
204
|
+
results.each { |r| puts " #{r[:id]}" }
|
|
205
|
+
----
|
|
206
|
+
|
|
207
|
+
==== Regex Patterns
|
|
208
|
+
|
|
209
|
+
[source,ruby]
|
|
210
|
+
----
|
|
211
|
+
# Regex search
|
|
212
|
+
results = search.search(
|
|
213
|
+
pattern: '^product_[a-z]+$',
|
|
214
|
+
type: 'entity',
|
|
215
|
+
regex: true
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
puts "\nRegex match '^product_[a-z]+$':"
|
|
219
|
+
results.each { |r| puts " #{r[:id]}" }
|
|
220
|
+
|
|
221
|
+
# Complex regex
|
|
222
|
+
results = search.search(
|
|
223
|
+
pattern: '(person|organization)',
|
|
224
|
+
regex: true
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
puts "\nMatching 'person' or 'organization':"
|
|
228
|
+
results.each { |r| puts " #{r[:type]}: #{r[:id]}" }
|
|
229
|
+
----
|
|
230
|
+
|
|
231
|
+
=== Step 4: Filtering Results
|
|
232
|
+
|
|
233
|
+
==== Filter by Type
|
|
234
|
+
|
|
235
|
+
[source,ruby]
|
|
236
|
+
----
|
|
237
|
+
# Only entities
|
|
238
|
+
entities = search.search(pattern: '*', type: 'entity')
|
|
239
|
+
puts "Entities: #{entities.size}"
|
|
240
|
+
|
|
241
|
+
# Only types
|
|
242
|
+
types = search.search(pattern: '*', type: 'type')
|
|
243
|
+
puts "Types: #{types.size}"
|
|
244
|
+
|
|
245
|
+
# Multiple queries
|
|
246
|
+
['entity', 'type', 'function'].each do |elem_type|
|
|
247
|
+
count = search.count(type: elem_type)
|
|
248
|
+
puts "#{elem_type}: #{count}"
|
|
249
|
+
end
|
|
250
|
+
----
|
|
251
|
+
|
|
252
|
+
==== Filter by Schema
|
|
253
|
+
|
|
254
|
+
[source,ruby]
|
|
255
|
+
----
|
|
256
|
+
# Entities in specific schema
|
|
257
|
+
results = search.list(type: 'entity', schema: 'base_schema')
|
|
258
|
+
|
|
259
|
+
puts "\nEntities in base_schema:"
|
|
260
|
+
results.each do |entity|
|
|
261
|
+
puts " #{entity[:id]}"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Search within schema
|
|
265
|
+
results = search.search(
|
|
266
|
+
pattern: 'p*',
|
|
267
|
+
type: 'entity',
|
|
268
|
+
schema: 'base_schema'
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
puts "\nbase_schema entities starting with 'p':"
|
|
272
|
+
results.each { |r| puts " #{r[:id]}" }
|
|
273
|
+
----
|
|
274
|
+
|
|
275
|
+
==== Filter by Category
|
|
276
|
+
|
|
277
|
+
[source,ruby]
|
|
278
|
+
----
|
|
279
|
+
# Find SELECT types
|
|
280
|
+
select_types = search.list(type: 'type', category: 'select')
|
|
281
|
+
puts "\nSELECT types: #{select_types.size}"
|
|
282
|
+
|
|
283
|
+
# Find ENUMERATION types
|
|
284
|
+
enum_types = search.list(type: 'type', category: 'enumeration')
|
|
285
|
+
puts "ENUMERATION types: #{enum_types.size}"
|
|
286
|
+
|
|
287
|
+
# Categories available
|
|
288
|
+
categories = ['select', 'enumeration', 'aggregate', 'simple']
|
|
289
|
+
categories.each do |cat|
|
|
290
|
+
count = search.count(type: 'type', category: cat)
|
|
291
|
+
puts " #{cat}: #{count}" if count > 0
|
|
292
|
+
end
|
|
293
|
+
----
|
|
294
|
+
|
|
295
|
+
=== Step 5: Advanced Queries
|
|
296
|
+
|
|
297
|
+
==== Find Attributes
|
|
298
|
+
|
|
299
|
+
[source,ruby]
|
|
300
|
+
----
|
|
301
|
+
# All attributes
|
|
302
|
+
attributes = search.list(type: 'attribute')
|
|
303
|
+
puts "\nTotal attributes: #{attributes.size}"
|
|
304
|
+
|
|
305
|
+
# Attributes in specific entity
|
|
306
|
+
results = search.search(
|
|
307
|
+
pattern: 'product_schema.product.*',
|
|
308
|
+
type: 'attribute'
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
puts "\nAttributes of 'product' entity:"
|
|
312
|
+
results.each do |attr|
|
|
313
|
+
puts " #{attr[:id]}"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Attributes named 'name'
|
|
317
|
+
results = search.search(
|
|
318
|
+
pattern: '*.*name',
|
|
319
|
+
type: 'attribute'
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
puts "\nAttributes named 'name':"
|
|
323
|
+
results.each { |r| puts " #{r[:path]}" }
|
|
324
|
+
----
|
|
325
|
+
|
|
326
|
+
==== Find Functions and Parameters
|
|
327
|
+
|
|
328
|
+
[source,ruby]
|
|
329
|
+
----
|
|
330
|
+
# All functions
|
|
331
|
+
functions = search.list(type: 'function')
|
|
332
|
+
puts "\nFunctions: #{functions.size}"
|
|
333
|
+
functions.each { |f| puts " #{f[:schema]}.#{f[:id]}" }
|
|
334
|
+
|
|
335
|
+
# Function parameters
|
|
336
|
+
if functions.any?
|
|
337
|
+
func = functions.first[:object]
|
|
338
|
+
puts "\nParameters of #{functions.first[:id]}:"
|
|
339
|
+
|
|
340
|
+
params = search.search(
|
|
341
|
+
pattern: "#{functions.first[:schema]}.#{functions.first[:id]}.*",
|
|
342
|
+
type: 'parameter'
|
|
343
|
+
)
|
|
344
|
+
params.each { |p| puts " #{p[:id]}" }
|
|
345
|
+
end
|
|
346
|
+
----
|
|
347
|
+
|
|
348
|
+
==== Complex Combined Queries
|
|
349
|
+
|
|
350
|
+
Create `complex_queries.rb`:
|
|
351
|
+
|
|
352
|
+
[source,ruby]
|
|
353
|
+
----
|
|
354
|
+
require 'expressir'
|
|
355
|
+
|
|
356
|
+
repo = Expressir::Model::Repository.from_package('catalog.ler')
|
|
357
|
+
search = Expressir::Model::SearchEngine.new(repo)
|
|
358
|
+
|
|
359
|
+
# Query 1: Find all assignment-related entities
|
|
360
|
+
puts "Assignment entities:"
|
|
361
|
+
results = search.search(
|
|
362
|
+
pattern: '*assignment*',
|
|
363
|
+
type: 'entity'
|
|
364
|
+
)
|
|
365
|
+
results.each { |r| puts " #{r[:path]}" }
|
|
366
|
+
|
|
367
|
+
# Query 2: SELECT types starting with 'action'
|
|
368
|
+
puts "\nSELECT types starting with 'action':"
|
|
369
|
+
results = search.search(
|
|
370
|
+
pattern: 'action*',
|
|
371
|
+
type: 'type',
|
|
372
|
+
category: 'select'
|
|
373
|
+
)
|
|
374
|
+
results.each { |r| puts " #{r[:path]}" }
|
|
375
|
+
|
|
376
|
+
# Query 3: Entities in specific schema matching pattern
|
|
377
|
+
puts "\nMatching pattern in schema:"
|
|
378
|
+
results = search.search(
|
|
379
|
+
pattern: '*product*',
|
|
380
|
+
type: 'entity',
|
|
381
|
+
schema: 'product_schema'
|
|
382
|
+
)
|
|
383
|
+
results.each { |r| puts " #{r[:id]}" }
|
|
384
|
+
|
|
385
|
+
# Query 4: Case-sensitive exact match
|
|
386
|
+
results = search.search(
|
|
387
|
+
pattern: 'Product',
|
|
388
|
+
type: 'entity',
|
|
389
|
+
case_sensitive: true,
|
|
390
|
+
exact: true
|
|
391
|
+
)
|
|
392
|
+
puts "\nCase-sensitive 'Product': #{results.size} results"
|
|
393
|
+
----
|
|
394
|
+
|
|
395
|
+
=== Step 6: Extracting Full Objects
|
|
396
|
+
|
|
397
|
+
==== Get Entity Details
|
|
398
|
+
|
|
399
|
+
[source,ruby]
|
|
400
|
+
----
|
|
401
|
+
# Search and get full entity object
|
|
402
|
+
results = search.search(pattern: 'product', type: 'entity', exact: true)
|
|
403
|
+
|
|
404
|
+
if results.any?
|
|
405
|
+
entity = results.first[:object]
|
|
406
|
+
|
|
407
|
+
puts "\nEntity: #{entity.id}"
|
|
408
|
+
puts "Attributes:"
|
|
409
|
+
entity.attributes.each do |attr|
|
|
410
|
+
optional = attr.optional ? " (optional)" : ""
|
|
411
|
+
puts " #{attr.id}: #{attr.type}#{optional}"
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
if entity.subtype_of && !entity.subtype_of.empty?
|
|
415
|
+
puts "Supertypes:"
|
|
416
|
+
entity.subtype_of.each { |st| puts " - #{st}" }
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
----
|
|
420
|
+
|
|
421
|
+
==== Analyze Type Details
|
|
422
|
+
|
|
423
|
+
[source,ruby]
|
|
424
|
+
----
|
|
425
|
+
# Get type details
|
|
426
|
+
results = search.search(pattern: 'identifier', type: 'type', exact: true)
|
|
427
|
+
|
|
428
|
+
if results.any?
|
|
429
|
+
type_decl = results.first[:object]
|
|
430
|
+
|
|
431
|
+
puts "\nType: #{type_decl.id}"
|
|
432
|
+
puts "Underlying: #{type_decl.underlying_type.class.name}"
|
|
433
|
+
|
|
434
|
+
if type_decl.where_rules && !type_decl.where_rules.empty?
|
|
435
|
+
puts "WHERE rules:"
|
|
436
|
+
type_decl.where_rules.each do |rule|
|
|
437
|
+
puts " #{rule.id}: #{rule.expression}"
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
----
|
|
442
|
+
|
|
443
|
+
=== Step 7: Relationship Analysis
|
|
444
|
+
|
|
445
|
+
==== Find Entity Dependencies
|
|
446
|
+
|
|
447
|
+
Create `find_dependencies.rb`:
|
|
448
|
+
|
|
449
|
+
[source,ruby]
|
|
450
|
+
----
|
|
451
|
+
require 'expressir'
|
|
452
|
+
|
|
453
|
+
repo = Expressir::Model::Repository.from_package('catalog.ler')
|
|
454
|
+
search = Expressir::Model::SearchEngine.new(repo)
|
|
455
|
+
|
|
456
|
+
def find_entity_dependencies(search, entity_name)
|
|
457
|
+
results = search.search(pattern: entity_name, type: 'entity', exact: true)
|
|
458
|
+
return if results.empty?
|
|
459
|
+
|
|
460
|
+
entity = results.first[:object]
|
|
461
|
+
dependencies = []
|
|
462
|
+
|
|
463
|
+
entity.attributes.each do |attr|
|
|
464
|
+
if attr.type.respond_to?(:ref) && attr.type.ref
|
|
465
|
+
ref = attr.type.ref
|
|
466
|
+
if ref.is_a?(Expressir::Model::Declarations::Entity)
|
|
467
|
+
dependencies << ref.id
|
|
468
|
+
elsif ref.is_a?(Expressir::Model::Declarations::Type)
|
|
469
|
+
dependencies << ref.id
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
dependencies.uniq
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Find dependencies for 'product' entity
|
|
478
|
+
deps = find_entity_dependencies(search, 'product')
|
|
479
|
+
puts "Dependencies of 'product':"
|
|
480
|
+
deps.each { |d| puts " - #{d}" }
|
|
481
|
+
----
|
|
482
|
+
|
|
483
|
+
==== Find All Uses of a Type
|
|
484
|
+
|
|
485
|
+
[source,ruby]
|
|
486
|
+
----
|
|
487
|
+
def find_type_usage(repo, type_name)
|
|
488
|
+
usages = []
|
|
489
|
+
|
|
490
|
+
repo.schemas.each do |schema|
|
|
491
|
+
schema.entities.each do |entity|
|
|
492
|
+
entity.attributes.each do |attr|
|
|
493
|
+
if attr.type.respond_to?(:id) && attr.type.id == type_name
|
|
494
|
+
usages << {
|
|
495
|
+
schema: schema.id,
|
|
496
|
+
entity: entity.id,
|
|
497
|
+
attribute: attr.id
|
|
498
|
+
}
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
usages
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# Find where 'identifier' type is used
|
|
508
|
+
usages = find_type_usage(repo, 'identifier')
|
|
509
|
+
puts "\nUsages of 'identifier' type:"
|
|
510
|
+
usages.each do |usage|
|
|
511
|
+
puts " #{usage[:schema]}.#{usage[:entity]}.#{usage[:attribute]}"
|
|
512
|
+
end
|
|
513
|
+
----
|
|
514
|
+
|
|
515
|
+
=== Step 8: Statistics and Aggregations
|
|
516
|
+
|
|
517
|
+
==== Element Distribution
|
|
518
|
+
|
|
519
|
+
Create `statistics.rb`:
|
|
520
|
+
|
|
521
|
+
[source,ruby]
|
|
522
|
+
----
|
|
523
|
+
require 'expressir'
|
|
524
|
+
|
|
525
|
+
repo = Expressir::Model::Repository.from_package('catalog.ler')
|
|
526
|
+
search = Expressir::Model::SearchEngine.new(repo)
|
|
527
|
+
|
|
528
|
+
puts "Repository Statistics"
|
|
529
|
+
puts "=" * 60
|
|
530
|
+
|
|
531
|
+
# Element counts
|
|
532
|
+
element_types = [
|
|
533
|
+
'entity', 'type', 'function', 'procedure',
|
|
534
|
+
'rule', 'constant', 'attribute'
|
|
535
|
+
]
|
|
536
|
+
|
|
537
|
+
puts "\nElement counts:"
|
|
538
|
+
element_types.each do |elem_type|
|
|
539
|
+
count = search.count(type: elem_type)
|
|
540
|
+
puts " #{elem_type.ljust(20)}: #{count}" if count > 0
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Per-schema breakdown
|
|
544
|
+
puts "\nEntities per schema:"
|
|
545
|
+
repo.schemas.each do |schema|
|
|
546
|
+
count = search.count(type: 'entity', schema: schema.id)
|
|
547
|
+
puts " #{schema.id.ljust(25)}: #{count}"
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Type categories
|
|
551
|
+
puts "\nTypes by category:"
|
|
552
|
+
categories = ['select', 'enumeration', 'aggregate', 'simple']
|
|
553
|
+
categories.each do |cat|
|
|
554
|
+
count = search.count(type: 'type', category: cat)
|
|
555
|
+
puts " #{cat.ljust(20)}: #{count}" if count > 0
|
|
556
|
+
end
|
|
557
|
+
----
|
|
558
|
+
|
|
559
|
+
==== Top Entities by Attribute Count
|
|
560
|
+
|
|
561
|
+
[source,ruby]
|
|
562
|
+
----
|
|
563
|
+
# Find entities with most attributes
|
|
564
|
+
entities = search.list(type: 'entity')
|
|
565
|
+
|
|
566
|
+
entity_attrs = entities.map do |e|
|
|
567
|
+
obj = e[:object]
|
|
568
|
+
{
|
|
569
|
+
path: e[:path],
|
|
570
|
+
count: obj.attributes.size
|
|
571
|
+
}
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
puts "\nTop entities by attribute count:"
|
|
575
|
+
entity_attrs.sort_by { |e| -e[:count] }
|
|
576
|
+
.take(10)
|
|
577
|
+
.each do |e|
|
|
578
|
+
puts " #{e[:path]}: #{e[:count]} attributes"
|
|
579
|
+
end
|
|
580
|
+
----
|
|
581
|
+
|
|
582
|
+
=== Step 9: Building Query Utilities
|
|
583
|
+
|
|
584
|
+
==== Create a Query Helper
|
|
585
|
+
|
|
586
|
+
Create `query_helper.rb`:
|
|
587
|
+
|
|
588
|
+
[source,ruby]
|
|
589
|
+
----
|
|
590
|
+
require 'expressir'
|
|
591
|
+
|
|
592
|
+
class SchemaQuery
|
|
593
|
+
def initialize(package_or_repo)
|
|
594
|
+
if package_or_repo.is_a?(String)
|
|
595
|
+
@repo = Expressir::Model::Repository.from_package(package_or_repo)
|
|
596
|
+
else
|
|
597
|
+
@repo = package_or_repo
|
|
598
|
+
end
|
|
599
|
+
@search = Expressir::Model::SearchEngine.new(@repo)
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def find_entity(name, schema: nil)
|
|
603
|
+
opts = { pattern: name, type: 'entity', exact: true }
|
|
604
|
+
opts[:schema] = schema if schema
|
|
605
|
+
results = @search.search(opts)
|
|
606
|
+
results.first[:object] if results.any?
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def find_entities_using_type(type_name)
|
|
610
|
+
results = []
|
|
611
|
+
@repo.schemas.each do |schema|
|
|
612
|
+
schema.entities.each do |entity|
|
|
613
|
+
entity.attributes.each do |attr|
|
|
614
|
+
if attr.type.respond_to?(:id) && attr.type.id == type_name
|
|
615
|
+
results << entity
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
results.uniq
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
def list_schemas
|
|
624
|
+
@repo.schemas.map(&:id)
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def schema_stats(schema_id)
|
|
628
|
+
{
|
|
629
|
+
entities: @search.count(type: 'entity', schema: schema_id),
|
|
630
|
+
types: @search.count(type: 'type', schema: schema_id),
|
|
631
|
+
functions: @search.count(type: 'function', schema: schema_id)
|
|
632
|
+
}
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
# Usage
|
|
637
|
+
query = SchemaQuery.new('catalog.ler')
|
|
638
|
+
|
|
639
|
+
puts "Schemas: #{query.list_schemas.join(', ')}"
|
|
640
|
+
|
|
641
|
+
entity = query.find_entity('product')
|
|
642
|
+
puts "\nFound entity: #{entity.id}" if entity
|
|
643
|
+
|
|
644
|
+
query.list_schemas.each do |schema|
|
|
645
|
+
stats = query.schema_stats(schema)
|
|
646
|
+
puts "\n#{schema}:"
|
|
647
|
+
puts " Entities: #{stats[:entities]}"
|
|
648
|
+
puts " Types: #{stats[:types]}"
|
|
649
|
+
puts " Functions: #{stats[:functions]}"
|
|
650
|
+
end
|
|
651
|
+
----
|
|
652
|
+
|
|
653
|
+
=== Step 10: Practice Exercises
|
|
654
|
+
|
|
655
|
+
==== Exercise 1: Find Orphan Entities
|
|
656
|
+
|
|
657
|
+
Write a query to find entities that are not referenced by any other entity.
|
|
658
|
+
|
|
659
|
+
Hint: Check all entity attributes to see which entity types are referenced.
|
|
660
|
+
|
|
661
|
+
==== Exercise 2: Circular References
|
|
662
|
+
|
|
663
|
+
Detect if any entities have circular references (entity A references entity B, which references entity A).
|
|
664
|
+
|
|
665
|
+
==== Exercise 3: Schema Dependency Graph
|
|
666
|
+
|
|
667
|
+
Create a visualization showing which schemas depend on which other schemas through interfaces.
|
|
668
|
+
|
|
669
|
+
==== Exercise 4: Entity Complexity Score
|
|
670
|
+
|
|
671
|
+
Calculate a complexity score for each entity based on:
|
|
672
|
+
* Number of attributes (1 point each)
|
|
673
|
+
* Number of supertypes (2 points each)
|
|
674
|
+
* Number of WHERE rules (3 points each)
|
|
675
|
+
|
|
676
|
+
Find the 5 most complex entities.
|
|
677
|
+
|
|
678
|
+
=== Common Patterns
|
|
679
|
+
|
|
680
|
+
==== Safe Object Access
|
|
681
|
+
|
|
682
|
+
[source,ruby]
|
|
683
|
+
----
|
|
684
|
+
# Always check if results exist
|
|
685
|
+
results = search.search(pattern: 'foo', type: 'entity')
|
|
686
|
+
|
|
687
|
+
if results.any?
|
|
688
|
+
entity = results.first[:object]
|
|
689
|
+
# Use entity safely
|
|
690
|
+
else
|
|
691
|
+
puts "Entity not found"
|
|
692
|
+
end
|
|
693
|
+
----
|
|
694
|
+
|
|
695
|
+
==== Handling Multiple Results
|
|
696
|
+
|
|
697
|
+
[source,ruby]
|
|
698
|
+
----
|
|
699
|
+
results = search.search(pattern: 'product*', type: 'entity')
|
|
700
|
+
|
|
701
|
+
case results.size
|
|
702
|
+
when 0
|
|
703
|
+
puts "No matches found"
|
|
704
|
+
when 1
|
|
705
|
+
puts "Found: #{results.first[:path]}"
|
|
706
|
+
else
|
|
707
|
+
puts "Multiple matches (#{results.size}):"
|
|
708
|
+
results.each { |r| puts " - #{r[:path]}" }
|
|
709
|
+
end
|
|
710
|
+
----
|
|
711
|
+
|
|
712
|
+
==== Case-Insensitive by Default
|
|
713
|
+
|
|
714
|
+
[source,ruby]
|
|
715
|
+
----
|
|
716
|
+
# Case-insensitive (default)
|
|
717
|
+
results = search.search(pattern: 'PRODUCT') # Finds 'product'
|
|
718
|
+
|
|
719
|
+
# Case-sensitive
|
|
720
|
+
results = search.search(pattern: 'PRODUCT', case_sensitive: true) # No match
|
|
721
|
+
----
|
|
722
|
+
|
|
723
|
+
=== Next Steps
|
|
724
|
+
|
|
725
|
+
Congratulations! You now know how to query EXPRESS schemas effectively.
|
|
726
|
+
|
|
727
|
+
**Continue learning**:
|
|
728
|
+
|
|
729
|
+
* link:liquid-templates.html[Liquid Templates] - Generate documentation from queries
|
|
730
|
+
* link:documentation-coverage.html[Documentation Coverage] - Analyze documentation
|
|
731
|
+
* link:../guides/ruby-api/[Ruby API Guides] - Advanced programmatic usage
|
|
732
|
+
|
|
733
|
+
**Read more**:
|
|
734
|
+
|
|
735
|
+
* link:../pages/data-model.html[Data Model] - Understanding the structure
|
|
736
|
+
* link:../references/[API References] - Complete API documentation
|
|
737
|
+
|
|
738
|
+
=== Summary
|
|
739
|
+
|
|
740
|
+
In this tutorial, you learned to:
|
|
741
|
+
|
|
742
|
+
* ✅ Use SearchEngine for fast queries
|
|
743
|
+
* ✅ Apply pattern matching with wildcards and regex
|
|
744
|
+
* ✅ Filter by type, schema, and category
|
|
745
|
+
* ✅ Build complex combined queries
|
|
746
|
+
* ✅ Extract and analyze full objects
|
|
747
|
+
* ✅ Find relationships and dependencies
|
|
748
|
+
* ✅ Generate statistics and aggregations
|
|
749
|
+
* ✅ Create reusable query utilities
|
|
750
|
+
|
|
751
|
+
You can now effectively explore and analyze any EXPRESS schema!
|