Sequences and lookups
Two query patterns recur often enough that Liberty ships a wizard for each:
| Pattern | Purpose | Typical SQL |
|---|---|---|
| Sequence | Compute the next value for an id column. | SELECT COALESCE(MAX(<col>), 0) + 1 AS NEXT_ID FROM <table> |
| Lookup | Supply value + label pairs for a dropdown bound to a screen column. | SELECT <value>, <label> FROM <table> ORDER BY <label> |
Both go through the Scaffold modal — tick a table, tick column(s), get a live SQL preview, save. The save writes two files: the query into the connector and the matching entry into the dictionary, atomically.
Where they live
Settings → Connectors → pick a connector → mode bar: Sequences (or Lookups) → + Add sequence (or + Add lookup).
The modal opens, introspects the pool, lists the schemas / tables.
Sequence — generate the next id
A sequence in Liberty is a query that returns one number — the next id to assign to a new row. It backs the dictionary's SEQUENCE rule: a screen field marked SEQUENCE reads from the sequence query each time the dialog opens for INSERT.
Field by field
| Field | Notes |
|---|---|
| Table | The source. Schema picker shows up on multi-schema pools. |
| Key column | The column whose MAX(...) + 1 gives the next value. The pool introspection lists every column with its SQL type. |
| Optional WHERE | The body of a WHERE clause (without the keyword). Use :placeholder params if the filter needs to vary per call — e.g. region = :region to scope the sequence per region. |
| Query name | Auto-suggested as get_<keyCol>_from_<table>_get (kept short via slugify). Override freely. The _get suffix is convention — sequences are read queries. |
| Sequence id | The key under [sequences.*] in the dictionary. Auto-suggested as get_<keyCol>_from_<table>. |
| Description | Shown in listings and tooltips. Auto-filled as "Get <col> from <table>". |
| Generated SQL | Live preview. Editable — your edits stick until you change the column / table / WHERE inputs, which restores the generated form. |
What gets saved
| File | Entry |
|---|---|
| Connector file | { name = "<queryName>", type = "sequence", sql = "..." } |
| Dictionary file | [connectors.<conn>.sequences.<seqId>] query = "<queryName>", dd_id = "<keyCol>", optional description |
The dictionary entry is what the SEQUENCE rule looks up. A column on a screen marked rule SEQUENCE with sequence = "<seqId>" calls this query when the dialog opens for INSERT and pre-fills the field with the result.
Concurrency note
SELECT MAX(...) + 1 is not atomic — two simultaneous INSERTs can both read the same MAX and produce a collision. Adequate when:
- Your INSERT happens behind a transactional UI (the user sees one dialog at a time).
- The id column has a database-level uniqueness constraint that will reject the duplicate (the screen reports the error and the operator retries).
For high-concurrency workloads, define the column as a database SEQUENCE (Postgres SEQUENCE, Oracle SEQUENCE, Identity column) and have the sequence query return nextval(...) instead — the database guarantees uniqueness.
Lookup — supply a dropdown's options
A lookup in Liberty is a query that returns rows of (value, label) pairs — the source for a screen column's dropdown. It backs the dictionary's LOOKUP rule: a column marked LOOKUP with lookup = "<id>" renders as a dropdown sourced from the lookup query.
Field by field
| Field | Notes |
|---|---|
| Table | The source of the dropdown rows. |
| Value column | The code stored on the row when the user picks an option. Type matters — VARCHAR(8) value column expects a VARCHAR target column on the screen. |
| Label column | The text shown to the user. Pick Same as value column when the value is already human-readable (e.g. country code displayed as code). |
| Optional WHERE | Body of a WHERE clause without the keyword. Common patterns: active = 'Y', deleted_at IS NULL. Use :placeholder params for filter-cascaded lookups (e.g. show only regions for the selected country — see Parameter binding). |
| Query name | Auto-suggested as get_<table>_get. |
| Lookup id | The key under [lookups.*] in the dictionary. Auto-suggested as get_<table>. |
| Description | Shown in listings and tooltips. |
| Generated SQL | Live preview, editable. The ORDER BY <label> is added automatically — drop it if the rows have an intrinsic order. |
What gets saved
| File | Entry |
|---|---|
| Connector file | { name = "<queryName>", type = "lookup", sql = "..." } |
| Dictionary file | [connectors.<conn>.lookups.<lookupId>] query = "<queryName>", value = "<valueCol>", label = "<labelCol>", optional description |
Cascading lookups
When the dropdown should be filtered by another field on the same screen — e.g. Role depends on Application — the lookup needs a :placeholder in the WHERE clause, and the screen's column declares a filter_from referencing the parent. The wiring lives on the screen side (Concepts → Form conditions until the Screens build section ships); the lookup query itself just needs the :placeholder.
SELECT role_id, role_name
FROM roles
WHERE app_id = :app_id
ORDER BY role_name
What's next
- Parameter binding — for sequences/lookups that take parameters from screen context.
- Per-dialect SQL variants — for a lookup that hits Postgres and Oracle.
- Concepts → Dictionary — the wider reference:
SEQUENCE,LOOKUP,LOGIN,SYSDATEand the other dictionary rules.