Quickstart#
In this example, Acme stores orders for itself and its customer Globex. You will write a contract that lets each tenant query its own orders, excludes invalid amounts, and allows queries only for reporting.
You will create two small files and use the peql command. You do not need a running server, an account or a separate parcel installation.
1. Install peQL#
On macOS or Linux:
curl -LsSf https://github.com/griot-cloud/peql/releases/latest/download/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
On Windows, in PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://github.com/griot-cloud/peql/releases/latest/download/install.ps1 | iex"
Follow any PATH instructions printed by the installer, then check that the command is available:
peql --version
If you prefer to install manually, download a binary for your operating system from peQL releases.
2. Create a workspace and some data#
Create an empty directory and move into it. These commands work in a Unix shell or PowerShell:
mkdir peql-demo
cd peql-demo
Use a text editor to save the following as orders.csv inside that directory:
order_id,tenant_id,amount
1,acme,120
2,globex,80
3,globex,-10
4,acme,200
Each row is an order. tenant_id identifies who it belongs to. Order 3 has a negative amount; our contract will keep it out of query results.
3. Describe the rules in a contract#
Save this as orders.yaml in the same directory:
contract: sales/orders
version: 1
owner: acme
binding:
parquet: data/orders/
expose:
- {name: order_id, type: int64}
- {name: amount, type: int64}
rules:
- id: reporting_only
op: decide
expr: ctx.purpose == 'reporting'
- id: own_orders
op: admit
expr: row.tenant_id == ctx.tenant
- id: positive_amount
op: assert
expr: row.amount > 0
on_fail: drop
The contract is called sales/orders and is owned by acme. Its binding points to the directory where peQL will write the data. Only order_id and amount are exposed to SQL; the rules can still read tenant_id.
The three rules do different jobs:
reporting_onlyrefuses callers whose purpose is notreporting.own_orderskeeps only rows whose tenant matches the caller’s tenant.positive_amountexcludes rows whose amount is zero or negative.
row refers to a data record; ctx refers to the caller. See the parcel documentation for expressions and other kinds of rule.
4. Register the contract and write the data#
Run this from peql-demo:
peql write orders.yaml --input orders.csv
Because you supplied a contract file, this command registers the contract before it writes. peQL uses the CSV’s column types to check the contract. The write is then refused, because order 3 breaks positive_amount:
refused: `sales/orders` refused rows that break its rules: positive_amount (1 row)
By default a write commits nothing when any row breaks a rule. Write the rows that pass and set the other aside:
peql write sales/orders --input orders.csv --on-violation exclude --excluded rejected.parquet
peQL writes Parquet under data/orders/ and validates the rows it wrote. The report shows three rows written and one row of positive_amount set aside in rejected.parquet, with a _violations column naming the rule it broke. --on-violation warn would store all four rows instead; queries would still leave order 3 out.
The original CSV stays unchanged. peQL also creates _peql/ for its registered contracts and state. Running the same write command again replaces the contract’s Parquet data; use --append only when you want to add more rows.
5. Run the same query as two callers#
First, query as an Acme analyst. The examples use single quotes around SQL and double quotes around the contract name in a Unix shell or PowerShell 7.3 and newer.
peql query 'SELECT order_id, amount FROM "sales/orders" ORDER BY order_id' --id acme-analyst --tenant acme --purpose reporting
You should receive:
+----------+--------+
| order_id | amount |
+----------+--------+
| 1 | 120 |
| 4 | 200 |
+----------+--------+
The SQL has no tenant filter. The contract supplied it using --tenant acme.
Before Globex can query, Acme’s operator must share the contract with that tenant:
peql publish sales/orders --to globex
Publishing makes the contract visible to Globex. Its rules still apply. Run the same SQL with a different caller:
peql query 'SELECT order_id, amount FROM "sales/orders" ORDER BY order_id' --id globex-analyst --tenant globex --purpose reporting
You should receive:
+----------+--------+
| order_id | amount |
+----------+--------+
| 2 | 80 |
+----------+--------+
Order 2 belongs to Globex and passes the quality check. Order 3 also belongs to Globex, but its negative amount kept it out of the data. Acme’s orders are not returned.
These flags let you try different caller contexts locally. In an application, the application authenticates users and supplies their context to peQL.
6. See a query refused#
Keep the tenant the same but change the purpose to marketing:
peql query 'SELECT order_id, amount FROM "sales/orders" ORDER BY order_id' --id globex-analyst --tenant globex --purpose marketing
The reporting_only rule refuses the query:
refused: denied by `sales/orders` rule `reporting_only`
This is an expected result. The command exits with status 1 and returns no rows.
7. Inspect the quality result#
peql validate sales/orders
The JSON verdict reports three stored rows and no failure of positive_amount: the failing row was set aside, not stored.
Add --envelope to a successful query to see which contract and rules applied. When stored rows break a rule, each contract’s excluded in the envelope says how many the query left out. The workspace’s _peql/audit.jsonl file records your query attempts, including the refusal.
You have now used the same data and SQL with different callers, and seen both row filtering and a complete refusal. Continue to Using peQL for Python and Rust integration, or read How it works to understand how peQL applies the rules.