Query model

Every Jnu method takes a query object in the ormdb style. This page is the full grammar as the Jnu service accepts it, with the behaviour of each variant checked against the live scheduler.

The query object

const query = {
  table: 'jobs', // policy name; see the data pages
  prototype: { status: 'running' }, // partial row every result must match
  columns: ['opaqueId', 'name'], // optional: only these columns
  order: 'startTime', // or ['startTime', 'id'], or 'random'
  desc: true, // sort direction for order
  limit: 20,
  skip: 0, // with limit, enables totalRowCount
};

Property

Meaning

table

Required. The policy name, which is usually the table name. Some tables have several policies, for example jobs and jobs_public.

prototype

Required, {} for everything you’re allowed to see. Each property is a column; a plain value means equality, an object is a value descriptor (below).

columns

Restrict the returned columns. Useful on jobResults, whose value column can be large.

order

A column name, an array of column names, or 'random'. The sort direction comes from desc; one direction applies to all columns.

desc

true for descending.

limit

Maximum rows.

skip

Rows to skip. When both limit and skip are present the result carries totalRowCount, the number of matching rows before paging. With limit alone it doesn’t.

original

For change and delete: the prototype that identifies the rows to affect, while prototype holds the new values. Usually { id }.

Queries are signed with the connection’s identity and expire one hour after they’re made (expiry in makeQuery), so a prepared query can’t be replayed later.

Value descriptors

A prototype value that’s an object is a value descriptor. Properties combine with AND.

Descriptor

Matches

{ value: x }

Equal to x. The same as writing x directly.

{ in: [a, b] }

Any of the listed values. null in the list matches NULL. An empty list matches nothing.

{ notIn: [a, b] }

None of the listed values.

{ not: true, value: x }

Not equal to x.

{ min: x }, { max: x }

>= x, <= x. Both together give a range. Dates work: { min: new Date(Date.now() - 864e5) }.

{ like: '%text%' }

SQL LIKE. You supply the wildcards.

{ like: '%text%', icase: true }

Case-insensitive LIKE.

{ orNull: true, value: x }

Equal to x or NULL.

Example: your jobs that are still running or estimating, started in the last 30 days, newest first, three columns only:

const rs = await db.select({
  table: 'jobs',
  prototype: {
    status: { in: ['running', 'estimation'] },
    startTime: { min: new Date(Date.now() - 30 * 864e5) },
  },
  columns: ['opaqueId', 'name', 'startTime'],
  order: 'startTime',
  desc: true,
  limit: 20,
  skip: 0,
});

Variants

The methods on a Connection are the select variants plus change, add and delete. What each one returns, as observed on the live service:

Method

Returns

select(query)

A ResultSet.

count(query)

A number.

selectSum(column, query)

A number.

selectDistinct(column, query)

A ResultSet of rows with just that column.

selectMax, selectMin, selectAvg

A number for numeric columns. On a timestamp column selectMax fails in the client (known issue, see below).

groupCount(query, [columns])

Fails in the client (known issue, see below). Use selectDistinct and count per value instead.

search(query), isearch(query)

Meant to apply LIKE to every prototype column, but they match nothing (known issue, see below). Use the like descriptor.

change(query)

{ rowCount }. prototype is the new values, original identifies the rows. Only columns the policy lists as changeable are applied; if none survive the error is Security clause <table> removed all columns from action prototype.

changeMany, changeOrAdd

Variants of change; changeOrAdd needs both change and add.changeMiss permission.

add(query)

Inserts prototype. Few tables allow it; the worker-owner tables do.

delete(query), deleteMany

Deletes rows matching original. Allowed where the data pages list full access.

Note

Known issues as of 2026-10-03, expected to be fixed: search() and isearch() match nothing because the client builds the LIKE clause from the wrong object; groupCount() and selectMax() on a timestamp column fail inside the client’s result handling. The workarounds in the table above work today.

A ResultSet holds all rows in memory. rowCount is the number returned, totalRowCount the number matching when paging. Iterate with getFirst() and getNext(), which return false when exhausted, or await rs.rows.forEach(fn).

Errors

Error text

Meaning

Invalid operation scheduler.jnu.<table>.query

The table has no policy, or your key doesn’t carry that operation.

Column(s) 'x' not allowed by security clause <table>

You filtered on a column the policy doesn’t allow in a where clause.

Security clause <table> removed all columns from action prototype

A change touched only columns the policy forbids.

<method> query missing prototype

Thrown client-side before sending; every query needs prototype, even {}.

Queries that are allowed but match nothing return an empty result, not an error. A job that exists but belongs to someone else looks the same as a job that doesn’t exist.

Values in results

Results are KVIN-serialized, so types survive the trip:

  • Timestamps are Date objects.

  • ethAddress columns are Address objects. String(addr) gives the hex form; compare with addr.eq(other), not ===.

  • finNum columns (DCC amounts) are BigNumber objects. Number(String(x)) or x.toNumber() gives a float.

  • json columns are parsed. Integers and floats are plain numbers.

The schema is available on every connection as db.schema.tables[name], with each column’s type under __name, so a program can discover columns and types at run time.