Databases & Entities XML Read-only

Entity Engine & XML

The XML provider maps elements and attributes to statically typed entities, validates their identities, reconstructs mapped relationships, and executes KQL over one consistent document generation through XMLRepository<T, TId>.

Provider Contract
CapabilityXML provider behavior
Access modeRead-only typed repository.
ConsistencyThe complete file is read before parsing; bytes from two file generations are never mixed.
Identity@Id, @EmbeddedId, or @IdClass, bound to elements or attributes.
QueriesProvider-neutral KQL over materialized mapped entities.
RelationshipsOwning paths plus inverse mappedBy reconstruction; eager and fetch-join loading.
MutationNot supported by the current XML repository.
Source Document

Assume a document containing user elements. An XML identity may live in an attribute while ordinary values live in child elements.

<?xml version="1.0" encoding="UTF-8"?>
<users>
  <user id="1">
    <login>alice</login>
    <connectionNumber>4</connectionNumber>
    <active>true</active>
  </user>
  <user id="2">
    <login>bob</login>
    <connectionNumber>14</connectionNumber>
    <active>true</active>
  </user>
</users>
XML Entity Mapping

Combine provider-neutral annotations from klyn.data.mapping with physical XML paths from klyn.data.xml.

import klyn.data.mapping
import klyn.data.xml

@Entity
@XMLCollection(path="/users/user")
class User:

    @Id
    @XMLAttribute(path="@id")
    public property id as Int = 0

    @XMLElement(path="login")
    public property login as String

    @XMLElement(path="connectionNumber")
    public property connectionNumber as Int = 0

    @XMLElement(path="active")
    public property active as Boolean = false

    public User():
        pass
AnnotationPurpose
@XMLCollection(path="/users/user")Selects the absolute set of entity elements.
@XMLAttribute(path="@id")Reads an attribute from the current entity element.
@XMLElement(path="login")Reads a child element relative to the current entity element.

Paths are validated while mapping metadata is built. Missing required values, invalid scalar conversions, null identities, and incompatible property types raise an Entity Engine exception instead of producing partially initialized objects.

Open the Repository

XMLConfiguration accepts either a string path or a Path. The repository's generic identity type must exactly match the effective identity mapping.

import klyn.data
import klyn.data.xml

configuration = XMLConfiguration("data/users.xml")
users = XMLRepository<User, Int>.open(configuration)

assert users.providerName() == "xml"
assert users.count() == 2l

Opening the repository validates mapping metadata but does not retain an open file handle. Each execution reads a complete document generation.

Typed XML Query

XML currently uses the explicit repository form because it is a direct read-only document source rather than a managed persistence context. The query remains statically typed and lowers to the same provider-neutral representation as query(em):.

frequentUsers = query:
    from users user
    where user.active and user.connectionNumber > 10
    order by user.connectionNumber desc, user.login

for user in frequentUsers.toList():
    print(user.login)

count(), exists(), first(), single(), toList(), and stream() retain their ordinary KQL contracts.

Identity Validation

Entity keys must be present and unique. validateBeforeRead() forces complete key and relationship validation before the caller consumes results.

users.validateBeforeRead()

userQuery = query:
    from users user
    where user.id == requestedId

user = userQuery.singleOrNull()

A duplicate key raises DuplicateIdentityException with the first and repeated source locations. A null or unconvertible key raises an entity mapping exception.

XML Relationships

Relationship annotations remain provider-neutral. An owning XML relationship must also declare the physical path that selects a nested entity, identity attribute, entity element, or repeated elements. An inverse mappedBy relationship declares no XML path and is reconstructed from the owning endpoint.

@ManyToOne(optional=false)
@XMLAttribute(path="@customerId")
public property customer as Customer

@OneToMany(mappedBy="customer")
public property orders as List<Order> = []

A to-many owning relationship uses @XMLElement, not @XMLAttribute. FetchType.EAGER relationships are populated automatically; lazy relationships can be requested by a KQL join fetch path.

Optional Persistent Indexes

Register an IndexDescriptor<T> for frequently filtered logical properties. The descriptor resolves its property path once, remains statically associated with the entity type, and writes type-aware index metadata beside the XML source.

configuration = XMLConfiguration(
    "data/users.xml",
    indexes=[IndexDescriptor<User>("login", unique=true)]
)
users = XMLRepository<User, Int>.open(configuration)

The sidecar belongs to one exact atomic source generation and is rebuilt when that generation changes. An index can reduce QIR row evaluation, but XML parsing remains fully buffered. Inspect query.explain() to confirm index selection; unique=true rejects duplicate indexed values during preparation.

Atomic Reads and Resource Safety
  • Replace complete XML files atomically when another process writes them.
  • Do not assume that two separate query executions observe the same file generation.
  • Use validateBeforeRead() when no item may be consumed before complete validation.
  • Strict validation pins that immutable generation for subsequent non-fetch executions on the repository.
  • Close QueryStream<T> with try-with-resources.
  • Treat external XML as untrusted input and keep entity/path limits appropriate to the application.
Next Step

Continue with Entity Engine and JSON for JSONPath-based entity mapping.