modelable 1.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of modelable might be problematic. Click here for more details.
- modelable/__init__.py +1 -0
- modelable/__main__.py +3 -0
- modelable/_pydantic_py314_compat.py +31 -0
- modelable/cli.py +41 -0
- modelable/commands/__init__.py +1 -0
- modelable/commands/apicurio.py +84 -0
- modelable/commands/codegen.py +241 -0
- modelable/commands/common.py +43 -0
- modelable/commands/compile.py +237 -0
- modelable/commands/create.py +164 -0
- modelable/commands/diff.py +82 -0
- modelable/commands/graph.py +53 -0
- modelable/commands/llm.py +564 -0
- modelable/commands/lsp.py +15 -0
- modelable/commands/runtime.py +37 -0
- modelable/commands/scenario.py +104 -0
- modelable/commands/spec.py +197 -0
- modelable/commands/workspace.py +240 -0
- modelable/compat/__init__.py +11 -0
- modelable/compat/checker.py +179 -0
- modelable/compat/diff.py +169 -0
- modelable/compiler/__init__.py +3 -0
- modelable/compiler/compiler.py +19 -0
- modelable/compiler/workspace.py +346 -0
- modelable/diagnostics/__init__.py +3 -0
- modelable/diagnostics/model.py +27 -0
- modelable/emitters/__init__.py +0 -0
- modelable/emitters/base.py +22 -0
- modelable/emitters/csharp.py +245 -0
- modelable/emitters/dbt_yaml.py +290 -0
- modelable/emitters/diagnostics.py +25 -0
- modelable/emitters/fhir.py +694 -0
- modelable/emitters/fhir_validator.py +36 -0
- modelable/emitters/go.py +334 -0
- modelable/emitters/java.py +264 -0
- modelable/emitters/json_schema.py +458 -0
- modelable/emitters/markdown.py +252 -0
- modelable/emitters/odcs.py +355 -0
- modelable/emitters/openlineage.py +315 -0
- modelable/emitters/openmetadata.py +258 -0
- modelable/emitters/python.py +282 -0
- modelable/emitters/rust.py +643 -0
- modelable/emitters/shapes.py +261 -0
- modelable/emitters/sql.py +266 -0
- modelable/emitters/targets.py +141 -0
- modelable/emitters/typescript.py +352 -0
- modelable/expressions/__init__.py +0 -0
- modelable/expressions/cel.py +547 -0
- modelable/governance/__init__.py +3 -0
- modelable/governance/checker.py +271 -0
- modelable/governance/por.py +46 -0
- modelable/grammar/__init__.py +1 -0
- modelable/grammar/modelable.lark +257 -0
- modelable/graph/__init__.py +5 -0
- modelable/graph/export.py +442 -0
- modelable/llm/__init__.py +43 -0
- modelable/llm/chat.py +255 -0
- modelable/llm/config.py +87 -0
- modelable/llm/context.py +194 -0
- modelable/llm/engine.py +976 -0
- modelable/llm/importers.py +1077 -0
- modelable/llm/provenance.py +84 -0
- modelable/llm/providers.py +182 -0
- modelable/llm/qa.py +126 -0
- modelable/llm/recommendations.py +33 -0
- modelable/llm/redaction.py +19 -0
- modelable/llm/render.py +279 -0
- modelable/llm/update_plan.py +101 -0
- modelable/llm/validation_help.py +10 -0
- modelable/lsp/__init__.py +3 -0
- modelable/lsp/__main__.py +4 -0
- modelable/lsp/code_actions.py +210 -0
- modelable/lsp/completion.py +480 -0
- modelable/lsp/definition.py +343 -0
- modelable/lsp/diagnostics.py +31 -0
- modelable/lsp/document_symbols.py +197 -0
- modelable/lsp/federation.py +261 -0
- modelable/lsp/folding.py +33 -0
- modelable/lsp/formatting.py +64 -0
- modelable/lsp/highlight.py +30 -0
- modelable/lsp/hover.py +370 -0
- modelable/lsp/inlay_hints.py +158 -0
- modelable/lsp/references.py +511 -0
- modelable/lsp/rename.py +564 -0
- modelable/lsp/semantic_tokens.py +412 -0
- modelable/lsp/server.py +370 -0
- modelable/lsp/workspace.py +83 -0
- modelable/lsp/workspace_symbols.py +104 -0
- modelable/parser/__init__.py +94 -0
- modelable/parser/ir.py +451 -0
- modelable/parser/parse.py +47 -0
- modelable/parser/transformer.py +798 -0
- modelable/parser/wire.py +68 -0
- modelable/planner/__init__.py +0 -0
- modelable/planner/lineage.py +91 -0
- modelable/planner/planner.py +134 -0
- modelable/planner/plans.py +122 -0
- modelable/py.typed +0 -0
- modelable/registry/__init__.py +9 -0
- modelable/registry/apicurio.py +166 -0
- modelable/registry/base.py +18 -0
- modelable/registry/factory.py +18 -0
- modelable/registry/index.py +419 -0
- modelable/registry/local.py +26 -0
- modelable/registry/oci.py +22 -0
- modelable/registry/resolver.py +213 -0
- modelable/registry/schema.sql +119 -0
- modelable/registry/signature.py +26 -0
- modelable/release.py +125 -0
- modelable/runtime/__init__.py +5 -0
- modelable/runtime/adapter/__init__.py +17 -0
- modelable/runtime/adapter/base.py +18 -0
- modelable/runtime/adapter/postgres.py +82 -0
- modelable/specs/__init__.py +23 -0
- modelable/specs/tracking.py +220 -0
- modelable/validation/__init__.py +3 -0
- modelable/validation/semantic.py +659 -0
- modelable-1.0.0.dist-info/METADATA +61 -0
- modelable-1.0.0.dist-info/RECORD +122 -0
- modelable-1.0.0.dist-info/WHEEL +4 -0
- modelable-1.0.0.dist-info/entry_points.txt +2 -0
- modelable-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
|
|
5
|
+
from modelable.expressions.cel import extract_field_refs, parse_cel
|
|
6
|
+
from modelable.parser.ir import (
|
|
7
|
+
AccessBlock,
|
|
8
|
+
ClassificationLevel,
|
|
9
|
+
ComputedMapping,
|
|
10
|
+
DirectMapping,
|
|
11
|
+
FieldDef,
|
|
12
|
+
MdlFile,
|
|
13
|
+
ModelVersion,
|
|
14
|
+
ProjectionField,
|
|
15
|
+
ProjectionVersion,
|
|
16
|
+
)
|
|
17
|
+
from modelable.registry.resolver import resolve_model_ref
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class GovernanceFinding:
|
|
22
|
+
code: str
|
|
23
|
+
subject: str
|
|
24
|
+
message: str
|
|
25
|
+
|
|
26
|
+
def as_dict(self) -> dict[str, str]:
|
|
27
|
+
return asdict(self)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_projection_governance_findings(
|
|
31
|
+
domain_name: str,
|
|
32
|
+
projection_name: str,
|
|
33
|
+
pv: ProjectionVersion,
|
|
34
|
+
mdl: MdlFile,
|
|
35
|
+
) -> list[GovernanceFinding]:
|
|
36
|
+
findings: list[GovernanceFinding] = []
|
|
37
|
+
subject = f"{domain_name}.{projection_name}@{pv.version}"
|
|
38
|
+
|
|
39
|
+
_check_projection_access(subject, domain_name, pv.access, findings)
|
|
40
|
+
_check_computed_field_access(subject, domain_name, pv, mdl, findings)
|
|
41
|
+
_check_projection_classification(subject, pv, mdl, findings)
|
|
42
|
+
|
|
43
|
+
return findings
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _check_projection_access(
|
|
47
|
+
subject: str,
|
|
48
|
+
domain_name: str,
|
|
49
|
+
access: AccessBlock | None,
|
|
50
|
+
findings: list[GovernanceFinding],
|
|
51
|
+
) -> None:
|
|
52
|
+
if access is None:
|
|
53
|
+
findings.append(
|
|
54
|
+
GovernanceFinding(
|
|
55
|
+
code="missing_project_grant",
|
|
56
|
+
subject=subject,
|
|
57
|
+
message=f"{subject} has no documented project grant",
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
findings.append(
|
|
61
|
+
GovernanceFinding(
|
|
62
|
+
code="missing_read_grant",
|
|
63
|
+
subject=subject,
|
|
64
|
+
message=f"{subject} has no documented read grant",
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
if not _has_entity_permission(access, "project"):
|
|
70
|
+
findings.append(
|
|
71
|
+
GovernanceFinding(
|
|
72
|
+
code="missing_project_grant",
|
|
73
|
+
subject=subject,
|
|
74
|
+
message=f"{subject} has no documented project grant for domain '{domain_name}'",
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if not _has_entity_permission(access, "read"):
|
|
79
|
+
findings.append(
|
|
80
|
+
GovernanceFinding(
|
|
81
|
+
code="missing_read_grant",
|
|
82
|
+
subject=subject,
|
|
83
|
+
message=f"{subject} has no documented read grant for domain '{domain_name}'",
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _check_computed_field_access(
|
|
89
|
+
subject: str,
|
|
90
|
+
projection_domain: str,
|
|
91
|
+
pv: ProjectionVersion,
|
|
92
|
+
mdl: MdlFile,
|
|
93
|
+
findings: list[GovernanceFinding],
|
|
94
|
+
) -> None:
|
|
95
|
+
resolved_sources = _build_resolved_sources(pv, mdl)
|
|
96
|
+
|
|
97
|
+
for proj_field, source_refs in _iter_projection_field_sources(pv, resolved_sources):
|
|
98
|
+
if not isinstance(proj_field.mapping, ComputedMapping):
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
for target_ref, target_version, field_name in source_refs:
|
|
102
|
+
if not _has_property_derivation(target_version.access, field_name, projection_domain):
|
|
103
|
+
findings.append(
|
|
104
|
+
GovernanceFinding(
|
|
105
|
+
code="missing_derivation_policy",
|
|
106
|
+
subject=subject,
|
|
107
|
+
message=(
|
|
108
|
+
f"{subject}.{proj_field.name} uses source field "
|
|
109
|
+
f"{target_ref}.{field_name} without documented derivation policy"
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _check_projection_classification(
|
|
116
|
+
subject: str,
|
|
117
|
+
pv: ProjectionVersion,
|
|
118
|
+
mdl: MdlFile,
|
|
119
|
+
findings: list[GovernanceFinding],
|
|
120
|
+
) -> None:
|
|
121
|
+
resolved_sources = _build_resolved_sources(pv, mdl)
|
|
122
|
+
|
|
123
|
+
for proj_field, source_refs in _iter_projection_field_sources(pv, resolved_sources):
|
|
124
|
+
for target_ref, target_version, field_name in source_refs:
|
|
125
|
+
source_field = _find_field(target_version, field_name)
|
|
126
|
+
if source_field is None:
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
_check_pii_preservation(subject, proj_field, target_ref, source_field, findings)
|
|
130
|
+
_check_classification_preservation(subject, proj_field, target_ref, source_field, findings)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _has_entity_permission(access: AccessBlock, permission: str) -> bool:
|
|
134
|
+
return any(permission in grant.permissions for grant in access.entity)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _has_property_derivation(
|
|
138
|
+
access: AccessBlock | None,
|
|
139
|
+
field_name: str,
|
|
140
|
+
projection_domain: str,
|
|
141
|
+
) -> bool:
|
|
142
|
+
if access is None:
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
grants = access.properties.get(field_name, [])
|
|
146
|
+
for grant in grants:
|
|
147
|
+
if grant.principal != projection_domain:
|
|
148
|
+
continue
|
|
149
|
+
if "read" in grant.permissions and "derive" in grant.permissions:
|
|
150
|
+
return True
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _iter_projection_field_sources(
|
|
155
|
+
pv: ProjectionVersion,
|
|
156
|
+
resolved_sources: dict[str, tuple[str, ModelVersion]],
|
|
157
|
+
) -> list[tuple[ProjectionField, list[tuple[str, ModelVersion, str]]]]:
|
|
158
|
+
result: list[tuple[ProjectionField, list[tuple[str, ModelVersion, str]]]] = []
|
|
159
|
+
for proj_field in pv.fields:
|
|
160
|
+
source_refs: list[tuple[str, ModelVersion, str]] = []
|
|
161
|
+
mapping = proj_field.mapping
|
|
162
|
+
|
|
163
|
+
if isinstance(mapping, DirectMapping):
|
|
164
|
+
target = resolved_sources.get(mapping.source_alias)
|
|
165
|
+
if target is not None:
|
|
166
|
+
target_ref, target_version = target
|
|
167
|
+
source_refs.append((target_ref, target_version, mapping.source_field))
|
|
168
|
+
elif isinstance(mapping, ComputedMapping):
|
|
169
|
+
expr_ast, _ = parse_cel(mapping.expression)
|
|
170
|
+
if expr_ast is not None:
|
|
171
|
+
for alias, field_name in extract_field_refs(expr_ast):
|
|
172
|
+
target = resolved_sources.get(alias)
|
|
173
|
+
if target is None:
|
|
174
|
+
continue
|
|
175
|
+
target_ref, target_version = target
|
|
176
|
+
source_refs.append((target_ref, target_version, field_name))
|
|
177
|
+
|
|
178
|
+
result.append((proj_field, source_refs))
|
|
179
|
+
|
|
180
|
+
return result
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _check_pii_preservation(
|
|
184
|
+
subject: str,
|
|
185
|
+
proj_field: ProjectionField,
|
|
186
|
+
source_ref: str,
|
|
187
|
+
source_field: FieldDef,
|
|
188
|
+
findings: list[GovernanceFinding],
|
|
189
|
+
) -> None:
|
|
190
|
+
if not source_field.is_pii or proj_field.is_pii:
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
findings.append(
|
|
194
|
+
GovernanceFinding(
|
|
195
|
+
code="missing_pii_metadata",
|
|
196
|
+
subject=subject,
|
|
197
|
+
message=(
|
|
198
|
+
f"{subject}.{proj_field.name} projects {source_ref}.{source_field.name} "
|
|
199
|
+
"without preserving @pii metadata"
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _check_classification_preservation(
|
|
206
|
+
subject: str,
|
|
207
|
+
proj_field: ProjectionField,
|
|
208
|
+
source_ref: str,
|
|
209
|
+
source_field: FieldDef,
|
|
210
|
+
findings: list[GovernanceFinding],
|
|
211
|
+
) -> None:
|
|
212
|
+
source_level = source_field.classification
|
|
213
|
+
if source_level is None:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
projected_level = proj_field.classification
|
|
217
|
+
if projected_level is None:
|
|
218
|
+
findings.append(
|
|
219
|
+
GovernanceFinding(
|
|
220
|
+
code="missing_classification_metadata",
|
|
221
|
+
subject=subject,
|
|
222
|
+
message=(
|
|
223
|
+
f"{subject}.{proj_field.name} projects {source_ref}.{source_field.name} "
|
|
224
|
+
"without preserving classification metadata"
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
if _classification_rank(projected_level) < _classification_rank(source_level):
|
|
231
|
+
findings.append(
|
|
232
|
+
GovernanceFinding(
|
|
233
|
+
code="lowered_classification",
|
|
234
|
+
subject=subject,
|
|
235
|
+
message=(
|
|
236
|
+
f"{subject}.{proj_field.name} lowers classification from "
|
|
237
|
+
f"{source_level.value} to {projected_level.value}"
|
|
238
|
+
),
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _classification_rank(level: ClassificationLevel) -> int:
|
|
244
|
+
order = {
|
|
245
|
+
ClassificationLevel.open: 0,
|
|
246
|
+
ClassificationLevel.internal: 1,
|
|
247
|
+
ClassificationLevel.confidential: 2,
|
|
248
|
+
ClassificationLevel.restricted: 3,
|
|
249
|
+
ClassificationLevel.secret: 4,
|
|
250
|
+
}
|
|
251
|
+
return order[level]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _find_field(version: ModelVersion, field_name: str) -> FieldDef | None:
|
|
255
|
+
return next((field for field in version.fields if field.name == field_name), None)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _build_resolved_sources(pv: ProjectionVersion, mdl: MdlFile) -> dict[str, tuple[str, ModelVersion]]:
|
|
259
|
+
resolved_sources: dict[str, tuple[str, ModelVersion]] = {}
|
|
260
|
+
all_sources = [(pv.source.model, pv.source.version, pv.source.alias)]
|
|
261
|
+
for join in pv.joins:
|
|
262
|
+
all_sources.append((join.model, join.version, join.alias))
|
|
263
|
+
|
|
264
|
+
for model_ref, version_spec, alias in all_sources:
|
|
265
|
+
try:
|
|
266
|
+
resolved = resolve_model_ref(mdl, model_ref, version_spec)
|
|
267
|
+
resolved_sources[alias] = (f"{model_ref}@{resolved.version.version}", resolved.version)
|
|
268
|
+
except LookupError:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
return resolved_sources
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
DEFAULT_POR_ISSUER = "modelable-registry.local"
|
|
6
|
+
DEFAULT_POR_ISSUED_AT = "1970-01-01T00:00:00Z"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class PortableOwnershipRecord:
|
|
11
|
+
model: str
|
|
12
|
+
issuer: str = DEFAULT_POR_ISSUER
|
|
13
|
+
issued_at: str = DEFAULT_POR_ISSUED_AT
|
|
14
|
+
signature: str | None = None
|
|
15
|
+
|
|
16
|
+
def as_dict(self) -> dict[str, str | None]:
|
|
17
|
+
return {
|
|
18
|
+
"model": self.model,
|
|
19
|
+
"issuer": self.issuer,
|
|
20
|
+
"issuedAt": self.issued_at,
|
|
21
|
+
**({"signature": self.signature} if self.signature else {}),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_por_reference(ref: str, *, issuer: str = DEFAULT_POR_ISSUER) -> dict[str, str]:
|
|
26
|
+
"""Return the unsigned POR reference embedded in generated artifacts."""
|
|
27
|
+
return {
|
|
28
|
+
"model": ref,
|
|
29
|
+
"issuer": issuer,
|
|
30
|
+
"issuedAt": DEFAULT_POR_ISSUED_AT,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_por_record(
|
|
35
|
+
ref: str,
|
|
36
|
+
*,
|
|
37
|
+
issuer: str = DEFAULT_POR_ISSUER,
|
|
38
|
+
issued_at: str = DEFAULT_POR_ISSUED_AT,
|
|
39
|
+
signature: str | None = None,
|
|
40
|
+
) -> PortableOwnershipRecord:
|
|
41
|
+
return PortableOwnershipRecord(
|
|
42
|
+
model=ref,
|
|
43
|
+
issuer=issuer,
|
|
44
|
+
issued_at=issued_at,
|
|
45
|
+
signature=signature,
|
|
46
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Modelable IDL Grammar
|
|
2
|
+
// Parser: Earley
|
|
3
|
+
|
|
4
|
+
start: statement+
|
|
5
|
+
|
|
6
|
+
statement: domain_decl
|
|
7
|
+
| binding_decl
|
|
8
|
+
| workspace_decl
|
|
9
|
+
| import_domain_stmt
|
|
10
|
+
| projection_decl
|
|
11
|
+
| subscription_decl
|
|
12
|
+
| consumer_decl
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
// -- Domain ------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
domain_decl: "domain" domain_name "{" domain_item* "}"
|
|
18
|
+
domain_name: IDENT | ESCAPED_STRING
|
|
19
|
+
|
|
20
|
+
domain_item: owner_attr
|
|
21
|
+
| contact_attr
|
|
22
|
+
| desc_attr
|
|
23
|
+
| model_decl
|
|
24
|
+
| projection_decl
|
|
25
|
+
| auto_projections_decl
|
|
26
|
+
| generate_block
|
|
27
|
+
|
|
28
|
+
owner_attr: "owner" ":" ESCAPED_STRING
|
|
29
|
+
contact_attr: "contact" ":" ESCAPED_STRING
|
|
30
|
+
desc_attr: "description" ":" ESCAPED_STRING
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
// -- Model -------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
model_decl: wire_annotation* model_kind IDENT model_header? "{" model_body_item* "}"
|
|
36
|
+
model_header: "@" INT model_change?
|
|
37
|
+
| model_change
|
|
38
|
+
model_change: "(" change_kind ")"
|
|
39
|
+
|
|
40
|
+
model_body_item: field_decl
|
|
41
|
+
| access_block
|
|
42
|
+
|
|
43
|
+
model_kind: "entity" -> mk_entity
|
|
44
|
+
| "aggregate" -> mk_aggregate
|
|
45
|
+
| "event" -> mk_event
|
|
46
|
+
| "value" -> mk_value
|
|
47
|
+
|
|
48
|
+
change_kind: "additive" -> ck_additive
|
|
49
|
+
| "breaking" -> ck_breaking
|
|
50
|
+
|
|
51
|
+
field_decl: annotation* IDENT optional_marker? ":" type_expr field_default?
|
|
52
|
+
optional_marker: "?"
|
|
53
|
+
field_default: "=" EXPRESSION
|
|
54
|
+
|
|
55
|
+
annotation: "@key" -> ann_key
|
|
56
|
+
| "@pii" -> ann_pii
|
|
57
|
+
| "@classification" "(" ESCAPED_STRING ")" -> ann_classification
|
|
58
|
+
| "@deprecated" "(" "replacedBy" ":" ESCAPED_STRING ")" -> ann_deprecated
|
|
59
|
+
| "@owner" "(" ESCAPED_STRING ")" -> ann_owner
|
|
60
|
+
| "@server" -> ann_server
|
|
61
|
+
| wire_annotation
|
|
62
|
+
| "@pitCutoff" "(" ANNOTATION_EXPR ")" -> ann_pit_cutoff
|
|
63
|
+
| "@latestBefore" "(" ANNOTATION_EXPR ")" -> ann_latest_before
|
|
64
|
+
| "@latestOnly" -> ann_latest_only
|
|
65
|
+
|
|
66
|
+
wire_annotation: "@wire" "(" wire_option ("," wire_option)* ")" -> ann_wire
|
|
67
|
+
wire_option: wire_key ":" wire_value
|
|
68
|
+
wire_key: IDENT ("." IDENT)?
|
|
69
|
+
wire_value: ESCAPED_STRING -> wire_string
|
|
70
|
+
| wire_map
|
|
71
|
+
wire_map: "{" wire_map_item ("," wire_map_item)* "}"
|
|
72
|
+
wire_map_item: IDENT ":" ESCAPED_STRING
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
// -- Types -------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
type_expr: primitive_type
|
|
78
|
+
| decimal_type
|
|
79
|
+
| enum_type
|
|
80
|
+
| array_type
|
|
81
|
+
| map_type
|
|
82
|
+
| ref_type
|
|
83
|
+
| object_type
|
|
84
|
+
| IDENT
|
|
85
|
+
|
|
86
|
+
primitive_type: "string" -> pt_string
|
|
87
|
+
| "int" -> pt_int
|
|
88
|
+
| "float" -> pt_float
|
|
89
|
+
| "bool" -> pt_bool
|
|
90
|
+
| "date" -> pt_date
|
|
91
|
+
| "time" -> pt_time
|
|
92
|
+
| "timestamp" -> pt_timestamp
|
|
93
|
+
| "uuid" -> pt_uuid
|
|
94
|
+
| "duration" -> pt_duration
|
|
95
|
+
| "binary" -> pt_binary
|
|
96
|
+
| "json" -> pt_json
|
|
97
|
+
|
|
98
|
+
decimal_type: "decimal" "(" INT "," INT ")"
|
|
99
|
+
enum_type: "enum" "(" IDENT ("," IDENT)* ")"
|
|
100
|
+
array_type: "array" "<" type_expr ">"
|
|
101
|
+
map_type: "map" "<" type_expr "," type_expr ">"
|
|
102
|
+
ref_type: "ref" "<" dotted_ref ">"
|
|
103
|
+
object_type: "object" "{" field_decl* "}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
// -- Projections --------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
projection_decl: wire_annotation* "projection" IDENT "@" INT projection_source_block? source_clause? "{" projection_body_item* "}"
|
|
109
|
+
projection_source_block: "{" source_clause "}"
|
|
110
|
+
|
|
111
|
+
projection_body_item: proj_field
|
|
112
|
+
| access_block
|
|
113
|
+
| subscription_block
|
|
114
|
+
| generate_block
|
|
115
|
+
| materialisation_block
|
|
116
|
+
|
|
117
|
+
source_clause: "from" dotted_ref "@" version_spec "as" IDENT join_clause* where_clause? group_clause?
|
|
118
|
+
|
|
119
|
+
where_clause: "where" FIELD_EXPRESSION
|
|
120
|
+
|
|
121
|
+
join_modifier: "left"
|
|
122
|
+
|
|
123
|
+
join_clause: join_prefix annotation*
|
|
124
|
+
| join_prefix join_option*
|
|
125
|
+
join_option: annotation
|
|
126
|
+
| cardinality_attr
|
|
127
|
+
cardinality_attr: "cardinality" ":" IDENT
|
|
128
|
+
join_prefix: "join" dotted_ref "@" version_spec "as" IDENT "on" EXPRESSION
|
|
129
|
+
| join_modifier "join" dotted_ref "@" version_spec "as" IDENT "on" EXPRESSION
|
|
130
|
+
|
|
131
|
+
group_clause: "group" "by" group_item ("," group_item)*
|
|
132
|
+
|
|
133
|
+
group_item: /[^,\n\r{}]+/
|
|
134
|
+
|
|
135
|
+
version_spec: INT -> version_exact
|
|
136
|
+
| INT "#" IDENT -> version_pinned
|
|
137
|
+
| ">=" INT "<" INT -> version_range
|
|
138
|
+
| ">=" INT -> version_min
|
|
139
|
+
|
|
140
|
+
proj_field: annotation* IDENT "<-" qualified_field -> direct_field
|
|
141
|
+
| annotation* IDENT "=" FIELD_EXPRESSION -> computed_field
|
|
142
|
+
|
|
143
|
+
subscription_block: "subscription" "{" subscription_item* "}"
|
|
144
|
+
subscription_item: IDENT ":" EXPRESSION
|
|
145
|
+
materialisation_block: "materialisation" "{" materialisation_item* "}"
|
|
146
|
+
materialisation_item: IDENT ":" EXPRESSION
|
|
147
|
+
|
|
148
|
+
qualified_field: IDENT "." IDENT
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
// -- Generate ----------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
generate_block: "generate" "{" generate_target* "}"
|
|
154
|
+
|
|
155
|
+
generate_target: target_name ("->" ESCAPED_STRING)?
|
|
156
|
+
|
|
157
|
+
target_name: "openapi" -> tn_openapi
|
|
158
|
+
| "typescript" -> tn_typescript
|
|
159
|
+
| "avro" -> tn_avro
|
|
160
|
+
| "protobuf" -> tn_protobuf
|
|
161
|
+
| "sql" "(" db_dialect ")" -> tn_sql
|
|
162
|
+
| "jsonschema" -> tn_jsonschema
|
|
163
|
+
| "asyncapi" -> tn_asyncapi
|
|
164
|
+
| "docs" -> tn_docs
|
|
165
|
+
|
|
166
|
+
db_dialect: "postgres" -> dd_postgres
|
|
167
|
+
| "mysql" -> dd_mysql
|
|
168
|
+
| "clickhouse" -> dd_clickhouse
|
|
169
|
+
| "sqlite" -> dd_sqlite
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// -- Deferred top-level forms ------------------------------------------------
|
|
173
|
+
// These are parsed more fully in later parser-plan tasks.
|
|
174
|
+
|
|
175
|
+
binding_decl: "binding" IDENT "{" binding_item* "}"
|
|
176
|
+
binding_item: binding_adapter_attr
|
|
177
|
+
| binding_model_attr
|
|
178
|
+
| binding_table_attr
|
|
179
|
+
| ignored_block_item
|
|
180
|
+
binding_adapter_attr: "adapter" ":" IDENT
|
|
181
|
+
binding_model_attr: "model" ":" dotted_ref "@" INT
|
|
182
|
+
binding_table_attr: "table" ":" ESCAPED_STRING
|
|
183
|
+
subscription_decl: "subscription" IDENT "{" subscription_decl_item* "}"
|
|
184
|
+
subscription_decl_item: IDENT ":" EXPRESSION
|
|
185
|
+
workspace_decl: "workspace" workspace_label? "{" workspace_item* "}"
|
|
186
|
+
workspace_label: IDENT | ESCAPED_STRING
|
|
187
|
+
workspace_item: workspace_name_attr
|
|
188
|
+
| workspace_description_attr
|
|
189
|
+
| registry_block
|
|
190
|
+
| peers_block
|
|
191
|
+
| generate_block
|
|
192
|
+
| ai_block
|
|
193
|
+
workspace_name_attr: "name" ":" ESCAPED_STRING
|
|
194
|
+
workspace_description_attr: "description" ":" ESCAPED_STRING
|
|
195
|
+
registry_block: "registry" "{" ignored_block_item* "}"
|
|
196
|
+
peers_block: "peers" ":" "[" PEERS_CONTENT "]"
|
|
197
|
+
ai_block: "ai" "{" ai_item* "}"
|
|
198
|
+
ai_item: ai_provider | ai_model | ai_repair_attempts
|
|
199
|
+
ai_provider: "provider" ":" ESCAPED_STRING
|
|
200
|
+
ai_model: "model" ":" ESCAPED_STRING
|
|
201
|
+
ai_repair_attempts: "repair_attempts" ":" INT
|
|
202
|
+
import_domain_stmt: "import" "domain" dotted_ref import_source? pinned_import? version_selector?
|
|
203
|
+
import_source: "from" "registry" ESCAPED_STRING
|
|
204
|
+
pinned_import: "at" dotted_ref "@" INT "#" IDENT
|
|
205
|
+
consumer_decl: "consumer" IDENT? "{" ignored_block_item* "}"
|
|
206
|
+
auto_projections_decl: "auto" "projections" IDENT "@" INT "{" auto_projection_item* "}"
|
|
207
|
+
|
|
208
|
+
auto_projection_item: auto_projection_kind auto_projection_option*
|
|
209
|
+
auto_projection_kind: "db" -> apk_db
|
|
210
|
+
| "request" -> apk_request
|
|
211
|
+
| "reply" -> apk_reply
|
|
212
|
+
| "event" -> apk_event
|
|
213
|
+
|
|
214
|
+
auto_projection_option: exclude_option | on_option
|
|
215
|
+
exclude_option: "exclude" "[" auto_projection_exclusion ("," auto_projection_exclusion)* "]"
|
|
216
|
+
on_option: "on" "[" IDENT ("," IDENT)* "]"
|
|
217
|
+
auto_projection_exclusion: annotation | IDENT
|
|
218
|
+
|
|
219
|
+
access_block: "access" "{" access_item* "}"
|
|
220
|
+
access_item: entity_grant
|
|
221
|
+
| property_grant
|
|
222
|
+
entity_grant: "entity" principal permission_list
|
|
223
|
+
property_grant: "property" IDENT principal permission_list
|
|
224
|
+
principal: PRINCIPAL
|
|
225
|
+
permission_list: "[" permission ("," permission)* "]"
|
|
226
|
+
permission: "read" -> p_read
|
|
227
|
+
| "project" -> p_project
|
|
228
|
+
| "subscribe" -> p_subscribe
|
|
229
|
+
| "write" -> p_write
|
|
230
|
+
| "transfer" -> p_transfer
|
|
231
|
+
| "manage_access" -> p_manage_access
|
|
232
|
+
| "derive" -> p_derive
|
|
233
|
+
| "redact" -> p_redact
|
|
234
|
+
|
|
235
|
+
version_selector: "@" version_expr
|
|
236
|
+
version_expr: INT | VERSION_RANGE
|
|
237
|
+
dotted_ref: IDENT ("." IDENT)*
|
|
238
|
+
|
|
239
|
+
ignored_block_item: /[^{}]+/ | "{" ignored_block_item* "}"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
// -- Lexing ------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
VERSION_RANGE: /[<>~=^0-9.,\s]+/
|
|
245
|
+
EXPRESSION: /[^\n\r{}]+/
|
|
246
|
+
FIELD_EXPRESSION: /[^\n\r]+(?:\n(?!\s*(?:@|[A-Za-z_][A-Za-z0-9_-]*\s*(?:<-|=)|generate\s*\{|materialisation\s*\{|subscription\s*\{|access\s*\{|from\s|group\s+by|\{|\}))[^\n\r]+)*/
|
|
247
|
+
ANNOTATION_EXPR: /[^)\n\r{}]+/
|
|
248
|
+
IDENT: /[A-Za-z_][A-Za-z0-9_-]*/
|
|
249
|
+
PEERS_CONTENT: /[^\]]+/
|
|
250
|
+
PRINCIPAL: /\*|[A-Za-z_][A-Za-z0-9_.-]*/
|
|
251
|
+
|
|
252
|
+
COMMENT: /\/\/[^\n]*/
|
|
253
|
+
%import common.ESCAPED_STRING
|
|
254
|
+
%import common.INT
|
|
255
|
+
%import common.WS
|
|
256
|
+
%ignore WS
|
|
257
|
+
%ignore COMMENT
|