This document details the four entity type implementations: Container, Document, Category, and Index.
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
A Container is a container for child entities, similar to a filesystem folder.
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
A Document is a typed object with properties. The type_label is application-defined and used for querying.
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
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>();
A Category automatically generates Container entities based on expression evaluation over documents of a specific type.
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
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
// 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);
}
An Index provides text search over documents. Requesting a child returns a Container containing documents matching the search term.
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
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
// 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);
}
| 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 |