arango-query-core 0.2.0__tar.gz → 0.2.1__tar.gz
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.
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/CHANGELOG.md +21 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/PKG-INFO +1 -1
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/owl_rdflib.py +37 -7
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/owl_turtle.py +41 -15
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/pyproject.toml +1 -1
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_owl_rdflib.py +50 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_owl_turtle_roundtrip.py +88 -0
- arango_query_core-0.2.0/.claude/settings.json +0 -8
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/.github/workflows/ci.yml +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/.github/workflows/publish.yml +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/.gitignore +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/LICENSE +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/README.md +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/__init__.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/aql.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/errors.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/exec.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/extensions.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/mapping.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/__init__.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/engine.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/fewshot.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/grounding.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/pathindex.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/postconditions.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/providers.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/seams.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/nl/synthbank.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/arango_query_core/py.typed +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/docs/PRD.md +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/fixtures/mappings/pg.owl.ttl +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_mapping_helpers.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_mapping_resolver_normalization.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_mapping_vci_detection.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_nl_engine.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_nl_fewshot.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_nl_grounding.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_nl_pathindex.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_nl_postconditions.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_nl_providers.py +0 -0
- {arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_nl_synthbank.py +0 -0
|
@@ -6,6 +6,27 @@ exact versions (contextual-data-fabric CC-9) and re-run their golden /
|
|
|
6
6
|
eval suites on every bump — pre-1.0, **minor** bumps may break the API
|
|
7
7
|
and this file must say so explicitly.
|
|
8
8
|
|
|
9
|
+
## [0.2.1] - 2026-10-16
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **OWL import keeps property names.** `owl_turtle.turtle_to_mapping` cut
|
|
14
|
+
every datatype-property name at its last underscore — a rule meant for the
|
|
15
|
+
`:<REL>_<prop>` relationship properties `mapping_to_turtle` writes — so
|
|
16
|
+
`file_name` imported as `name` and `account_id` as `id`, including on a
|
|
17
|
+
round trip of this library's own export. Entity properties now keep their
|
|
18
|
+
full name; relationship properties lose exactly their `<REL>_` prefix.
|
|
19
|
+
- **Shared fields reach every class.** Both importers (`turtle_to_mapping`
|
|
20
|
+
and the rdflib-based `parse_owl_with_rdflib`) dropped a property whose
|
|
21
|
+
`rdfs:domain` is an `owl:unionOf` list — arangodb-schema-analyzer
|
|
22
|
+
0.15.1's form for a field several classes share — and the rdflib one read
|
|
23
|
+
only the first of several domains. Each member / domain now gets the field,
|
|
24
|
+
and the anonymous union class is no longer imported as an entity.
|
|
25
|
+
- **Stored field names.** `phys:attributeName` (analyzer 0.15.1) now maps a
|
|
26
|
+
property to its stored field in the physical mapping
|
|
27
|
+
(`properties.<name>.field`), so a sanitized or renamed field (`file-name`,
|
|
28
|
+
`page_count`) resolves to the attribute that holds it.
|
|
29
|
+
|
|
9
30
|
## [0.2.0] - 2026-08-23
|
|
10
31
|
|
|
11
32
|
### BREAKING
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: arango-query-core
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Shared conceptual-model and NL substrate for ArangoDB query transpilers (arango-cypher-py, arango-sparql-py)
|
|
5
5
|
Project-URL: Repository, https://github.com/ArthurKeen/arango-query-core
|
|
6
6
|
Project-URL: Changelog, https://github.com/ArthurKeen/arango-query-core/blob/main/CHANGELOG.md
|
|
@@ -42,23 +42,30 @@ def parse_owl_with_rdflib(turtle_text: str) -> MappingBundle:
|
|
|
42
42
|
entities: list[dict[str, Any]] = []
|
|
43
43
|
entity_props: dict[str, list[dict[str, str]]] = {}
|
|
44
44
|
|
|
45
|
-
for cls in g
|
|
45
|
+
for cls in _named_classes(g, RDF, OWL):
|
|
46
46
|
name = _local_name(cls)
|
|
47
47
|
entities.append({"name": name, "labels": [name], "properties": []})
|
|
48
48
|
entity_props[name] = []
|
|
49
49
|
|
|
50
50
|
# ── Extract datatype properties ──────────────────────────────────
|
|
51
|
+
# A property's domain may be one class, several (each gets the field),
|
|
52
|
+
# or an owl:unionOf class expression — arangodb-schema-analyzer's form
|
|
53
|
+
# for a field several classes share. phys:attributeName names the
|
|
54
|
+
# stored field when it differs from the property name.
|
|
55
|
+
fields: dict[str, dict[str, str]] = {}
|
|
51
56
|
for prop in g.subjects(RDF.type, OWL.DatatypeProperty):
|
|
52
57
|
prop_name = _local_name(prop)
|
|
53
|
-
domain = g.value(prop, RDFS.domain)
|
|
54
58
|
range_uri = g.value(prop, RDFS.range)
|
|
55
59
|
prop_entry: dict[str, str] = {"name": prop_name}
|
|
56
60
|
if range_uri:
|
|
57
61
|
prop_entry["type"] = _xsd_to_simple(str(range_uri))
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
entity_props
|
|
62
|
+
attribute = g.value(prop, PHYS.attributeName)
|
|
63
|
+
for domain_node in g.objects(prop, RDFS.domain):
|
|
64
|
+
for domain_name in _domain_names(g, domain_node, OWL):
|
|
65
|
+
if domain_name in entity_props:
|
|
66
|
+
entity_props[domain_name].append(prop_entry)
|
|
67
|
+
if attribute:
|
|
68
|
+
fields.setdefault(domain_name, {})[prop_name] = str(attribute)
|
|
62
69
|
|
|
63
70
|
for entity in entities:
|
|
64
71
|
entity["properties"] = entity_props.get(entity["name"], [])
|
|
@@ -82,7 +89,7 @@ def parse_owl_with_rdflib(turtle_text: str) -> MappingBundle:
|
|
|
82
89
|
physical_entities: dict[str, dict[str, Any]] = {}
|
|
83
90
|
physical_rels: dict[str, dict[str, Any]] = {}
|
|
84
91
|
|
|
85
|
-
for cls in g
|
|
92
|
+
for cls in _named_classes(g, RDF, OWL):
|
|
86
93
|
name = _local_name(cls)
|
|
87
94
|
coll = g.value(cls, PHYS.collectionName)
|
|
88
95
|
style = g.value(cls, PHYS.style) or g.value(cls, PHYS.mappingStyle)
|
|
@@ -98,6 +105,8 @@ def parse_owl_with_rdflib(turtle_text: str) -> MappingBundle:
|
|
|
98
105
|
pm_entry["typeField"] = str(type_field)
|
|
99
106
|
if type_value:
|
|
100
107
|
pm_entry["typeValue"] = str(type_value)
|
|
108
|
+
for prop_name, field in fields.get(name, {}).items():
|
|
109
|
+
pm_entry["properties"][prop_name] = {"field": field}
|
|
101
110
|
physical_entities[name] = pm_entry
|
|
102
111
|
|
|
103
112
|
for prop in g.subjects(RDF.type, OWL.ObjectProperty):
|
|
@@ -127,6 +136,27 @@ def parse_owl_with_rdflib(turtle_text: str) -> MappingBundle:
|
|
|
127
136
|
)
|
|
128
137
|
|
|
129
138
|
|
|
139
|
+
def _named_classes(g: Any, rdf: Any, owl: Any) -> list[Any]:
|
|
140
|
+
"""Every ``owl:Class`` with an IRI. An anonymous class expression (the
|
|
141
|
+
``[ a owl:Class ; owl:unionOf ( … ) ]`` of a shared field's domain) is not
|
|
142
|
+
an entity."""
|
|
143
|
+
import rdflib
|
|
144
|
+
|
|
145
|
+
return [c for c in g.subjects(rdf.type, owl.Class) if isinstance(c, rdflib.URIRef)]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _domain_names(g: Any, domain: Any, owl: Any) -> list[str]:
|
|
149
|
+
"""Class names a ``rdfs:domain`` value covers: the class itself, or every
|
|
150
|
+
member of an ``owl:unionOf`` list."""
|
|
151
|
+
import rdflib
|
|
152
|
+
from rdflib.collection import Collection
|
|
153
|
+
|
|
154
|
+
if isinstance(domain, rdflib.BNode):
|
|
155
|
+
members = g.value(domain, owl.unionOf)
|
|
156
|
+
return [_local_name(m) for m in Collection(g, members)] if members is not None else []
|
|
157
|
+
return [_local_name(domain)]
|
|
158
|
+
|
|
159
|
+
|
|
130
160
|
def _local_name(uri: Any) -> str:
|
|
131
161
|
"""Extract the local (fragment / last-path-segment) name from a URI."""
|
|
132
162
|
s = str(uri)
|
|
@@ -111,10 +111,21 @@ def mapping_to_turtle(bundle: MappingBundle) -> str:
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
def turtle_to_mapping(turtle: str) -> MappingBundle:
|
|
114
|
-
"""Parse an OWL/Turtle string into a MappingBundle.
|
|
114
|
+
"""Parse an OWL/Turtle string into a MappingBundle.
|
|
115
|
+
|
|
116
|
+
A line-oriented reader for the layout :func:`mapping_to_turtle` and
|
|
117
|
+
arangodb-schema-analyzer write (no rdflib needed; see
|
|
118
|
+
:mod:`.owl_rdflib` for arbitrary Turtle). A datatype property keeps its
|
|
119
|
+
full local name (``:file_name`` is ``file_name``); only a relationship
|
|
120
|
+
property written ``:<REL>_<prop>`` with ``rdfs:domain :<REL>`` loses the
|
|
121
|
+
``<REL>_`` prefix. Its domain may be one class, several ``rdfs:domain``
|
|
122
|
+
lines, or an ``owl:unionOf`` list (the analyzer's form for a field
|
|
123
|
+
several classes share), and ``phys:attributeName`` names the stored
|
|
124
|
+
field when it differs from the property name.
|
|
125
|
+
"""
|
|
115
126
|
classes: dict[str, dict[str, str]] = {}
|
|
116
127
|
obj_props: dict[str, dict[str, str]] = {}
|
|
117
|
-
data_props: list[dict[str,
|
|
128
|
+
data_props: list[dict[str, Any]] = []
|
|
118
129
|
annotations: dict[str, dict[str, str]] = {}
|
|
119
130
|
|
|
120
131
|
current_subject: str | None = None
|
|
@@ -143,14 +154,20 @@ def turtle_to_mapping(turtle: str) -> MappingBundle:
|
|
|
143
154
|
if m_dataprop:
|
|
144
155
|
current_subject = m_dataprop.group(1)
|
|
145
156
|
current_kind = "dataprop"
|
|
146
|
-
data_props.append({"name": current_subject})
|
|
157
|
+
data_props.append({"name": current_subject, "domains": []})
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
m_union = re.match(r"rdfs:domain\s+\[.*owl:unionOf\s*\(([^)]*)\)", stripped)
|
|
161
|
+
if m_union:
|
|
162
|
+
if current_kind == "dataprop" and data_props:
|
|
163
|
+
data_props[-1]["domains"].extend(re.findall(r":(\w+)", m_union.group(1)))
|
|
147
164
|
continue
|
|
148
165
|
|
|
149
166
|
m_domain = re.match(r"rdfs:domain\s+:(\w+)", stripped)
|
|
150
167
|
if m_domain:
|
|
151
168
|
domain = m_domain.group(1)
|
|
152
169
|
if current_kind == "dataprop" and data_props:
|
|
153
|
-
data_props[-1]["
|
|
170
|
+
data_props[-1]["domains"].append(domain)
|
|
154
171
|
elif current_kind == "objprop" and current_subject:
|
|
155
172
|
obj_props[current_subject]["domain"] = domain
|
|
156
173
|
continue
|
|
@@ -164,7 +181,14 @@ def turtle_to_mapping(turtle: str) -> MappingBundle:
|
|
|
164
181
|
obj_props[current_subject]["range"] = rng.lstrip(":")
|
|
165
182
|
continue
|
|
166
183
|
|
|
167
|
-
for annot in (
|
|
184
|
+
for annot in (
|
|
185
|
+
"mappingStyle",
|
|
186
|
+
"collectionName",
|
|
187
|
+
"typeField",
|
|
188
|
+
"typeValue",
|
|
189
|
+
"edgeCollectionName",
|
|
190
|
+
"attributeName",
|
|
191
|
+
):
|
|
168
192
|
m_a = re.match(rf':(\w+)\s+phys:{annot}\s+"([^"]+)"', stripped)
|
|
169
193
|
if m_a:
|
|
170
194
|
subject = m_a.group(1)
|
|
@@ -174,14 +198,8 @@ def turtle_to_mapping(turtle: str) -> MappingBundle:
|
|
|
174
198
|
entities_cs: list[dict[str, Any]] = []
|
|
175
199
|
entities_pm: dict[str, dict[str, Any]] = {}
|
|
176
200
|
for name in classes:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
"name": dp["name"].split("_")[-1] if "_" in dp["name"] else dp["name"],
|
|
180
|
-
"type": _from_xsd_type(dp.get("range", "xsd:string")),
|
|
181
|
-
}
|
|
182
|
-
for dp in data_props
|
|
183
|
-
if dp.get("domain") == name
|
|
184
|
-
]
|
|
201
|
+
own = [dp for dp in data_props if name in dp["domains"]]
|
|
202
|
+
props = [{"name": dp["name"], "type": _from_xsd_type(dp.get("range", "xsd:string"))} for dp in own]
|
|
185
203
|
entities_cs.append({"name": name, "labels": [name], "properties": props})
|
|
186
204
|
ann = annotations.get(name, {})
|
|
187
205
|
pm_entry: dict[str, Any] = {
|
|
@@ -191,6 +209,13 @@ def turtle_to_mapping(turtle: str) -> MappingBundle:
|
|
|
191
209
|
if pm_entry["style"] == "LABEL":
|
|
192
210
|
pm_entry["typeField"] = ann.get("typeField", "type")
|
|
193
211
|
pm_entry["typeValue"] = ann.get("typeValue", name)
|
|
212
|
+
fields = {
|
|
213
|
+
dp["name"]: {"field": annotations[dp["name"]]["attributeName"]}
|
|
214
|
+
for dp in own
|
|
215
|
+
if annotations.get(dp["name"], {}).get("attributeName")
|
|
216
|
+
}
|
|
217
|
+
if fields:
|
|
218
|
+
pm_entry["properties"] = fields
|
|
194
219
|
entities_pm[name] = pm_entry
|
|
195
220
|
|
|
196
221
|
rels_cs: list[dict[str, Any]] = []
|
|
@@ -201,11 +226,12 @@ def turtle_to_mapping(turtle: str) -> MappingBundle:
|
|
|
201
226
|
to_e = op.get("range", "Any")
|
|
202
227
|
props = [
|
|
203
228
|
{
|
|
204
|
-
|
|
229
|
+
# Exactly the `<REL>_` prefix mapping_to_turtle adds, not every occurrence.
|
|
230
|
+
"name": dp["name"].removeprefix(f"{rtype}_"),
|
|
205
231
|
"type": _from_xsd_type(dp.get("range", "xsd:string")),
|
|
206
232
|
}
|
|
207
233
|
for dp in data_props
|
|
208
|
-
if dp
|
|
234
|
+
if rtype in dp["domains"]
|
|
209
235
|
]
|
|
210
236
|
rels_cs.append({"type": rtype, "fromEntity": from_e, "toEntity": to_e, "properties": props})
|
|
211
237
|
ann = annotations.get(rtype, {})
|
|
@@ -189,3 +189,53 @@ class TestImportError:
|
|
|
189
189
|
with patch("builtins.__import__", side_effect=mock_import):
|
|
190
190
|
with pytest.raises(ImportError, match="rdflib is required"):
|
|
191
191
|
parse_owl_with_rdflib(MINIMAL_OWL)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
ANALYZER_OWL = """\
|
|
195
|
+
@prefix : <http://arangodb.com/schema/hybrid#> .
|
|
196
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
197
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
198
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
199
|
+
@prefix phys: <http://arangodb.com/schema/physical#> .
|
|
200
|
+
|
|
201
|
+
:Chunk a owl:Class ; phys:collectionName "chunks" .
|
|
202
|
+
:Document a owl:Class ; phys:collectionName "documents" .
|
|
203
|
+
:Note a owl:Class ; phys:collectionName "notes" .
|
|
204
|
+
|
|
205
|
+
:content a owl:DatatypeProperty ;
|
|
206
|
+
rdfs:domain [ a owl:Class ; owl:unionOf ( :Chunk :Document ) ] .
|
|
207
|
+
:file_name a owl:DatatypeProperty ;
|
|
208
|
+
rdfs:domain :Document ;
|
|
209
|
+
phys:attributeName "file-name" .
|
|
210
|
+
:tag a owl:DatatypeProperty ;
|
|
211
|
+
rdfs:domain :Document , :Note ;
|
|
212
|
+
rdfs:range xsd:string .
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _names(bundle, entity):
|
|
217
|
+
return sorted(
|
|
218
|
+
p["name"]
|
|
219
|
+
for e in bundle.conceptual_schema["entities"]
|
|
220
|
+
if e["name"] == entity
|
|
221
|
+
for p in e["properties"]
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_a_union_domain_gives_the_field_to_every_member():
|
|
226
|
+
bundle = parse_owl_with_rdflib(ANALYZER_OWL)
|
|
227
|
+
# The anonymous union class is not an entity of its own.
|
|
228
|
+
assert sorted(e["name"] for e in bundle.conceptual_schema["entities"]) == ["Chunk", "Document", "Note"]
|
|
229
|
+
assert _names(bundle, "Chunk") == ["content"]
|
|
230
|
+
assert _names(bundle, "Document") == ["content", "file_name", "tag"]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_several_domains_each_get_the_field():
|
|
234
|
+
assert _names(parse_owl_with_rdflib(ANALYZER_OWL), "Note") == ["tag"]
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_attribute_name_becomes_the_physical_field():
|
|
238
|
+
bundle = parse_owl_with_rdflib(ANALYZER_OWL)
|
|
239
|
+
assert bundle.physical_mapping["entities"]["Document"]["properties"] == {
|
|
240
|
+
"file_name": {"field": "file-name"}
|
|
241
|
+
}
|
|
@@ -164,3 +164,91 @@ def test_export_label_style() -> None:
|
|
|
164
164
|
assert pm_p["style"] == "LABEL"
|
|
165
165
|
assert pm_p["typeField"] == "type"
|
|
166
166
|
assert pm_p["typeValue"] == "Person"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# Property names, shared fields and stored-field names (import fixes)
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
# The layout arangodb-schema-analyzer >= 0.15.1 writes: a field two classes
|
|
174
|
+
# share has an owl:unionOf domain; a renamed or sanitized field carries its
|
|
175
|
+
# stored name in phys:attributeName.
|
|
176
|
+
ANALYZER_OWL = """\
|
|
177
|
+
@prefix : <http://arangodb.com/schema/hybrid#> .
|
|
178
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
179
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
180
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
181
|
+
@prefix phys: <http://arangodb.com/schema/physical#> .
|
|
182
|
+
|
|
183
|
+
:Chunk a owl:Class ;
|
|
184
|
+
rdfs:label "Chunk" .
|
|
185
|
+
:Chunk phys:mappingStyle "COLLECTION" .
|
|
186
|
+
:Chunk phys:collectionName "chunks" .
|
|
187
|
+
|
|
188
|
+
:Document a owl:Class ;
|
|
189
|
+
rdfs:label "Document" .
|
|
190
|
+
:Document phys:mappingStyle "COLLECTION" .
|
|
191
|
+
:Document phys:collectionName "documents" .
|
|
192
|
+
|
|
193
|
+
:content a owl:DatatypeProperty ;
|
|
194
|
+
rdfs:label "content" ;
|
|
195
|
+
rdfs:domain [ a owl:Class ; owl:unionOf ( :Chunk :Document ) ] .
|
|
196
|
+
|
|
197
|
+
:file_name a owl:DatatypeProperty ;
|
|
198
|
+
rdfs:label "file-name" ;
|
|
199
|
+
rdfs:domain :Document .
|
|
200
|
+
:file_name phys:attributeName "file-name" .
|
|
201
|
+
|
|
202
|
+
:page_count a owl:DatatypeProperty ;
|
|
203
|
+
rdfs:label "page_count" ;
|
|
204
|
+
rdfs:domain :Document ;
|
|
205
|
+
rdfs:range xsd:integer .
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _props(bundle: MappingBundle, entity: str) -> list[str]:
|
|
210
|
+
return [
|
|
211
|
+
p["name"]
|
|
212
|
+
for e in bundle.conceptual_schema["entities"]
|
|
213
|
+
if e["name"] == entity
|
|
214
|
+
for p in e["properties"]
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_snake_case_entity_properties_keep_their_names(simple_bundle: MappingBundle) -> None:
|
|
219
|
+
"""Regression: every name was cut at its last underscore (file_name -> name)."""
|
|
220
|
+
simple_bundle.conceptual_schema["entities"][0]["properties"].append(
|
|
221
|
+
{"name": "account_id", "type": "string"}
|
|
222
|
+
)
|
|
223
|
+
back = turtle_to_mapping(mapping_to_turtle(simple_bundle))
|
|
224
|
+
assert _props(back, "Person") == ["name", "age", "account_id"]
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_relationship_properties_lose_only_their_prefix(simple_bundle: MappingBundle) -> None:
|
|
228
|
+
simple_bundle.conceptual_schema["relationships"][0]["properties"] = [
|
|
229
|
+
{"name": "role_name", "type": "string"}
|
|
230
|
+
]
|
|
231
|
+
back = turtle_to_mapping(mapping_to_turtle(simple_bundle))
|
|
232
|
+
(rel,) = back.conceptual_schema["relationships"]
|
|
233
|
+
assert [p["name"] for p in rel["properties"]] == ["role_name"]
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_a_field_with_a_union_domain_belongs_to_every_member() -> None:
|
|
237
|
+
bundle = turtle_to_mapping(ANALYZER_OWL)
|
|
238
|
+
assert _props(bundle, "Chunk") == ["content"]
|
|
239
|
+
assert _props(bundle, "Document") == ["content", "file_name", "page_count"]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_attribute_name_maps_the_property_to_its_stored_field() -> None:
|
|
243
|
+
bundle = turtle_to_mapping(ANALYZER_OWL)
|
|
244
|
+
assert bundle.physical_mapping["entities"]["Document"]["properties"] == {
|
|
245
|
+
"file_name": {"field": "file-name"}
|
|
246
|
+
}
|
|
247
|
+
assert "properties" not in bundle.physical_mapping["entities"]["Chunk"]
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_a_property_declared_once_per_domain_reaches_each(simple_bundle: MappingBundle) -> None:
|
|
251
|
+
"""mapping_to_turtle writes one block per entity for a shared name."""
|
|
252
|
+
simple_bundle.conceptual_schema["entities"][1]["properties"].append({"name": "name", "type": "string"})
|
|
253
|
+
back = turtle_to_mapping(mapping_to_turtle(simple_bundle))
|
|
254
|
+
assert "name" in _props(back, "Person") and "name" in _props(back, "Movie")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{arango_query_core-0.2.0 → arango_query_core-0.2.1}/tests/test_mapping_resolver_normalization.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|