06-Entity-Types.md 20 KB

Entity Types

This document details the four entity type implementations: Container, Document, Category, and Index.

Entity Type Overview

graph TB
    subgraph Entity Types
        Container[Container - Container]
        Document[Document - Properties]
        Category[Category - Auto Categories]
        Index[Index - Text Search]
    end
    
    subgraph Relationships
        Container -->|contains| Container
        Container -->|contains| Document
        Container -->|contains| Category
        Container -->|contains| Index
        Category -->|generates| Container
        Index -->|returns| IndexResult
        IndexResult -->|contains| Document
    end

Container Entity

A Container is a container for child entities, similar to a filesystem folder.

Implementation

namespace Implexus.Entities {

public class Container : AbstractEntity {
    
    public Container(Engine engine, Path path) {
        base(engine, path);
    }
    
    public override EntityType entity_type { get { return EntityType.CONTAINER; } }
    
    // Child creation - only Container can create children
    public override Entity? create_container(string name) throws EngineError {
        validate_can_create_child(name);
        
        var child_path = _path.child(name);
        var container = new Container(_engine, child_path);
        
        save_entity(container);
        register_child(name);
        
        _engine.entity_created(container);
        return container;
    }
    
    public override Entity? create_document(string name, string type_label) throws EngineError {
        validate_can_create_child(name);
        
        var child_path = _path.child(name);
        var document = new Document(_engine, child_path, type_label);
        
        save_entity(document);
        register_child(name);
        register_type(type_label, child_path);
        
        _engine.entity_created(document);
        return document;
    }
    
    public override Entity? create_category(
        string name, 
        string type_label, 
        string expression
    ) throws EngineError {
        validate_can_create_child(name);
        
        var child_path = _path.child(name);
        var category = new Category(_engine, child_path, type_label, expression);
        
        save_entity(category);
        register_child(name);
        
        _engine.entity_created(category);
        return category;
    }
    
    public override Entity? create_index(
        string name, 
        string type_label, 
        string expression
    ) throws EngineError {
        validate_can_create_child(name);
        
        var child_path = _path.child(name);
        var index = new Index(_engine, child_path, type_label, expression);
        
        save_entity(index);
        register_child(name);
        
        _engine.entity_created(index);
        return index;
    }
    
    public override void delete() throws EngineError {
        // Delete all children first
        foreach (var child_name in child_names) {
            var child = get_child(child_name);
            if (child != null) {
                ((!) child).delete();
            }
        }
        base.delete();
    }
    
    private void validate_can_create_child(string name) throws EngineError {
        if (name == null || name == "") {
            throw new EngineError.INVALID_PATH("Child name cannot be empty");
        }
        var child_path = _path.child(name);
        if (_engine.entity_exists(child_path)) {
            throw new EngineError.ENTITY_ALREADY_EXISTS(
                "Entity already exists: %s", child_path.to_string()
            );
        }
    }
    
    private void save_entity(Entity entity) throws EngineError {
        var serializer = new EntitySerializer();
        var data = serializer.serialize(entity);
        _engine.configuration.storage.save_entity(entity.path, data);
    }
    
    private void register_child(string name) {
        _engine.configuration.storage.register_child(_path, name);
    }
    
    private void register_type(string type_label, Path path) {
        _engine.configuration.storage.register_type(path, type_label);
    }
}

} // namespace Implexus.Entities

Document Entity

A Document is a typed object with properties. The type_label is application-defined and used for querying.

Implementation

namespace Implexus.Entities {

public class Document : AbstractEntity {
    
    private string _type_label;
    private Invercargill.DataStructures.PropertyDictionary _properties;
    
    public Document(Engine engine, Path path, string type_label) {
        base(engine, path);
        _type_label = type_label;
        _properties = new Invercargill.DataStructures.PropertyDictionary();
    }
    
    public override EntityType entity_type { get { return EntityType.DOCUMENT; } }
    
    public override string type_label { 
        owned get { return _type_label; } 
    }
    
    public override Invercargill.Properties properties { 
        owned get { return _properties; } 
    }
    
    public override Invercargill.Element? get_property(string name) {
        return _properties.get(name);
    }
    
    public override void set_property(string name, Invercargill.Element value) {
        _properties.set(name, value);
        save();
        _engine.entity_modified(this);
    }
    
    public override void remove_property(string name) {
        _properties.remove(name);
        save();
        _engine.entity_modified(this);
    }
    
    public override Invercargill.ReadOnlySet<string> child_names { 
        owned get { 
            // Documents don't have children
            return new Invercargill.DataStructures.HashSet<string>().as_read_only();
        }
    }
    
    public override Entity? get_child(string name) {
        return null; // Documents don't have children
    }
    
    private void save() throws EngineError {
        var serializer = new EntitySerializer();
        var data = serializer.serialize(this);
        _engine.configuration.storage.save_entity(_path, data);
    }
}

} // namespace Implexus.Entities

Properties Interface

Documents implement Invercargill.Properties through PropertyDictionary:

// Using Invercargill.DataStructures.PropertyDictionary
var doc = container.create_document("user1", "User");
doc.set_property("name", new Invercargill.ValueElement("John"));
doc.set_property("age", new Invercargill.ValueElement(30));
doc.set_property("active", new Invercargill.ValueElement(true));

// Reading properties
var name = doc.get_property("name").to_value<string>();
var age = doc.get_property("age").to_value<int>();

Category Entity

A Category automatically generates Container entities based on expression evaluation over documents of a specific type.

How It Works

  1. Configure a Category with a type_label and expression
  2. When a child is requested by name, the expression is evaluated on all documents of that type
  3. A Container is returned containing documents where the expression result matches the requested name

Implementation

namespace Implexus.Entities {

public class Category : AbstractEntity {
    
    private string _type_label;
    private string _expression;
    private Invercargill.Expressions.Expression? _compiled_expression;
    
    public Category(Engine engine, Path path, string type_label, string expression) {
        base(engine, path);
        _type_label = type_label;
        _expression = expression;
        _compiled_expression = null;
    }
    
    public override EntityType entity_type { get { return EntityType.CATEGORY; } }
    
    public override string configured_type_label { 
        owned get { return _type_label; } 
    }
    
    public override string configured_expression { 
        owned get { return _expression; } 
    }
    
    // Compile expression lazily
    private Invercargill.Expressions.Expression get_compiled_expression() throws EngineError {
        if (_compiled_expression == null) {
            var parser = new Invercargill.Expressions.ExpressionParser();
            try {
                _compiled_expression = parser.parse(_expression);
            } catch (Error e) {
                throw new EngineError.EXPRESSION_ERROR(
                    "Failed to parse expression: %s", e.message
                );
            }
        }
        return (!) _compiled_expression;
    }
    
    // Child names are the unique values of the expression over all documents
    public override Invercargill.ReadOnlySet<string> child_names { 
        owned get {
            var names = new Invercargill.DataStructures.HashSet<string>();
            try {
                var expr = get_compiled_expression();
                var evaluator = new Invercargill.Expressions.ExpressionEvaluator();
                
                foreach (var doc in _engine.query_by_type(_type_label)) {
                    var result = evaluator.evaluate(expr, doc.properties);
                    if (result != null && !result.is_null()) {
                        names.add(result.to_string());
                    }
                }
            } catch (Error e) {
                warning("Error evaluating category expression: %s", e.message);
            }
            return names.as_read_only();
        }
    }
    
    // Get child returns a Container containing matching documents
    public override Entity? get_child(string name) {
        var matching_docs = new Invercargill.DataStructures.Vector<Entity>();
        
        try {
            var expr = get_compiled_expression();
            var evaluator = new Invercargill.Expressions.ExpressionEvaluator();
            
            foreach (var doc in _engine.query_by_type(_type_label)) {
                var result = evaluator.evaluate(expr, doc.properties);
                if (result != null && result.to_string() == name) {
                    matching_docs.add(doc);
                }
            }
        } catch (Error e) {
            warning("Error evaluating category expression: %s", e.message);
            return null;
        }
        
        if (matching_docs.count == 0) {
            return null;
        }
        
        // Return a virtual container containing the matching documents
        return new CategoryContainer(_engine, _path.child(name), matching_docs);
    }
    
    // Categorys cannot create children
    public override Entity? create_container(string name) throws EngineError {
        throw new EngineError.INVALID_OPERATION("Cannot create children in a category");
    }
    
    public override Invercargill.Enumerable<Entity> get_children() {
        return child_names.select(name => get_child(name))
                          .where(entity => entity != null)
                          .select(entity => (!) entity);
    }
}

} // namespace Implexus.Entities

CategoryContainer

A virtual container that contains documents matched by the category:

namespace Implexus.Entities {

internal class CategoryContainer : AbstractEntity {
    
    private Invercargill.DataStructures.Vector<Entity> _documents;
    
    public CategoryContainer(
        Engine engine, 
        Path path, 
        Invercargill.DataStructures.Vector<Entity> documents
    ) {
        base(engine, path);
        _documents = documents;
    }
    
    public override EntityType entity_type { get { return EntityType.CONTAINER; } }
    
    public override Invercargill.ReadOnlySet<string> child_names {
        owned get {
            var names = new Invercargill.DataStructures.HashSet<string>();
            foreach (var doc in _documents) {
                names.add(doc.name);
            }
            return names.as_read_only();
        }
    }
    
    public override Entity? get_child(string name) {
        foreach (var doc in _documents) {
            if (doc.name == name) {
                return doc;
            }
        }
        return null;
    }
    
    public override Invercargill.Enumerable<Entity> get_children() {
        return _documents.as_enumerable();
    }
    
    // Read-only - cannot create children
    public override Entity? create_container(string name) throws EngineError {
        throw new EngineError.INVALID_OPERATION("Cannot create children in a category container");
    }
    
    public override void delete() throws EngineError {
        throw new EngineError.INVALID_OPERATION("Cannot delete a category container");
    }
}

} // namespace Implexus.Entities

Category Usage Example

// Create documents with a "status" property
var tasks = engine.get_root().create_container("tasks");
var task1 = tasks.create_document("task1", "Task");
task1.set_property("status", new ValueElement("pending"));
task1.set_property("title", new ValueElement("First task"));

var task2 = tasks.create_document("task2", "Task");
task2.set_property("status", new ValueElement("done"));
task2.set_property("title", new ValueElement("Second task"));

var task3 = tasks.create_document("task3", "Task");
task3.set_property("status", new ValueElement("pending"));
task3.set_property("title", new ValueElement("Third task"));

// Create a category that groups by status
var by_status = tasks.create_category("by_status", "Task", "status");

// Navigate the category
var pending = by_status.get_child("pending");  // Returns Container with task1, task3
var done = by_status.get_child("done");        // Returns Container with task2

// List all status values
foreach (var status in by_status.child_names) {
    print("Status: %s\n", status);
}

Index Entity

An Index provides text search over documents. Requesting a child returns a Container containing documents matching the search term.

Implementation

namespace Implexus.Entities {

public class Index : AbstractEntity {
    
    private string _type_label;
    private string _expression;
    private Invercargill.Expressions.Expression? _compiled_expression;
    
    public Index(Engine engine, Path path, string type_label, string expression) {
        base(engine, path);
        _type_label = type_label;
        _expression = expression;
        _compiled_expression = null;
    }
    
    public override EntityType entity_type { get { return EntityType.INDEX; } }
    
    public override string configured_type_label { 
        owned get { return _type_label; } 
    }
    
    public override string configured_expression { 
        owned get { return _expression; } 
    }
    
    // Index children are opaque - cannot list them
    public override Invercargill.ReadOnlySet<string> child_names { 
        owned get {
            // Indexes have opaque children - return empty set
            return new Invercargill.DataStructures.HashSet<string>().as_read_only();
        }
    }
    
    // Get child performs text search and returns a Container with results
    public override Entity? get_child(string search_term) {
        var matching_docs = new Invercargill.DataStructures.Vector<Entity>();
        
        try {
            var expr = get_compiled_expression();
            var evaluator = new Invercargill.Expressions.ExpressionEvaluator();
            var search_lower = search_term.down();
            
            foreach (var doc in _engine.query_by_type(_type_label)) {
                var result = evaluator.evaluate(expr, doc.properties);
                if (result != null) {
                    var text = result.to_string().down();
                    if (text.contains(search_lower)) {
                        matching_docs.add(doc);
                    }
                }
            }
        } catch (Error e) {
            warning("Error evaluating index expression: %s", e.message);
            return null;
        }
        
        if (matching_docs.count == 0) {
            return null;
        }
        
        // Return an IndexResult - a Container containing matching documents
        return new IndexResult(_engine, _path.child(search_term), search_term, matching_docs);
    }
    
    // Indexes cannot create children
    public override Entity? create_container(string name) throws EngineError {
        throw new EngineError.INVALID_OPERATION("Cannot create children in an index");
    }
    
    private Invercargill.Expressions.Expression get_compiled_expression() throws EngineError {
        if (_compiled_expression == null) {
            var parser = new Invercargill.Expressions.ExpressionParser();
            try {
                _compiled_expression = parser.parse(_expression);
            } catch (Error e) {
                throw new EngineError.EXPRESSION_ERROR(
                    "Failed to parse expression: %s", e.message
                );
            }
        }
        return (!) _compiled_expression;
    }
}

} // namespace Implexus.Entities

IndexResult

The Container returned by an Index query:

namespace Implexus.Entities {

public class IndexResult : AbstractEntity {
    
    private string _search_term;
    private Invercargill.DataStructures.Vector<Entity> _documents;
    
    public IndexResult(
        Engine engine, 
        Path path, 
        string search_term,
        Invercargill.DataStructures.Vector<Entity> documents
    ) {
        base(engine, path);
        _search_term = search_term;
        _documents = documents;
    }
    
    public string search_term { get { return _search_term; } }
    
    public override EntityType entity_type { get { return EntityType.CONTAINER; } }
    
    public override Invercargill.ReadOnlySet<string> child_names {
        owned get {
            var names = new Invercargill.DataStructures.HashSet<string>();
            foreach (var doc in _documents) {
                names.add(doc.name);
            }
            return names.as_read_only();
        }
    }
    
    public override Entity? get_child(string name) {
        foreach (var doc in _documents) {
            if (doc.name == name) {
                return doc;
            }
        }
        return null;
    }
    
    public override Invercargill.Enumerable<Entity> get_children() {
        return _documents.as_enumerable();
    }
    
    // Read-only
    public override Entity? create_container(string name) throws EngineError {
        throw new EngineError.INVALID_OPERATION("Cannot create children in an index result");
    }
    
    public override void delete() throws EngineError {
        throw new EngineError.INVALID_OPERATION("Cannot delete an index result");
    }
}

} // namespace Implexus.Entities

Index Usage Example

// Create documents with searchable content
var articles = engine.get_root().create_container("articles");
var article1 = articles.create_document("article1", "Article");
article1.set_property("title", new ValueElement("Introduction to Vala"));
article1.set_property("content", new ValueElement("Vala is a programming language..."));

var article2 = articles.create_document("article2", "Article");
article2.set_property("title", new ValueElement("Advanced Vala Techniques"));
article2.set_property("content", new ValueElement("This article covers advanced..."));

// Create an index over the content property
var search = articles.create_index("search", "Article", "content");

// Search for documents
var results = search.get_child("Vala");  // Returns IndexResult with article1, article2
var intro_results = search.get_child("Introduction");  // Returns IndexResult with article1

// Navigate results
foreach (var doc in results.get_children()) {
    print("Found: %s\n", doc.name);
}

Entity Type Comparison

Feature Container Document Category Index
Can contain children Yes No Virtual Virtual
Has properties No Yes No No
Has type_label No Yes No No
Child names enumerable Yes - Yes No
Expression-based No No Yes Yes
Children are persistent Yes - No No
Can create children Yes No No No