Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions native/src/main/java/org/humemai/nativelib/NativeCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -185,6 +186,18 @@ static void append(final StringBuilder sb, final Object value) {
appendDocument(sb, doc);
} else if (value instanceof Identifiable id) {
tagged(sb, "R").append('"').append(id.getIdentity().toString()).append("\"]}");
} else if (value instanceof Result r) {
// A query row nested in a value, for example an entry of schema:types.properties (#195): its element when it has one, as a
// Document value is written, else its properties as a map. Before, it fell through to String.valueOf and reached Python as a str.
final Optional<Document> element = r.isElement() ? r.getElement() : Optional.empty();
if (element.isPresent())
appendDocument(sb, element.get());
else {
final Map<String, Object> properties = new LinkedHashMap<>();
for (final String name : r.getPropertyNames())
properties.put(name, r.getProperty(name));
append(sb, properties);
}
} else if (value instanceof Map<?, ?> map) {
boolean stringKeys = true;
for (final Object key : map.keySet())
Expand Down
13 changes: 7 additions & 6 deletions src/arcadedb/_native/columnar.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
them.
"""

import re
from .schema_objects import ( # the schema-entry reader shared with NativeType (#195)
_name_type,
)

try:
import numpy as _np
Expand All @@ -25,7 +27,6 @@
_INT64_MAX = 2**63 - 1
_INT32_MAX = 2**31 - 1
_NoneType = type(None)
_PROPERTY = re.compile(r"name: (.+?), type: ([A-Z_]+)")


class Packed:
Expand Down Expand Up @@ -75,10 +76,10 @@ def _declared_types(db, type_name):
if current in seen or current not in rows:
continue
seen.add(current)
for text in rows[current].get("properties") or []:
m = _PROPERTY.search(text)
if m:
out.setdefault(m.group(1), m.group(2))
for entry in rows[current].get("properties") or []:
name, kind = _name_type(entry) # a dict since #195, the text form before
if name and kind:
out.setdefault(name, str(kind))
queue.extend(rows[current].get("parentTypes") or [])
return out

Expand Down
16 changes: 11 additions & 5 deletions src/arcadedb/_native/schema_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
_PROPERTY = re.compile(r"name: (.+?), type: ([A-Z_]+)")


def _name_type(entry):
"""(name, type) of an entry of a ``schema:types`` row's ``properties``: a dict since #195 (the codec writes a nested row
as a map), the engine's text form of the row before it; (None, None) when neither.
"""
if isinstance(entry, dict):
return entry.get("name"), entry.get("type")
m = _PROPERTY.search(entry) if isinstance(entry, str) else None
return (m.group(1), m.group(2)) if m else (None, None)


class NativeType:
"""A type as a ``schema:types`` row."""

Expand All @@ -26,11 +36,7 @@ def getType(self) -> str:
return self._row.get("type")

def getPropertyNames(self):
return [
m.group(1)
for m in map(_PROPERTY.search, self._row.get("properties") or [])
if m
]
return [n for n, _ in map(_name_type, self._row.get("properties") or []) if n]

def existsProperty(self, name: str) -> bool:
return name in self.getPropertyNames()
Expand Down
18 changes: 14 additions & 4 deletions src/arcadedb/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ def _is_native(db) -> bool:
_PROPERTY = re.compile(r"name: (.+?), type: ([A-Z_]+)")


def _property_name_type(entry):
"""(name, type) of an entry of a native ``schema:types`` row's ``properties``: a dict since #195, the engine's text form
of the row before it; (None, None) when neither. Kept here, not imported from ``_native``, so the JVM build never loads it.
"""
if isinstance(entry, dict):
return entry.get("name"), entry.get("type")
m = _PROPERTY.search(entry) if isinstance(entry, str) else None
return (m.group(1), m.group(2)) if m else (None, None)


def _declared_types_native(
db, type_name: str, names: List[str]
) -> Tuple[bool, Dict[str, str]]:
Expand All @@ -180,10 +190,10 @@ def _declared_types_native(
if current in seen or current not in rows:
continue
seen.add(current)
for text in rows[current].get("properties") or []:
m = _PROPERTY.search(text)
if m and m.group(1) in names:
declared.setdefault(m.group(1), m.group(2))
for entry in rows[current].get("properties") or []:
name, kind_name = _property_name_type(entry)
if name in names and kind_name:
declared.setdefault(name, str(kind_name))
queue.extend(rows[current].get("parentTypes") or [])
return kind == "vertex", declared

Expand Down
40 changes: 40 additions & 0 deletions tests/test_schema_nested_rows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""#195: a query row nested in a value (the entries of `schema:types.properties`) reaches Python as a map on both builds.

The native codec wrote a nested Result through String.valueOf, so each entry arrived as a str and code that reads the schema
(examples/04_csv_import_documents.py) stopped with AttributeError on the native package only.
"""

import arcadedb


def _as_map(entry):
# the JVM build hands back the engine's Result object, the native build a dict
return dict(entry.toMap()) if hasattr(entry, "toMap") else entry


def test_schema_type_properties_are_maps(temp_db_path):
with arcadedb.create_database(temp_db_path) as db:
db.command("sql", "CREATE DOCUMENT TYPE Movie")
db.command("sql", "CREATE PROPERTY Movie.title STRING")
db.command("sql", "CREATE PROPERTY Movie.year INTEGER")

rows = db.query(
"sql", "SELECT properties FROM schema:types WHERE name = 'Movie'"
).to_list()
assert len(rows) == 1
entries = list(rows[0].get("properties"))
assert len(entries) == 2

maps = [_as_map(e) for e in entries]
assert all(isinstance(m, dict) for m in maps), [
type(e).__name__ for e in entries
]
by_name = {str(m["name"]): m for m in maps}
assert set(by_name) == {"title", "year"}
assert str(by_name["title"]["type"]).upper() == "STRING"
assert str(by_name["year"]["type"]).upper() == "INTEGER"

if arcadedb.__backend__ == "native":
assert all(isinstance(e, dict) for e in entries), [
type(e).__name__ for e in entries
]
Loading