Composability: Functions & Imports
Composability: Functions & Imports
Trilogy models are building blocks. Two language features do most of the work: functions package reusable logic within and across queries, and imports share whole models across files and projects.
Functions
Function statements define reusable custom functions that can be called throughout your Trilogy scripts:
def double(x) -> x * 2;
Functions support type hints and default parameter values:
def calculate_tax(amount: float, rate: float) -> amount * rate;
def apply_discount(price, discount = 0.1) -> price * (1 - discount);
Call them with the @ prefix:
def format_currency(amount) -> concat('$', cast(round(amount, 2) as string));
select
order.id,
@format_currency(order.total) -> formatted_total
;
Define a metric or a formatting rule once, and every query — human- or AI-authored — uses the same definition.
Bindings can also carry value constraints. This function accepts literal rates only within the declared range:
def discounted(price: float, rate: float[0..1] = 0.1) -> price * (1 - rate);
select @discounted(100.0, 0.2) as discounted_price;
Use @lib.function_name(...) for functions from an aliased import. Functions can return boolean predicates and can package aggregate checks for use in datasource assertions.
Imports
Import statements bring concepts and definitions from other Trilogy files into the current scope:
import customer;
import models.order;
import ..parent_module;
Use as to give the import a local alias; all concepts from the imported module are namespaced under it:
import customer as c;
import models.order as ord;
After aliasing, reference concepts like c.id or ord.total.
An import without as brings definitions into the current namespace. Use aliases when two models have overlapping names.
Selective Imports
Expose just the concepts a query needs:
from customer as c import id, name;
select c.id, c.name;
Unselected concepts are hidden from the public namespace; the model retains internal dependencies needed to resolve lineage and plan the selected concepts.
Self Imports
self import as <alias>; gives the current model another role. This is useful for employees and managers, parent-child records, or origin/destination relationships that use the same underlying model.
Save this model as employee.preql:
self import as manager;
key id int;
property id.name string;
datasource employees (
employee_id: id,
employee_name: name,
manager_id: ~?manager.id,
)
grain (id)
address employees;
Query it from another file:
import employee as employee;
where employee.id is not null
select employee.id, employee.name, employee.manager.name;
The manager_id binding connects each employee to the manager role. It is partial because not every employee is a manager, and nullable because some employees have no manager. The alias retains the model's properties, so employee.manager.name resolves through that relationship. The where clause keeps the employee population, excluding rows from only the manager side while retaining employees whose manager is null. A self import needs a file context; import the saved model when using the Python SDK.
Standard Library
The standard library ships with Trilogy and provides common types and utilities:
import std.money;
import std.display;
Putting Them Together
A typical project defines shared models — concepts, datasources, types, and functions — in dedicated files, then composes them in queries:
import order as order;
import std.money;
select
order.customer.id,
order.customer.name,
sum(order.total) -> customer_total
;
This assumes order.preql imports and connects its customer model. Independent imports do not establish a relationship by themselves: define a reusable merge in the model, or use a query-scoped join.