This document outlines the structure and implementation plan for an examples directory containing a comprehensive demo program showcasing the Invercargill-Sql ORM features.
The examples directory will contain a single executable target that demonstrates all high-level ORM features through a cohesive demo application. The example uses a simple e-commerce domain model with users, products, and orders.
examples/
├── meson.build # Build configuration for demo executable
├── entities/
│ ├── user.vala # User entity
│ ├── product.vala # Product entity
│ └── order.vala # Order entity
├── projections/
│ ├── user-summary.vala # Simple projection example
│ ├── order-detail.vala # Join projection example
│ └── sales-report.vala # Aggregate projection example
├── migrations/
│ ├── v001-create-users.vala
│ ├── v002-create-products.vala
│ └── v003-create-orders.vala
└── demo.vala # Main demo program
Each entity class includes a static configure_mapper method that encapsulates its ORM mapping configuration.
public class User : Object {
public int64 id { get; set; }
public string name { get; set; }
public string email { get; set; }
public int64 age { get; set; }
public bool is_active { get; set; }
public User() {
name = "";
email = "";
}
public static void configure_mapper(EntityMapperBuilder<User> b) {
b.table("users")
.column<int64?>("id", u => u.id, (u, v) => u.id = v)
.column<string>("name", u => u.name, (u, v) => u.name = v)
.column<string>("email", u => u.email, (u, v) => u.email = v)
.column<int64?>("age", u => u.age, (u, v) => u.age = v)
.column<bool?>("is_active", u => u.is_active, (u, v) => u.is_active = v);
}
}
public class Product : Object {
public int64 id { get; set; }
public string name { get; set; }
public string category { get; set; }
public double price { get; set; }
public int64 stock { get; set; }
public Product() {
name = "";
category = "";
}
public static void configure_mapper(EntityMapperBuilder<Product> b) {
b.table("products")
.column<int64?>("id", p => p.id, (p, v) => p.id = v)
.column<string>("name", p => p.name, (p, v) => p.name = v)
.column<string>("category", p => p.category, (p, v) => p.category = v)
.column<double?>("price", p => p.price, (p, v) => p.price = v)
.column<int64?>("stock", p => p.stock, (p, v) => p.stock = v);
}
}
public class Order : Object {
public int64 id { get; set; }
public int64 user_id { get; set; }
public int64 product_id { get; set; }
public int64 quantity { get; set; }
public double total { get; set; }
public string status { get; set; }
public DateTime? created_at { get; set; }
public Order() {
status = "";
}
public static void configure_mapper(EntityMapperBuilder<Order> b) {
b.table("orders")
.column<int64?>("id", o => o.id, (o, v) => o.id = v)
.column<int64?>("user_id", o => o.user_id, (o, v) => o.user_id = v)
.column<int64?>("product_id", o => o.product_id, (o, v) => o.product_id = v)
.column<int64?>("quantity", o => o.quantity, (o, v) => o.quantity = v)
.column<double?>("total", o => o.total, (o, v) => o.total = v)
.column<string>("status", o => o.status, (o, v) => o.status = v)
.column<DateTime?>("created_at", o => o.created_at, (o, v) => o.created_at = v);
}
}
Each projection class includes a static configure_projection method that encapsulates its projection definition.
Simple projection demonstrating basic field selection.
public class UserSummary : Object {
public int64 user_id { get; set; }
public string user_name { get; set; }
public string email { get; set; }
public UserSummary() {
user_name = "";
email = "";
}
public static void configure_projection(ProjectionBuilder<UserSummary> p) {
p.source<User>("u")
.select<int64?>("user_id", "u.id", (x, v) => x.user_id = v)
.select<string>("user_name", "u.name", (x, v) => x.user_name = v)
.select<string>("email", "u.email", (x, v) => x.email = v);
}
}
Join projection demonstrating entity relationships.
public class OrderDetail : Object {
public int64 order_id { get; set; }
public string user_name { get; set; }
public string product_name { get; set; }
public int64 quantity { get; set; }
public double total { get; set; }
public string status { get; set; }
public OrderDetail() {
user_name = "";
product_name = "";
status = "";
}
public static void configure_projection(ProjectionBuilder<OrderDetail> p) {
p.source<User>("u")
.join<Order>("o", "u.id == o.user_id")
.join<Product>("p", "o.product_id == p.id")
.select<int64?>("order_id", "o.id", (x, v) => x.order_id = v)
.select<string>("user_name", "u.name", (x, v) => x.user_name = v)
.select<string>("product_name", "p.name", (x, v) => x.product_name = v)
.select<int64?>("quantity", "o.quantity", (x, v) => x.quantity = v)
.select<double?>("total", "o.total", (x, v) => x.total = v)
.select<string>("status", "o.status", (x, v) => x.status = v);
}
}
Aggregate projection demonstrating GROUP BY and aggregate functions.
public class SalesReport : Object {
public string category { get; set; }
public int64 total_orders { get; set; }
public double total_revenue { get; set; }
public double avg_order_value { get; set; }
public SalesReport() {
category = "";
}
public static void configure_projection(ProjectionBuilder<SalesReport> p) {
p.source<Order>("o")
.join<Product>("p", "o.product_id == p.id")
.group_by("p.category")
.select<string>("category", "p.category", (x, v) => x.category = v)
.select<int64?>("total_orders", "COUNT(o.id)", (x, v) => x.total_orders = v)
.select<double?>("total_revenue", "SUM(o.total)", (x, v) => x.total_revenue = v)
.select<double?>("avg_order_value", "AVG(o.total)", (x, v) => x.avg_order_value = v);
}
}
Creates the users table with indexes.
Creates the products table with category index.
Creates the orders table with foreign key indexes.
The main demo program demo.vala is organized into clear sections:
configure_mapper methodsconfigure_projection methodsThe demo program accepts an optional connection string argument:
void main(string[] args) {
string connection_string = args.length > 1
? args[1]
: "sqlite::memory:";
// ... rest of demo
}
Usage examples:
# Default: in-memory database
./orm-demo
# Custom SQLite file
./orm-demo sqlite:///path/to/database.sqlite
# Read-only connection
./orm-demo sqlite:///data/app.sqlite?mode=ro
Using the static configuration methods, registration becomes clean and self-documenting:
// Entity registration using static configure_mapper method
session.register_with_schema<User>("users", User.configure_mapper);
session.register_with_schema<Product>("products", Product.configure_mapper);
session.register_with_schema<Order>("orders", Order.configure_mapper);
// Projection registration using static configure_projection method
session.register_projection<UserSummary>(UserSummary.configure_projection);
session.register_projection<OrderDetail>(OrderDetail.configure_projection);
session.register_projection<SalesReport>(SalesReport.configure_projection);
The examples/meson.build file:
# Demo executable
demo_sources = files(
'demo.vala',
'entities/user.vala',
'entities/product.vala',
'entities/order.vala',
'projections/user-summary.vala',
'projections/order-detail.vala',
'projections/sales-report.vala',
'migrations/v001-create-users.vala',
'migrations/v002-create-products.vala',
'migrations/v003-create-orders.vala'
)
demo_exe = executable('orm-demo', demo_sources,
dependencies: [dependencies, invercargill_sql_dep],
link_with: invercargill_sql
)
examples/ directory structureexamples/meson.buildexamples/demo.valasrc/meson.build to include examples subdirectoryThe example code should:
When run, the demo should produce output similar to:
=== Invercargill-Sql ORM Demo ===
--- Running Migrations ---
Applied migration: V001_CreateUsers (version 1)
Applied migration: V002_CreateProducts (version 2)
Applied migration: V003_CreateOrders (version 3)
Current database version: 3
--- Inserting Data ---
Inserted user: Alice (ID: 1)
Inserted user: Bob (ID: 2)
Inserted user: Charlie (ID: 3)
Inserted product: Widget (ID: 1)
Inserted product: Gadget (ID: 2)
Inserted product: Doohickey (ID: 3)
Inserted 5 orders
--- Querying Entities ---
All users: 3
Users over 25: 2
Users ordered by age: Bob(22), Alice(30), Charlie(35)
--- Updating Data ---
Updated Alice's age: 31
Verified update: Alice is now 31
--- Deleting Data ---
Deleted user: Bob
Remaining users: 2
--- Querying Projections ---
User summaries: 2
Order details: 5
Sales by category: 3
=== Demo Complete ===