Advanced topics Klyn Entity Framework KQL

Entities and Typed Queries

The Klyn Entity Framework unifies typed entity mapping, persistence, schema management, and KQL queries under klyn.data. SQL, memory, JSON, and XML sources expose the same provider-neutral entity and query concepts without silently evaluating unsupported operations on the client.

The Programming Model

EntityManagerFactory owns immutable mapping metadata and shared provider state. Each short-lived EntityManager owns an identity map, a transaction, and one unit of work. A Repository<T, TId> is deliberately query-only: entity lifecycle operations belong to the manager so every read and write observes one persistence context.

TypeResponsibility
EntityManagerFactoryValidated entity model, provider configuration, schema manager, and manager creation.
EntityManagerIdentity tracking, find, persist, merge, remove, transactions, and flushing.
Repository<T, TId>Typed source used by KQL. The key type can be inferred from the mapping.
Query<T>Immutable lazy query executed explicitly with toList(), first(), count(), or stream().
Static translation only

A query: block is parsed and type-checked by the compiler. Unsupported expressions raise a translation diagnostic; Klyn never hides a provider limitation by downloading rows and evaluating an arbitrary method locally.

Entity Mapping

Import klyn.data.mapping for provider-neutral entity semantics and klyn.data.sql for relational bindings. Matching class, property, table, and column names are inferred; annotate only constraints or names that differ.

import klyn.data.mapping
import klyn.data.sql

@Entity
@Table(name="T_Articles")
class Article:

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public property id as Int = 0

    @Column(nullable=false, length=120)
    public property name as String

    @Column(nullable=false, precision=12, scale=2)
    public property price as Double

    public Article():
        pass

    public Article(name as String, price as Double):
        this.name = name
        this.price = price

The parameterless constructor is required by the current materializer. The same @Entity, @Id, embedding, inheritance, version, and relationship annotations can be shared by providers; physical bindings such as @Table, @Column, and @JoinColumn remain provider-specific.

SQL Configuration

SQLConfiguration selects an official direct SQL driver and its matching dialect. Klyn currently accepts mariadb, postgresql, and sqlserver. ODBC uses the separate ODBCConfiguration type and requires an explicit dialect.

import klyn.data
import klyn.data.sql

configuration = SQLConfiguration(
    driver="mariadb",
    host="localhost",
    port=3306,
    database="klyn",
    username="klyn",
    password="secret"
)

try factory = EntityManagerFactory(configuration):
    print(factory.mappedTypes)

The factory discovers linked @Entity classes once and keeps that model immutable. It is thread-safe; an EntityManager is intentionally not thread-safe and must stay inside one request, command, or worker.

Schema Management

The factory-owned SchemaManager creates, drops, validates, and compares provider structures from the same mapping metadata used by queries and persistence.

try factory = EntityManagerFactory(configuration):
    schema = factory.schema

    if not schema.exists(Article.type):
        schema.create(Article.type)

    schema.validate(Article.type)

    plan = schema.diff()
    if not plan.empty:
        print(plan.preview)
        schema.apply(plan)

create(), drop(), exists(), and validate() also have no-argument forms that operate on every entity known to the factory. Existing structures are validated rather than altered silently.

Transactions And Entity Lifecycle

Use try-with-resources for both factory and manager. Register mutations on the manager, flush with saveChanges(), and commit or roll back the manager-owned transaction.

try factory = EntityManagerFactory(configuration),
    manager = factory.createEntityManager():

    transaction = manager.transaction
    transaction.begin()
    try:
        manager.persist(Article("Mechanical keyboard", 129.90))
        manager.persist(Article("Desk lamp", 42.00))
        manager.saveChanges()
        transaction.commit()
    catch error as Exception:
        if transaction.active:
            transaction.rollback()
        throw error

Use manager.find<Article>(id) for identity lookup, manager.merge(detached) for detached state, and manager.remove(entity) for deletion. Repeated loads of the same identity within one manager return its managed instance.

Typed KQL Queries

Obtain a repository from the manager, then build an immutable query with the native query: expression. repository<Article>() infers the key type from the unique @Id; repository<Article, Int>() is the mandatory explicit form.

minimum = 50.0
maximum = 150.0

try factory = EntityManagerFactory(configuration),
    manager = factory.createEntityManager():

    articles = manager.repository<Article>()
    selected as Query<Article> = query:
        from articles article
        where article.price >= minimum and article.price <= maximum
        order by article.price asc

    for article in selected.toList():
        print(article.name + ": " + String(article.price))

Query variables and result types are checked at compile time. Execution remains lazy until a terminal operation is called. Scalar and immutable captures are copied when the query is created; use Parameter<T> for an explicitly late-bound value.

Provider Scope
Provider0.1.5 role
SQLTyped KQL, identity tracking, CRUD, transactions, and schema management.
MemoryTyped repositories and in-memory KQL over mapped entities.
JSONRead-only typed repository with JSON field/path mapping.
XMLRead-only typed repository with XML element/attribute mapping.

Provider capabilities are explicit. Applying an annotation or operation that the active source cannot honor raises a mapping, translation, or capability exception as early as possible.

Relationships

The unified mapping package includes @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, cascades, fetch strategies, join metadata, ordering, embedded values, inheritance, and optimistic versioning. Their defaults follow JPA conventions. A provider must reject relationship behavior it cannot implement; it must never return a silently incomplete graph.

@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="categoryId", nullable=false)
public property category as Category
Complete Sample

samples/sql/Entities.kn is a complete MariaDB desktop application. It creates an article table through SchemaManager, inserts ten entities in one transaction, and executes a typed KQL price query when the user presses Search.

klyn samples/sql/Entities.kn
  • Keep one manager per logical unit of work and never share it between threads.
  • Use repositories for typed queries and the manager for every entity mutation.
  • Keep transactions short and always roll back the exceptional path.
  • Never log credentials or construct raw SQL from untrusted input.
  • Use explain() or toProviderText() for redacted query diagnostics.