Sources
Docs: Panes, Preferences, Policies, and Execution context. Web App: Select a Pane, open Plots, and choose a named side tab. Results, History and Details appear below its SQL, and Read by lists the Pieces that read the Plot. Under the Pane’s own Plots, Its Panes lists the Plots of the Panes it declares, to read; a Page’s Plots tab lists those alone. React source is in Programming. Adding a Plot to a Pane opens a dialog that asks for its name and one sentence on what it computes. The name seeds the Plot’s handle and its catalog address; the address stays fixed after the Plot is created, even when it is renamed. Every Plot needs a description, shown in the band under its header. While you edit SQL, an advisory check runs after a short pause. Warnings & Errors are underlined in the source, marked in the gutter, and listed in the footer under the SQL, with the other checks. Hovering one shows its message and code; one with a suggested replacement shows it as a button. The footer also lists findings without a source location, such as a Plot that blocks publication. Choosing a finding with a location moves the editor to it. A Plot’s recorded warnings and errors hold a Publish, as every Piece’s do. The panel distinguishes Advisory SQL from Server validation. A pending or unavailable check is not an all-clear. Missing catalog access or an unavailable advisory check leaves the source editable and keeps the existing save controls available. Server checks and Policies still apply when you save. Saving a Plot’s SQL records server validation diagnostics. The server also plans the SQL the way a read does, without running it: SQL the server cannot plan recordsPV_STMT_004 with the reason, so a Plot that would fail on
every read says so when you save. An aggregate in ORDER BY reads table
columns, not output aliases: ORDER BY MIN(priority) where priority is only
an alias records PV_REF_003 or PV_STMT_004. The save plans SQL that takes
parameters with the Plot’s saved parameter values, SQL that reads another
Plot against that Plot’s last output schema, and SQL that reads a Pane by name
(parables.<parable>.<pane>, <pane>_by_actor or <pane>_data) against the
Pane’s type, as Prepare does. So this holds in Personal mode, which has no
Prepare, and after an archive import. A column name that the SQL can resolve
two ways, such as ORDER BY c.actor_id beside an output column actor_id,
records PV_STMT_004: order by the output column’s name. Saving new parameter values alone checks the SQL again.
The save also runs the planning pass that Prepare runs for the Plot’s path. When that pass refuses
the Plot, the save records PV_STMT_004 with the words Prepare will give, so the Patch shows the
problem before Prepare. For example, an output column of a type that a Plot cannot hold, such as an
INTERVAL, records a finding that names the column and its type, and says to cast its expression
in the SELECT to a type such as VARCHAR or BIGINT. A refusal at Prepare keeps the planner’s
own reason after “This SQL could not be planned:”.
A parameter that has no saved value stays unbound, as it does at Prepare: the save runs Prepare’s
planning pass and skips only the execution plan, which needs a value. It records no finding for a read
of a Plot that has no output schema yet. When the SQL reads another Plot, a
column the server cannot find there is not a finding: that Plot’s last
output schema can trail its SQL. With
Published Versions, Prepare and Publish also plan every Plot with its parameter
defaults and the other Plots bound, and SQL the server cannot plan is a
Prepare finding that blocks publication. An unavailable
validator does not prevent saving: the draft and its last successful schema
stay available.
A save also checks how deep a Pipeline’s plan nests, as Prepare does
(PARABLE-8430). query-runner runs a Pipeline from encoded plan stages, and
protobuf decodes at most 100 nested levels. Each plan level costs two levels.
A table scan adds three or more for its table’s schema, and window frames and
CASE add their own. A view is never encoded, so the check does not apply to
it.
PV_STMT_005: the plan is too deep to decode. This error blocks publication, and Prepare refuses the same plan withplot_plan_failed.PV_STMT_006: the plan nests 80 levels or more. This advisory names the depth and the limit. It does not block publication.
PV_SYS_001 (unchecked).
Your SQL draft still saves, but unchecked Plots cannot be published. Saving an unchecked
Plot again checks it again, even when its SQL did not change, so an agent that
cannot reach the re-check below recovers by sending the Plot once more. A save
that does not send an unchecked Plot leaves it unchecked.
An explicit re-check of the Parable or of a Patch checks again each Plot that
is unchecked or has a stale check, with the same shared budget. It also checks
again each Plot whose check waited on a Plot its SQL reads (PV_REF_007),
since that Plot may have a schema now. It skips a Plot whose check is current
and complete, including one with an SQL error that needs an author edit. Re-checking does not change your
authored content, but it writes check results onto the Plots it checks, so a
Patch re-check runs only on a Pending Patch. A Proposed or Published Patch
cannot change, so its re-check is refused with PA-AU-004. Prepare checks a
Proposed Patch’s Plots without changing them. A re-check needs a participation on
the Parable, as a read does. A person with none gets 404 PA-AU-001, the
answer a read gives them. A Patch re-check gives the same answer to a person
with no participation who did not start the Patch.
An upload (Upload Parable) runs the same checks before it answers. Uploads
and explicit re-checks also plan Plots with saved parameters and retry Plots
that were left unchecked or waiting on another Plot’s columns. A persistent SQL
error or an unavailable planner still needs an author edit or a later re-check.
An upload does not take Plot schemas from the archive. The web app allows up to
five minutes for an upload or re-check and names the command if its request
times out. An upload that was already saved keeps its content even if its later
checks could not finish.
An uploaded Pipeline may have checked columns before it has a planned or stored
output. Planning a Plot that reads it can use those checked columns, with
each column treated as nullable because the check does not prove non-null
values. Reading that Pipeline still requires its planned output and data.
The save’s check derives the schema of SQL without parameter defaults. A save
of SQL with explicit defaults, including [], plans it right after the save
with those defaults, as publication does, and records that schema, so Plots
that read it in the same draft use it at once. Planning runs
no SQL, so a Plot that calls a model costs no call. A Plot the planner refuses
keeps its last successful schema. Publishing checks the definition, its parameters, its findings and the plan of
each Plot before it publishes the Release. A Plot whose SQL takes $ inputs needs no saved defaults to
publish: publishing plans it with the inputs unbound, as long as no output
column takes its type from one (cast the input when one does). A Report member
still needs its values, because its run binds what the definition saved.
When publication is blocked, the current Prepare findings identify the Plot and
retain the server’s reason, including after reloading the Patch. A Publish
refused by the gate names the same findings in its own message.
Publishing also judges what each Plot’s SQL reads, for the person who publishes
(PARABLE-9299). A Refresh runs a published Plot’s SQL with the Parable’s
authority, so the rows of every Plot that SQL reads go into the new Plot’s
stored rows. A read of those rows still refuses a reader whom a Plot behind them
keeps out, but the rows are copied all the same, and the new Plot’s output
schema shows that Plot’s columns. So a Publish refuses with 403 when it makes
a Plot read a Plot of this Parable that the person who publishes may not read,
by the view.preferences Policy of that Plot’s Pane. The refusal names the
person’s own Plot, never the Plot they may not read. The Publish takes the reads
from each Plot’s plan, so a join, a subquery and a GRAPH_TABLE pattern count
alike. A Plot that reads another Plot also reads what that Plot reads, a view
and a Pipeline alike. It judges every read of a Plot that the Changes add, move
to another Pane, or whose SQL they change. It judges every read of a view that
the Changes make a Pipeline, because that Plot then stores what it reads. It
also judges each read that another Plot gains from them. A read that the
published Version already holds stays, so a person with Publishing can publish
other Changes beside a Plot that the Primary published. The save’s check still
plans against every Plot, so its stored result is the same for everyone with
Primary or Publishing on the Changes. A Delivery that Parable applies by itself
publishes SQL that a person already published, so it is not judged again. A
person who publishes an update of a Workspace copy is judged.
A Plot returns exactly the columns its Pane’s Perception declares for it. A
column the declaration does not have, a declared column the SQL leaves out, or
a column of another type (text where a number is declared, say) is an error
under Warnings & Errors (PA-AU-047), judged from the schema recorded for the
saved SQL. A list or object column that the save’s check cannot type, such as
a list of structs, is judged once planning has typed the SQL. Prepare and Publish
refuse such a Plot, naming each column, because every read of it would be
refused. End a Plot’s SQL in an explicit column list,
never SELECT *.
The declared columns, their types and the name of the Pane’s kind are part of
the Pane’s Perception. Under Warnings & Errors, a reader who can read the
Plot’s SQL (view.plots) but not the Pane’s Perception (view.perception)
gets the same error with no column, type or kind in it: “Plot regionChart does
not return the rows its Pane declares. Every read of it is refused until its
columns match.”
A value that breaks the declaration when the Plot is read refuses that read
with PA-AU-047. Examples are a null where the Perception requires a value, a
number past the declared maximum, and text its scalar does not accept. The
refusal names the Plot, the column that holds the value and what is wrong with
it, never the value. The Pane shows it as the read’s error. A Plot whose SQL
reads such a Plot gets the same refusal, naming the Plot whose rows broke it.
Warnings & Errors, Prepare and Publish check the columns and their types, not
the values. A Plot that returns such a value publishes, and its first read is
refused. An outer join returns a null for a row with no match. Return a value
with COALESCE, or declare the field T | null. A NaN or an infinity, such as
a floating-point number divided by zero, reads as a null too.
A Plot needs a contract: its Pane’s Perception declares it under plots, or
its Pane’s type is an object kind, so its rows come from that type. A Plot with
neither has no contract, and every read of it is refused (“Plot requires an
authored Perception computation contract”). Prepare and Publish refuse it,
naming the Plot and its Pane. It never stops another Plot from reading: SQL
that names it is refused as not ready, and every other read goes on.
A Pane’s type is the kind of the rows its Plot returns: a Plot it owns that
its Perception does not declare returns rows of that type:
any of the type’s fields, as columns of the same names and types. Its rows are
the type picked to the columns its saved SQL returns, and it takes no input. A
column the type does not have, or one of another type, is PA-AU-047, named
against the type (“it returns quarter, which RegionSales does not have”). Add
the field to the type or leave the column out. Until the save records the SQL’s
columns, the Plot’s rows are the whole type. Return a Temporal.Date field as a
DATE column and a Temporal.DateTime field as a TIMESTAMP, with a CAST
where the source holds the day or time as text
(Perceptions).
A Lab Plot is the exception (PARABLE-7230). A Lab declares its SQL as Plots in
parable.lab.json, and the import keeps them on the Lab’s data Pane
(Lab Parables). A Parable has a Perception, but
the Plots in a Lab have none, as they can query each other. A Lab Plot’s
contract comes from the output schema that its SQL check recorded, the same
schema that types the Plots that read it: no input, and rows of exactly those
columns, each of which may be null. Each column takes the plain scalar of its
kind: Generic.Text, Generic.Float64 or Generic.Boolean, with Generic.JSON
for JSON, and lists and objects of them. A column of another kind, such as a
map, leaves the Plot with no contract, as does SQL with no recorded schema.
Such a Plot is refused as not ready, and Prepare and Publish name it with
Lab Plot <slug> has no checked output for its current SQL. When the check
did not finish (PV_SYS_001), as when a Lab is imported before the Query
Layer is ready, an editor’s next read of the draft, through a Plot read or SQL
in the Parable, checks its Plots again and reads once more. A Release needs no
re-check: Publish plans every Plot. The data Pane owns
every Lab Plot and shows no kind, so the rule that each Plot of a Pane has a
typed contract (PA-AU-049) does not apply to it. Only a Lab package import
changes a Lab Plot. A save from the Plot editor is refused. Structured
Parables keep the Perception contracts above.
To clear a Plot finding, return the Patch to Pending with Patch Proposal,
fix the SQL, propose it again, and choose Prepare Patches in the
Publishing options. Prepare runs every check on the current Patch, the Plot
checks included. A finding the fix resolved stops blocking, and Publish
Patches becomes available.
Saved findings belong to the exact SQL and selected Workspace and definition.
They replace advisory feedback for that saved text and clear when a completed
server check clears them. Changing a Plot’s SQL starts a new check.
The catalog follows the selected Workspace and draft. Earlier Releases
remain read-only, and their advisory check does not use the current catalog.
A Plot’s advisory check can also use the columns of other Plots’ unsaved SQL.
Typing a Plot back to its saved SQL withdraws its unsaved draft, including when
an earlier save is still pending. Other Plots then validate against the saved
schema again.
Structure
A Plot is a typed computation, and it always lives on a Pane. The Pane’s Perception declaresPlotContract<Input, Output> under a local Plot name, or
the Plot returns rows of the Pane’s type under its handle. SQL
is the implementation language; React rendering belongs to the Pane.
The SQL is the Plot’s implementation: it is saved with the Parable’s draft and
versioned with it. A Plot has no Policies of its own; its Pane’s Policies
govern it.
null; each other Plot is
declared. A save that adds a second undeclared Plot, or an undeclared Plot on
a Pane whose type is null, is refused with PA-AU-049. The name matches
the Plot’s handle when first saved, and the Plot must belong to the Pane. Until
the Perception declares it, the Pane’s code has no plots.activityFacts and
Preview reports that the Plot’s contract is unavailable.
Perceptions lists the
type to declare for each kind of SQL column.
Optional Artifact inputs
A Plot can run in a Workspace that has not uploaded an Artifact. Declare the Artifact’s canonical name and row type as the thirdPlotContract argument:
SELECT email, department FROM artifacts.census. When the
Artifact is absent, the Plot reads an empty table with the declared columns.
A present Artifact keeps its real rows. Missing columns, incompatible column
types, an unavailable publication, and denied access fail the read. A missing
or incompatible column fails with QL-TB-005, and an unavailable publication
fails with QL-TB-006. Optional inputs do not bypass those errors. Use
T | null for nullable columns, and declare every column as a required
property.
The Plot save checks each declared row. A row type that is not an object of
columns fails the check with PV_STMT_004, and the message names the
declaration.
Each Plot owns its declaration. A nested Plot uses its own declaration, and
one Plot cannot authorize another Plot’s missing input. An unused declaration
produces no warning. The declaration does not add the empty table to the
Workspace’s catalog listings.
The Pane reads plots.census().meta?.absentOptionalArtifacts. A nonempty list
names known absent inputs, including inputs of nested Plots or stored Reports.
It makes no claim about unlisted inputs. An empty list means the producer
recorded no absent optional inputs. A missing or null value means unknown,
as with older stored outputs. Check a canonical name in the list before
showing an upload prompt. Do not treat unknown provenance as a present upload.
A stored Report retains the input state from its production run. Uploading or
deleting an Artifact later does not change that Report’s warning. Existing
two-argument contracts keep their current behavior.
A Parable’s own data file follows the same declaration. Its table is at
artifacts.parable_<parable>.<table>
(Artifact Pool).
Files do not travel with a Release or a Deploy. A copy of the Parable in
another Workspace has no file until someone adds one there. Declare every
Parable table that a Plot reads. This Perception declares the table of
actors.csv, artifacts.parable_perceptions.actors:
actors file, the Plot reads an empty
table with the declared columns, and absentOptionalArtifacts names
artifacts.parable_perceptions.actors. The Pane can then offer an upload with
files.upload (Panes).
Other Pieces read a Plot by address. A Pane that declares the Plot’s Pane in
its Perception reaches the Plot in code as panes['<pane>'].plots['<plot>']()
or panes['<pane>'].plots.run('<plot>'). A Page holds no data, so a Page
places the Pane that owns the Plot, and that Pane renders the rows. Page code
that reads a Plot gets the note
page-plot-read.
Read by on the Plots tab lists each Page and Pane whose code names the
Plot this way.
Deleting that Plot, or its Pane, is refused, and the refusal names the readers.
The Plot’s own Pane is a reader too while its Perception still declares the
Plot in plots. To delete the Plot, remove that declaration first or in the
same save. The Pane’s own declaration does not refuse a save that deletes the
Pane with its Plots.
Deleting a Pane that another Perception declares is refused the same way.
A Plot’s SQL reads a Pane’s saved rows by name, as typed rows of the Pane’s
type (Preferences):
A field keeps its scalar, and its column carries it: a
Generic.Boolean field
is a boolean column, Finance.Money an integer, Temporal.Date a date, and
Contact.Email text. A field that may be unset keeps its scalar too: a
negated?: Generic.Boolean | null field is a boolean column. The value reads in the scalar’s canonical form, as a
stored column of that scalar holds it. So a field compares as its scalar does
(semantic types): a
Generic.Text field compared with a Contact.Email column is refused with
QL-PL-007 unless one side is cast. A Ref<T> field is its item’s id, so it
joins on the other Pane’s id. <pane> is the snake_case of the Pane’s
handle, the rule a new Plot’s slug follows: the Pane timeSpendScope is
time_spend_scope, and readerAIUsage is reader_ai_usage. Handles are
unique ignoring case, so no two Panes share a name. A Plot whose slug is a
Pane’s name keeps it, and that Pane cannot be read by name until one of them is
renamed. A field that no layer sets is null, and the Plot supplies its default
with COALESCE. The value the Pane’s code holds on the reader’s screen
(preferences.current(value)) comes before the saved rows. A filter on id
reads only that item. The SQL editor’s catalog lists each Pane it can read this
way. A Pane is never a Plot dependency: a change to its rows reads again only
the Plots that read it by name, and the Plots that read those. A Report run
keeps the moment it started, and its Plots read the rows as they were then;
reading a chosen Refresh reads the rows as of that Refresh. A Plot never reads
workspace.preferences
(typed-read).
Each column has the type of its field. A record, such as an interface the
Perception declares, is a struct column. A list of records is a list of
structs, also when the list may be null. SQL reads them as it reads other
structs and lists: unnest(concepts) gives one row for each element,
cardinality(concepts) counts the elements, and concept['name'] reads one
field of a record. A record’s fields have their own types, and each field can
be null. A value that a struct cannot hold is its JSON text: a map, a tuple, a
mix of types, a list of lists, or a record that holds itself, directly or
through other records. The JSON functions read that text, as in
json_get_array(column). unnest or cardinality given text, an aggregate
such as bool_or given a type it does not take, or a JSON function given a
list or a record, is refused with QL-PL-019, and the refusal names the
function.
Panes that read one Plot with the same parameters, the same Pane values, the
same reading and the same View as while a read of it is still running share
that read: the browser sends one request and each Pane gets the same rows.
Collect nested values before sorting
When a Plot collects lists or records, keepORDER BY outside array_agg.
The current engine can reserve excessive memory when it merges an ordered
aggregate of nested values. Small groups can trigger this too. A larger
memory or spill budget does not correct the estimate.
Choose the order that the result needs. For Provider references whose order
does not carry meaning, collect the lists, flatten them, then sort the values:
Reference Values
array_distinct before array_sort only when the result is a set.
flatten removes the list boundaries, including empty or null input lists.
Null elements inside a list remain and sort last. This changes the order from
an aggregate that used Provider priority or a record key.
When a separate priority determines the order, number the source records
first. Collect a record containing that number and its original list, then
sort the collected records:
Reference Records By Priority
row_key within each group to break ties. Put position first
in the record because array_sort compares fields in their declared order.
This preserves each record’s list, including null lists, empty lists, null
elements, and duplicates. It returns a list of records with position and
provider_refs, so declare that shape in the Perception. A Pane that needs
only the references can read those records in order and flatten their lists.
Do not substitute this result for a bare list without updating its consumers.
Build a group’s list once with GROUP BY. A window that copies the full
list onto every source record can consume memory in proportion to the square
of the group size. The example’s window carries only a row number. An
ORDER BY in a subquery does not guarantee the order of an unordered aggregate
after parallel execution. Keep the final array_sort.
These patterns still retain the result of each group. Split or limit an
inherently large group instead of assuming that any list fits in memory.
Plot results in the browser
Once a read answers, the browser keeps its result in two places: in the tab’s memory, and in a store on the reader’s device (IndexedDB) for 14 days after its last use. The next read of that Plot with the same parameters, the same values of the Panes the Plot reads, the same reading, the same Release or draft and the same View as answers from one of them at once. This holds after a reload, in a new tab, and on a return to a Parable. Parable authors build none of this: every Pane’s Plot hook reads this way. The store is the first copy of Provider data that Parable keeps at rest in a browser. It follows these rules:- One reader, one access state. Each time a Page resolves its Panes, the server returns a key that it derives from the reader’s whole access state on the Parable: the reader, their seat, the Policies of the Parable’s current Release, the switch rows of its filter Policies, the reader’s Workspace Roles and their permissions, and the Workspace’s Provider Policies and source permissions switches. The browser encrypts each stored result under this key and holds the key in page memory only. When any of these inputs changes, the key changes, and a result stored under the old key can neither answer nor be decrypted.
- What is never stored. A View as preview, an author’s SQL preview, a read
that failed or was refused, and a read through a Platform Parable stay in tab
memory only. A read through a Platform Parable answers there only by the
catch-up rule below: the key covers this Parable’s access, not the access to
the Parables it reads. A reader with All data access, a staff member, an impersonation,
a Workspace that enforces source permissions, and a Parable whose
filter Policy reads data through
in_plotget no key, so nothing of theirs is stored. - A chosen Refresh. Its rows never change, so its result never reads again. Each Refresh in the menu keeps its own results: a return to yesterday’s Refresh after today’s arrived is instant. When a Refresh leaves the menu, its results go.
- Latest, and a Plot that reads a stored output. A Pipeline, or a Plot that reads one, answers from the store until Latest moves to a newer Refresh. Then its results go and it reads from the server.
- Latest, and a live Plot. It answers at once. When its rows are older than a minute, one read goes to the server in the background, after the Page is drawn. When that read finds other rows, the Pane takes them in place: no reload, no remount, and the chart’s marks move to their new values. The same rows in another order are no change.
- A slow store. On Latest, a read waits for the store while the store looks for the result. When the store has not found it after 60 ms, as on a page that is still starting, the read also goes to the server, and the first answer shows. When the store found the result, the read waits for the store and sends nothing to the server. A chosen Refresh never reads from the server beside the store.
- A Page that opens again. While the Page resolves its Panes, the browser reads the results that the Page used the last time. When the key arrives, it decrypts them at once, so the Page’s first reads find them ready.
- Sign-out. A logout deletes the store on that Workspace’s host: the logout page opens on that host and deletes the store before it goes to the sign-in page. A session that only expired keeps the store locked: no key, so nothing answers, until the same person signs in again.
- Another person in the same browser. When a page of another person opens on the host, on any route, every open tab of the host forgets the keys and closes the store. The store empties itself before its next first read.
- A tab that stays open. The server checks the reader’s access state again when the tab becomes visible, and in the background on a read when the last check is a minute old. When the last check is five minutes old, the read waits for a new check before it answers. A background read that the server refuses as signed out (401) locks the store in every tab and clears the Pages. A background read that the server refuses for access (403) deletes that result, and its Pane reads again. A Pane resolution that the server refuses deletes every result kept under that access state and clears the Page.
- When Latest moves to a newer Refresh, every result that read a stored output on Latest is dropped, whether the move shows while the Pages read Latest, while they read a chosen Refresh, or on a return to a Parable that was closed when it moved. A live Plot’s result stays and reads again in the background.
- When anyone saves or deletes a row of a Pane, the reader included, in another tab, another session or through Ponder, the open Pages update that Pane’s rows in place, and only the results of the Plots that read the Pane by name, or read such a Plot, are dropped and read again, in tab memory and in the store. A chosen Refresh’s results of live Plots stay: they read rows as of their run. Where the platform cannot tell which Panes a Plot reads, every result is dropped.
- When a seat on the Parable, a row that a Policy reads, a Provider Policy or a source permissions switch changes, the reader’s access is decided again: the tab drops what it kept in memory, and every open Page resolves its Panes again before it reads. A narrowed Policy returns fewer rows rather than refusing the read. The stored results are not deleted: the change gives a new key, so they never answer again.
- When a Plot read is refused, the Page resolves its Panes again. The Panes read again with no rows only when the reader’s access changed. Any other refusal drops every result the tab kept in memory.
- A tab hears row, seat, Provider Policy and source permissions changes for a Parable only while that Parable is open in it. A result with a key answers at once on a return, because the Page’s Pane resolution confirmed the reader’s access state again. A result with no key answers only once the tab has heard every change it missed meanwhile. A Pane that asks before then waits for it, up to two seconds, and then reads from the server. When too much changed to catch up on, every result kept in that Workspace is dropped, and the stored Latest results of that Workspace too.
breakCache(): that read goes to the server and its answer replaces the result
in memory and in the store. rerun() runs the hook again and is answered from
a kept result when there is one.
Read by does not list another Plot whose SQL reads this Plot by its catalog address,
and deleting a Plot that only SQL reads is not refused.
Plots read as views
A Plot nothing stores is read as a view of its SQL. A stored Plot is a Pipeline (its Pipeline switch,requiresReportingRun, is on). Report plans
are the only schedule, so a Plot runs on a schedule only as part of its
Report. Every other Plot whose SQL binds no parameters is a view. SQL that names
parables.{parable}.{plot} for a view, in another Plot, a Pane’s query or a
Report run, runs the view’s SQL as part of its own, so small Plots compose the
way SQL views do: a Plot can union one mapping Plot per Provider, each
of which can read further views.
Views compose; they do not make reads fast. A view’s SQL runs inside every read
that names it, once for each place it is named, so a Plot that unions many
mapping Plots is computed again for every Pane that reads it. Parables are
product experiences, and a Page must open fast: make any expensive query a
Pipeline, judged by what it scans, joins, aggregates or calls, and let other
Plots and Panes read its stored output. Work down from what each Page shows first. Readers see how
old stored data is and can refresh it; see
Reports.
To measure instead of judging, record in the editor drawer’s Performance
tab and open the Page a few times: each Plot read shows its time, rows and
size, whether it was live or stored, and where its server time went. A live
read that holds the Page back on every open is a Pipeline, and the tab’s hints
say so. Ponder reads the same recording while you record.
- Columns. A view serves its output schema, the columns a stored table of
the Plot would serve: the schema publication planned for its SQL, or, for
SQL a draft added or changed, the schema the draft’s last save derived, with
every column nullable. If its SQL no longer returns those columns with those
types, the read refuses until the Plot is planned again, or for a draft,
until a save derives its schema again. A value of the declared type in
another storage is served as declared: text that SQL functions return as
Utf8VieworLargeUtf8(CAST(… AS VARCHAR),string_agg) reads asUtf8, and a timestamp in another unit in the declared unit. A finer timestamp, such as the nanosecondsCAST(x AS TIMESTAMP)returns, is rounded down, so an instant never moves into the next second or day; inside a list or struct it refuses the read. - Definition. A Pane read and a Plot preview read each view from the definition they show: a draft reads the draft’s SQL, so an author who previews, in a Patch, a Plot that unions mapping Plots sees their draft mapping Plots, including Plots only the draft has, and a Release reads that Release’s SQL. SQL outside a Parable reads the Release the Workspace serves.
- Views of views. A view can read another view. Views that read each other
are refused: reading one answers
Plot views form a cycle: … reads …. Publication refuses them with a 409 that names the cycle, such asPlot a reads Plot b reads Plot a. Change one Plot so these Plots do not read each other.Publish and automatic Delivery find the cycle after planning fails: they resolve the current SQL and name only the Plots in a closed cycle. A Plot that only reads a cycle is not named. Old saved schemas, reference pins, SQL comments, and string values cannot prove a cycle. Successful publication adds no validation request for this diagnosis. - Report runs. A Pipeline may read views of its own Parable. The run computes each view inside its own plan, so the run’s pinned inputs are the Provider tables and stored Plots under the views. A view of the run’s Parable must come from the Release the run captured; a run captured before a later publication of the view is refused, and running the Report again uses the new Release. A run whose view the Workspace catalog serves from an earlier Release is refused with that cause instead: the catalog has not caught up with the captured Release, or that Release was retracted. A run a few minutes later reads the Release the Workspace serves.
plots.activityFacts()is a hook exposingstatus,rows,refreshing,rerunandbreakCache. Pane code draws three states: reading (statusisrunningwith no rows yet), empty (readywith no rows) and rows. A read that fails never reaches it: the host shows the failure in the Pane’s place, with Retry (below).plotApi.run('activityFacts')runs from an action handler.result.metaidentifies the data that answered the request, including a resolved Report when relevant.
PaneComponent.plots), without rendering it.
status is running while a read with no rows on screen is in flight, the
first one included, then ready. rows is empty until the first answer
arrives, so empty rows mean no data only when status is ready. Render the
states in this order, with no error branch:
rerun. Returning to the window does
not ask again. A refusal asks every other Pane that shows rows to read again
under the current access; a Pane still reading keeps its read, and refusals of
reads that began before that refresh ask for no other. A refused Plot read of a
Page whose Pane resolution returned a store key resolves the Page’s Panes
again instead, and the other Panes keep their rows unless the reader’s access
changed. A Page whose Plots the reader’s Policies refuse sends one read per
Plot.
A failed read’s error has a message and a code. The code is the server’s
error code when it supplies one, such as QL-TO-002; a bridge code such as
TIMEOUT when the host does not answer; or PLOT_FAILED when the failure has
no code of its own. A server error that names no cause carries only its
service’s general code, such as web-api’s WA-SY-001, and a Pane gets
PLOT_FAILED for it too. The host’s error for a hook shows the message, and run,
preview and readReport reject with the error, for the action that called
them to handle. A read a filter Policy refuses
has the code QL-PL-015, and error.details.filter names the filter: the
Plot does not output a column the filter reads, so none of its rows can be
shown to this reader. That is not a failure to retry. For a hook the host
shows it as hidden (below); an action that gets it from run shows it the
same way, such as “Hidden by” and the filter’s name.
The hook reads again when a Preference changes, when the reader, the Workspace,
the definition, or the data reading changes, when the window regains focus, and
on rerun() and breakCache(). On the window regaining focus only a hook that
shows an answer reads again; one that failed, waits for a Refresh or is still
reading is not asked again. A kept result answers it when there
is one, except for breakCache(). When it reads again, the same Plot or the
same Plot with new parameters, it keeps the rows it shows on screen until the
new ones arrive: status stays ready and refreshing is true meanwhile, so
a Pane never blanks while it reads again, and its chart can move from the old
values to the new. A Pane can use refreshing to show that newer rows are
coming. The platform dims the Pane after 150 milliseconds while the rows on
screen are no longer what the reader asked for: rows of another parameter set,
or rows read before a Pane value the Plot reads, a Preference, a row it bound
or the context changed. A date range in a saved-data Pane is such a Pane value.
The same inputs read again, as when the window regains focus, on rerun(), or
when a background read finds newer rows, do not dim the Pane. An answer from
the browser arrives before the dim. The dim draws each top box of the Pane at
55% opacity, also a box that sets its own opacity or holds one from an entrance
animation. It fades in, but a box whose entrance animation holds its opacity
dims with no fade. The dim ends at once when the new rows arrive. For a reader
who asks for reduced motion it shows with no fade. A first
read, a read after a failure, or a read after the reader’s access changed
starts with no rows and status running, so no row read under the old access
stays on screen. A Pane that shows its empty state without checking status
reports no data during those reads.
When a background read finds newer rows for the result a hook shows, the hook
takes them in place: status stays ready, refreshing stays false, and the
Pane’s component is not mounted again. The platform moves the SVG marks that
stay on screen, and the widths and positions of HTML bars, from their old
values to the new ones. New marks appear and removed marks go at once. A mark
moves best when the Pane keys it by its category, not by its index. A Pane
that draws on a canvas, or that sets data-plot-tween="off" on an element,
draws its new rows at once, and so does every Pane for a reader who asks for
reduced motion.
The hook waits 70 seconds for a read. The application ends a Plot data request
at 55 seconds and the read then fails with that error, so a slow read stays
running until its rows or its failure arrive. A first read with no answer
after 70 seconds fails with the code TIMEOUT. A read of the same parameters
again with no answer keeps the rows it left on screen, still ready, and
refreshing returns to false. A read of new parameters with no answer fails:
another parameter set’s rows never stand as its answer.
A Pane resolution waits up to 60 seconds. A save of Preference rows never
publishes or proposes, so it answers like any other host call. Other host
calls wait 10 seconds, except SQL that a Lab runs with query, which waits
70 seconds.
A Plot hook retries a short server failure (HTTP 500, 502 or 503) twice: after
one second, then after two seconds. It stays running during those pauses.
When a read fails for good, the host replaces the Pane with an error that names
the Plot, shows the failure’s message, and offers Retry. Retry reloads only
that Pane and reads its Plots from the server, not from kept results; the other
Panes keep their state. A change to a Pane the Plot reads, a new context such
as another reading or View as, or a change to the Pane’s input also reads the
failed Pane again. Returning to the window does not. Leaving a Pane cancels its scheduled retries and the reads it
still has in flight, so the server stops them; leaving a Parable cancels every
read of its Pages. Refusals, invalid requests
and expired time limits are not retried: the Pane shows their error at once. A
read the server refuses the reader (HTTP 403), such as one a Provider Policy
refuses, shows “You don’t have access to this” with “Your current access
doesn’t include this Pane’s data”, and offers no Retry, since the same read
is refused again. It reads again when the context changes, such as the reading
or View as; returning to the window does not ask again. A
read a filter Policy refuses (QL-PL-015) is not
a failure: the Pane shows “Hidden by” and the filter’s name, says the data
can’t be narrowed to the rows the reader may see, and offers no Retry. It
reads again when the context changes, such as the reading or View as.
A preview that draws Panes without running them, such as the picture Ponder
takes of a Page, reads no Plot. A Pane that reads one shows a calm “Live data
isn’t drawn in previews” in the picture, not “Could not read” with Retry.
A Plot that a chosen older Refresh did not capture (PD-RESULT-001, or
PD-RESULT-002 for a Plot it reads) is not a failure: the Pane shows
Not in this Refresh with Read Latest, which moves the reading to
Latest, and no Retry.
A Plot that the chosen Refresh’s Release computes as a view, rather than
storing it, reads under that Refresh as its view over what the Refresh stored,
with the saved rows as of its start and the directories it captured. Only a view that needs other
data, such as the Pool’s current schema, answers Not in this Refresh.
On Latest, a reader is served the newest Release whose stored Plots can be
served (see Reports). Its readers stay on the previous Release
while a new Release’s first Report runs, so they never wait on a gap. A reader
who opens the new Release early, such as its publisher, sees No data yet
where its Pages go, with Power on Parable and Preview for a seat that
may run them, until a compatible Refresh completes. A Patch on a line
with no such Refresh shows the same state with Preview, and Read
Preview once a Preview of it completes (Reports, PARABLE-6285).
Their Panes do not read there. The state follows the line’s Latest
(currentDisabledReason), so a Patch on a line that has a Refresh
does not show it, even when the Patch edits a stored Plot. A Pane that reads a
stored Plot with no data yet anywhere else, such as in that Patch, on an
older Release or in a Personal Parable, gets PD-RESULT-002. While a
Refresh of the Parable is
queued or running, as the Refresh menu shows it, that read waits: the hook
stays loading, so the Pane
draws its own loading state and draws its rows when
the Refresh completes. A Pane that runs its Plot itself (plotApi.run) waits
for the Refresh the same way. When no Refresh is queued or running, nothing
will answer the read: a Patch that plans no output for the Plot, an older
Release, a Personal Parable, or a draft that changed its contract. The Pane
then says No data yet with the server’s sentence, such as “This Plot has
no data in these Changes until they are published and the Report runs.” or
“This Plot has no data in this Release.”, and offers no
Retry, and plotApi.run
rejects with that sentence. A Refresh that starts reads it again and waits; a Refresh that ends
without data says why. A Refresh that ends, or Latest moving to
a newer one, also reads again every Pane whose read failed: one that says
No data yet, Refreshing…, or Could not read. Returning to the
window does not. Other not-ready reads on Latest draw
Refreshing… over a skeleton.
A Pane may catch a Plot hook’s failure in its own error boundary and keep the
rest of itself drawn, as Team Time Spend’s Home does when its teams can’t be
read. The host still knows a read below failed: when the reading, a Preference
or the context changes, it draws that Pane afresh, so its own error state
does not outlive the reading that caused it.
A slow read ends with the code QL-TO-002 and a message that names the limit
that ended it, which the host’s error shows. The web app’s server ends the read at 55 seconds and
cancels it, which stops the query: “This Plot read took longer than 55 s, the
web app’s limit, and was stopped.” Ponder stops waiting for a Plot read it
runs as a tool after 30 seconds.
The host remembers a read that failed. Asked again before its
hold ends, it answers with the same failure and sends nothing. The hold starts
at one second, doubles with each failure in a row and stops growing at five
minutes, so Pane code that runs a failing Plot in a loop sends at most one
request per hold. The first two holds leave room for the hook’s own retries.
Anything that could change the answer ends every hold: a refused read, a
row saved here or elsewhere, a Provider Policy saved, a Refresh that
starts or ends, or Latest moving to a newer Refresh. A refused read (HTTP 401
or 403) is not held. A Pane’s frame may send at most 60 requests a second;
past that the host answers RATE_LIMITED, “This preview may send at most 60
requests per second.”
A 429 with no code of its own is a rate limit on the reader: Parable allows 500 requests a minute from one address, counting every tab of
the app. Every Plot read and Pane resolution then pauses for 30 seconds, or
for as long as the answer’s Retry-After says, doubling to two minutes while
429s keep coming. During the pause nothing is sent: a read answers with the
429, and the Pane shows “Rate limit exceeded” with Retry.
When too many queries are running, Parable does not start the read.
The read fails with the code QL-IN-008 and the message “Too many queries
are running right now. Try again in a moment.” The host shows it in the Pane’s
place, and Retry reads it again.
Generated types come from the Perception’s declared inputs and outputs. A Plot
whose rows come from its Pane’s type is typed as rows of
Pick<Type, columns>, with the columns its saved SQL returns.
SQL planning and execution check that contract, including columns, nullability,
and scalar representations. The editor’s Preview sends its edited SQL under
the selected Plot’s same contract and its Pane’s current Policies, so it works
only for someone who may save that SQL (Security). A read of a
Plot with no saved SQL reports that it has none, and creates no SQL or data.
SQL Output contracts support explicit object fields and homogeneous arrays,
including nested objects and arrays of objects. Dictionary maps, tuple columns,
multidimensional arrays, and mixed unions refuse at compilation. These SQL
restrictions do not limit the ordinary types of Panes and their saved rows.
Mounted Plot actions and hooks retain the contract they were compiled against,
including hooks reached through a referenced Pane. If the selected contract
changes, existing callers refuse until refreshed; they cannot silently inherit
the new input or output type. Changing current access still takes effect on
each request.
Plots of a Platform Parable’s tab read the Parable beside it
A Platform Parable’s tab sits beside the Parable on screen, its subject. The tab’s Plots read that Parable through five relations:subject.parable (its name, handle
and the people who hold Primary on its Deployment), subject.revision (the
Release, Patch or Refresh on screen), subject.patch (the Patch, its stage,
its Problem, the person who started it and each person who changed it),
subject.pieces (the Pieces of the revision) and subject.changes (each
Piece the Patch adds, changes or removes). The
Parable Pool
lists their columns.
Incremental Plots
A Plot can declare that its output is a set of time intervals, so that a change to a source runs again only the intervals the change touched. Parable marks the intervals that each source change touched and runs each one into the Plot’s stable table. A Report Refresh pins that table instead of running the Plot (Incremental Plots, PARABLE-8076, PARABLE-8078, PARABLE-8142). A publish that changes the Plot builds a new table beside the current one (Epochs, PARABLE-8080). The declaration is the Plot’sincremental field. A Plot without it is not
incremental. Only a Pipeline, a Plot that a Report runs, can declare it.
Saving the declaration on a Plot that a Pane runs is refused. Saving it in a
Parable whose Home Page declares a react contribution is refused too,
because a Refresh about a Patch cannot use an incremental Pipeline.
A source in
sources can be an incremental Plot of the same Parable, named
as the SQL names it: parables.{parable}.{plot} (PARABLE-8079). Each interval
of it that completes then marks the intervals of this Plot that its range
reaches. The grain must be the same as that Plot’s grain, or coarser, also
when this Plot reads it through a view of the Parable. Publish refuses a finer
one (MANIFEST_INVALID, path /incremental/grain, PARABLE-8246)
(Incremental Plots).
The stable table starts empty. Parable marks an interval only when a change
lands in its time range after the line publishes the declaration. Until the
Plot commits its first interval, a Refresh runs it in full, as it runs any
Pipeline. After that, the Refresh pins the table, and the table holds only
the intervals that changed since the publish. History from before the
publish needs a backfill. A person with the budget override permission and
the Primary seat starts one from the Pipeline’s History, with Backfill
under Intervals (PARABLE-8691, Run history and status).
So a Pipeline that becomes incremental shows only new intervals until someone
backfills its history.
Saving, Preview and Prepare check the declaration against the Plot’s SQL,
and the editor shows the same findings while you edit. Each finding holds
Publish. PV_INC_001 to PV_INC_010, PV_INC_012 and PV_INC_013 are
errors, and PV_INC_011 is a warning:
The AI functions (
ponder(), ask(), choice(), noul(), score()) are
allowed. The inference log keeps each answer by its inputs, so a second run
of an interval reads the same answers.
An interval run also runs the SQL of each Plot that the Plot reads as a view.
So PV_INC_006 also reports a call in such a view, or in a view that the view
reads. Saving reports it on the reference to the view, and
the message names the function and the view that calls it. The editor shows
it while you edit the incremental Plot, before a save, when your Policies let
you view the SQL of every Plot view in the Parable (view.plots). It judges
each view by the view’s saved SQL, so a view with unsaved edits gets no
finding in the editor until you save it. If you edit the view later, or set it back, the
save checks again each incremental Plot that reads it, also through other
views. This includes a Plot that your Patch does not change: the editor
shows its finding, and the Patch does not list the Plot. On a Workspace copy, this
includes a Plot that the copy reads from its upstream Release. Prepare checks
every Plot. Preview and Prepare also report it on the reference to the view,
but their message names neither the view nor the function. The editor and
the answer to a save or a re-check name them only when your Policies let you
view the SQL of every Plot view in the Parable (view.plots).
Each interval run also checks its whole plan, views included. This catches a
call that the save cannot see, such as a call in a Plot of a Platform Parable.
A run whose plan calls one of these functions fails with QL-PL-024, and the
message names the function and the view. Intervals, in the Pipeline’s
History, shows the message and the code
(Run history and status). The interval runs again
after its wait and fails the same way until you remove the call from the view.
When the check cannot derive the type of a time column, it does not let the
column pass. It reports PV_INC_011. An expression such as
date_trunc('hour', created_at) has no derived type, so cast it:
CAST(date_trunc('hour', created_at) AS TIMESTAMP) AS event_time. A
timeColumn with no scalar, or a source Plot with no derived schema yet,
gets the same warning.
Split an interval
A Plot that declaressplit runs each interval as parts units
(PARABLE-8081). Part p keeps the rows whose
parable_interval_split_part(key, parts) is p. The planner puts that
filter on the output, and moves the same filter down to the scan of each
source in sources that the key comes from. So each part reads only its own
keys, and needs less memory than the whole interval.
The planner moves the filter only where the Plot’s answer stays the same:
- through a
GROUP BYthat includes the key, - through a window whose
PARTITION BYincludes the key, - through a join, to each side whose rows the join does not fill with
NULL.
sources, with
the step that stopped it, such as a GROUP BY without the key. A key that
the Plot computes, such as lower(email) AS email, is moved as the same
expression. A Plot that groups by ROLLUP, CUBE or GROUPING SETS cannot
split.
The hash is fixed: a key is in the same part in every release. A NULL key
is in part 0. A key can be text, a number, a boolean, binary or a time. Each
part has its own row in the interval ledger, runs and retries on its own, and
replaces only its own rows in the stable table.
A save that changes split on a Plot that the line has published as
incremental is refused. Another key, another parts, and a split that is
added or removed are all changes. The Plot’s stable table holds the rows of
the published split, and a run with another split would lose rows or keep
them twice. A change of split waits for epochs
(PARABLE-8080). An
archive import is refused the same way.
Foreign keys and graph edges
Every Parable is a graph. Its nodes are its Plots, and a Plot’s output columns are its properties. Two facts on a Plot give the graph its edges (PARABLE-9104).GRAPH_TABLE reads them to turn a graph pattern into joins.
Both are null on a Plot without them. A Plot without
graphEdge is a node,
also when it has foreign keys. View Plots can be nodes and edges.
These rules hold for every published Plot:
- A column has one foreign key or none.
referencessplits at its first dot. A catalog slug never holds a dot, and a column name can. The part before the dot is the catalog slug of a Plot of the same Parable. The part after it is an output column of that Plot.- The foreign-key column and the column it references have the same type: the same primitive and the same scalar. Nullability does not count.
graphEdge.fromandgraphEdge.toare two different columns, and each one has a foreign key.- Names compare exactly as the catalog spells them: the catalog slug, and the column name as the Plot’s output schema holds it.
edit_pieces and an archive upload check
the same rules.
Publish checks all five rules against the output schemas that it plans, after
the Plot SQL gate is clear. Every publish path runs the same check: Publish &
Deploy, a Workspace Publish and a Platform Parable’s automatic apply. Prepare
does not run it, so a broken fact shows at Publish. Each refusal is
MANIFEST_INVALID, with entityKey, the Plot, and path, a JSON pointer
such as /foreignKeys/0/references or /graphEdge/from. The sentence names
the Plot and what is wrong, for example “Plot reports_to has a foreign key on
manager_id to Plot people, but this Parable has no Plot people.”
The facts travel with the Plot. The catalog snapshot that the editor, the save
and the publish gate validate against carries them on each Plot’s entry. The
published Plots read that Query Layer makes carries them from the published
Plot, so a query reads the graph of the Release it reads. A Parable archive
carries them in each Plot’s run settings.
A Refresh writes an edge Plot’s output for fast hops. The rows are sorted by
the from column, in row groups of at most 8,192 rows, with a bloom filter on
the to column. A hop from one row of the from Plot then reads about 1% of
the edge Plot, and a hop into one row of the to Plot about 18%, measured on
1M edges. The cost is a larger file than the same rows sorted with the
default settings: 10.1 MB against 4.65 MB for 1M edges. A Plot that is not an
edge keeps the default layout. An incremental edge Plot keeps the default
layout too.
Where a Pipeline keeps its output
A Pipeline can name the store that keeps its output, with the Plot’smaterialize field: { "store": "clickhouse" } keeps it in the Workspace’s
own ClickHouse instance, and { "store": "delta" } keeps it in Delta. A Plot without the field keeps its
output in Delta, as every Plot did before the field
(PARABLE-9101). An
archive carries the field with the Plot’s other run settings.
Save, publish and an archive import refuse a store the output cannot use,
with MANIFEST_INVALID, path /materialize/store, and a sentence that names
the Plot and the fix:
ClickHouse storage is not live yet. Until Parable writes a Pipeline’s
output to ClickHouse and reads it from there (the next steps of PARABLE-9101),
a Pipeline that names ClickHouse keeps its output in Delta, and every reader
reads it as before. A deployed Workspace also needs its ClickHouse instance,
whose rollout waits on PARABLE-9102, and that instance’s reader and writer
credentials (PARABLE-7556).
Services
Kysely is available by default. It compiles query structure into SQL and a separate parameters array. Preview the result through:preview in the Piece
editor’s canvas and on a Page in Ponder’s chat. Both send the SQL to the
server, which runs it only for someone who may save it, at their own seat
whatever their participation mode, and refuses anyone else with HTTP 403,
PA-AU-019 and the reason before any query starts (Security).
A refused preview is a refused read, so the Page’s Panes resolve again, and
where the Page has no store key the kept results drop and the Panes read again:
offer a preview only where the Pane’s Policies allow
edit.plots or create.plots. The picture Ponder takes of a Page and a Lab Page refuse it. run,
the hooks and readReport send no SQL and work for every
reader of the Plot. SQL is saved only as a Plot’s
implementation: through a save of the Plot in the editor or by Ponder, or by a
proposal from a Pane’s code, propose.change(plot, { sql }, { expectedRevision }),
when its Perception lists "plot" in proposes. A proposal goes into the
reader’s Patch after the reader confirms it, needs what the same edit
needs in the editor (edit.plots included), held by the reader and the Pane’s
author alike, and reaches readers when the Patch is published. A query built in the
app any other way is not stored.
A Pane that shows SQL rather than running it, such as a mapping table or a
drill-down, reads a Plot’s saved SQL with plotApi.source('meetings'), or a
declared Pane’s with panes.meetingZoom.plotSource('zoomMeetings'). Either
returns the SQL of the definition being viewed: the Release, or the draft
open in the editor. It returns null when the reader’s Policies do not let
them view the Plot’s SQL (view.plots); the Plot still runs for them. A
surface that does not hold the Parable’s definition, such as a Pane preview in chat,
refuses the call.
A Pane can check a SQL condition or value against a Plot’s columns with
plotApi.checkSql('meetings', { kind: 'condition', sql }). Use kind: 'value'
for a value expression. The check validates the expression against one Plot’s
columns without running the query. It requires the reader’s access to the
Plot’s output. Its validation catalog contains only that Plot’s columns.
References to other Provider Pool, Workspace, artifact, or Plot relations are
refused without looking up their columns. It checks parsing, column references,
and scalar rules. It does not use the query planner, so it can miss unknown
functions and conditions that are not Boolean.
checked: true means the validator ran. It does not guarantee that the SQL
will execute. checked: false means validation was unavailable.
SqlEditor accepts this function through its check prop and shows the
diagnostics beside the text. A declared Pane exposes the same check through
panes.meetingZoom.checkSql('zoomMeetings', { kind: 'condition', sql }).
A Plot’s SQL source can also keep parameter defaults beside its code.
Those defaults belong to the definition, survive publication, download and
upload, and must match the Perception’s declared Input. Older Releases
keep their own defaults after a later edit changes or clears them. React and CSS sources cannot carry
SQL parameters. An explicit empty array stays distinct from omitted defaults.
Use a declared tuple or scalar array for nonempty parameters. Native Dates
serialize as timestamps; strings, finite numbers, booleans, and null retain
their values. Parameters remain bound
values and are not interpolated into SQL. A read that passes no parameters uses
the saved parameters. An explicit array replaces them, including an empty array.
Use explicit casts when needed to keep the planned output type stable.
Native numeric values must be finite. Integer-valued numbers must fit
JavaScript’s safe range, from -9007199254740991 through 9007199254740991.
This also applies inside nested values, parameters, saved defaults, and Report
results. Unsupported values refuse before a write or result completes. Stored
values and Report results are never rounded or changed when they are read.
Finite fractions remain supported. Use an explicitly declared string contract
when an application needs to retain larger integral identifiers as text.
A Plot edit uses the revision last read. A refused save preserves the local
draft; reload explicitly before replacing it.
plotApi.key('activityFacts') returns the declared Plot’s stable identity; the
identity grants no access by itself.
Save a query for the Workspace
Save a query for the Workspace (save_query_for_workspace, PARABLE-5378)
keeps one SQL query’s rows where everyone in the Workspace can open them, in
one call. It is for a person or an agent who has a query and wants no Page of
their own design, such as “save the count of open pull requests per repository
so everyone in my Workspace can see it”. It shares with the whole Workspace,
not with one team. It takes a name, a one-sentence description, one statement
over the Provider Pools with no parameters, and an idempotencyKey. It needs
the permission to manage Parables and the permission to query the Provider
Pools, and it asks for a person’s approval in Ponder. Its MCP tool says
destructiveHint: false, because it only adds a Parable.
The server runs five stages, each through the operation that a person or
Ponder would call for it, so each stage keeps that operation’s checks. Every
check that can refuse runs in the check stage. The checks that need only
the SQL run before the Parable exists. Plot planning runs on the new Parable,
before it is shared or holds a Patch, because the Query Layer plans a Plot
only in the trusted context of a Parable that exists, as for a save in the
app. A refusal there removes the new Parable:
Each field takes the type that Plot planning names for its column, such as
Temporal.DateTime, Generic.Int64 or Contact.Email. A column of plain
text, a plain number or yes or no is Generic.Text, Generic.Float64 or
Generic.Boolean, as a Plot’s rows read it.
The rows are a plain Plot, not a Pipeline: each reader reads them live, under
their own Provider Policies. To read them, read the Plot’s data with the
rootRefId and the plotKey that the answer gives.
A refusal that the caller can correct is an answer with saved: false, the
stage that stopped, and a refusal with its code, its message, a correction,
and retry. With retry: true the same call can pass later, for example
while the Plot check cannot finish. When a stage after create fails, the
save removes the Parable it made, as the product deletes a Parable: it
pockets it, then deletes it. The Parable leaves every list, and its handle is
free again. A Parable with a Release is never removed, and neither
is one that another call is writing. A failed cleanup or a stopped process
can leave a draft. The same call continues that draft, or starts again after
its removal.
A save binds its normalized input to the create receipt before it writes
the Patch. A replay with different input cannot reach cleanup of the earlier
save, even if the first process stopped just after create. Calls with the
same Workspace and key run one at a time across replicas. A concurrent call
answers PA-AU-008 (command in progress) and can retry after the first ends.
The cleanup checks for Releases on the primary while it holds the
same Parable lock as Publish. It refuses cleanup if its save-lock connection
stopped.
A save runs for at most five minutes, or the request’s shorter deadline.
Cleanup has a further 30 seconds. Each API replica admits at most four saves
and uses at most a quarter of its primary connection pool for their locks.
This reserves connections for each save’s commands. A full admission limit
answers command in progress. A pool with fewer than four connections admits
no save.
A call with the same key continues a save that stopped, for example after a
timeout: it skips every stage whose effect is already there and makes no
second Parable or Release. After SQL preflight passes, the same key with
another name, description or SQL is refused with PA-AU-006 at create.
Preflight can first refuse invalid SQL or a caller without Pool access.
In Ponder, Parable sets
idempotencyKey for each Message, so the save makes its key from the person,
the Workspace, the name and the SQL: a call in a later Message with the same
input continues the same save. Inside a Ponder turn the Patch still starts in
the new Parable’s own Problem, because the turn’s Problem belongs to another
Parable.
Each Workspace makes at most 120 saves in one hour, counted on every replica.
A save past the limit is refused with HTTP 429, PA-AU-090 and a
Retry-After header, before anything is created. Each caller also makes at
most 10 calls a minute. A save counts when it reaches create, so a save that
Plot planning refuses on the new Parable counts too. While that Parable
exists, for the time of one plan, it holds the handle of its name: another
save with the same name is then refused with PA-AU-024, and the same call
passes after the removal. A Parable that exists for a moment holds no other
place in the limit.
Nullable Conditions
When a condition can be NULL, writeFILTER (WHERE enabled IS TRUE) or
FILTER (WHERE coalesce(enabled, false)). A comparison on a column that can be
NULL is such a condition: write FILTER (WHERE (amount > 0) IS TRUE). Use the
same explicit condition in CASE WHEN enabled IS TRUE THEN ... ELSE ... END.
The pinned DataFusion 53.1 engine can count NULL filter values as true in a
grouped count. This example gives both input rows a NULL condition. The correct
count is zero. The bare filter can return two, while IS TRUE returns zero:
PV_AGG_001 for a grouped, non-distinct COUNT with
one argument whose filter is a bare nullable Boolean catalog column. This advisory does not
block publication. It does not infer nullability for computed expressions,
derived tables, or unknown columns, so a check with no advisory does not replace
the authoring rule. The CASE form above is the same explicit-NULL convention,
not a separate known CASE defect.
Large results
A Pane’s Plot read returns at most 10,000 rows. Read only what the Pane draws, in this order of preference: aggregate in SQL, store heavy work, then page through rows a person is browsing. The examples use Team Time Spend’saction_hours (one row per person, Action and day) and time_spend_facts
(hours per person, category and day), both stored by Pipelines.
1. Read what you draw. Aggregate in SQL to the grain the Pane shows, and
never send raw rows to the browser to add up there. Apply filters, the scope
and corrections before the GROUP BY. A Home chart of hours per category
reads a few dozen rows, not every fact:
COUNT(*) or SUM sees them, so the total is
exact for every reader and under View as:
time_spend_facts is a Pipeline:
the categorizing and corrections run once per Report run, and every Pane reads
the result.
3. Page through rows only for a person browsing them, such as a table, a
list or a drill-in, and never to assemble a total. Every page is a full Plot
read: the server plans it, binds its inputs, runs it and sorts the whole result
again. Prefer keyset pages over a stored output. The Pane passes the last row
it shows as parameters and asks for the rows after it, ordered by columns that
end in a unique key:
OFFSET is for shallow pages only: the server still sorts every row and skips
the ones before the page, so a deeper page costs more.
meta.reading.reportRunId, but a paging Plot like the one above
reads stored Plots by name and its result does not say which Refresh they came
from.
4. The 10,000-row limit is a guard, not a target. When a Plot has more
rows, the Pane gets the first 10,000 and its hook’s truncated is true. The
host shows no notice of its own, so a Pane that could be cut off says so, and
the editor drawer’s Performance tab flags the read (“Stopped at the
10,000-row page, so its Pane drew part of the answer”). A Pane that is cut off
is a bug: aggregate, store or page instead.
5. Ponder and other agents follow the same rules. Ask the aggregate
question in SQL instead of paging through rows to count or add them up. The
Plot read operation an agent calls takes up to 10,000 rows per page and
answers with nextCursor while more rows remain; each page is again a full
read.
Storage
A definition version identifies code and schema. A Report identifies produced data. For a Pipeline, Latest (the API’s Current) reads one Refresh: the newest completed Refresh whose Release is in the same major line as the selected Release. A Release that keeps its major number reads the Refreshes of earlier Releases in its major line. A Release that starts a new major line needs a Refresh of that line. Every stored Plot reads its output from that one Refresh, and a Plot it did not store, or stored under another output shape, has no data until the next Refresh. Publishing does not run a Report or change its schedule. A Report run stores every Plot’s output as a table of its own, written once and never overwritten. An incremental Plot is the exception: a Report run pins its one table instead (Incremental Plot tables). A later run cannot change an earlier one, so choosing an older Report returns the rows it held when it completed, in the order the run wrote them. A Report run that fails or is cancelled stores no readable output, even for Plots that finished. SQL that namesparables.{parable}.{plot} for a
Pipeline reads the output of the newest completed Report
that fits the published definition, and an empty table until one completes. A
completed run’s rows show on the next read. For
a Plot nothing stores, the same address runs the Plot’s SQL
(Plots read as views).
A Pipeline’s output was produced under its run’s principal, so it is served only to a
reader who still has that run’s access to its inputs
(Security).
A run that completes reaches parables.{parable}.{plot} within moments. In rare
cases, a read can keep answering with the earlier output, or the empty table,
until Parable loads its catalog again, which it does three minutes after the
last load.
Each Report keeps its newest 30 completed runs and its recent manual
Refreshes (Reports).
An older run leaves the Refresh menu, Latest and parables.{parable}.{plot} before
its tables are deleted, so no read ever sees a table that is partly
deleted.
Choosing a Refresh pins that run; an unavailable or incompatible result does not
fall back to Latest. Other Plots and previews can read completed Pipeline
outputs with their own SQL and parameters. A direct read of a stored output
keeps the inputs its run captured and refuses other parameters.
Use plotApi.readReport('activityFacts') to read completed output with its
captured inputs. It returns the Perception’s typed rows without requiring
today’s input tuple. It uses the selected Refresh, or the Refresh Latest
reads, and refuses when no materialized result is available.
run and preview keep their declared input requirements.
A named read runs the Plot’s SQL from its implementation in the selected ref or
Release. The result names that implementation row and its version
(sourceImplementationId and sourceImplementationVersion), and a Report
records the same pair for each Plot it ran. A stored result keeps the contract
and source that produced it. consumerContractHash names the contract of the
selected definition that accepted the rows. materialized is true only when the
rows come from the selected run’s stored output, and false for a query that
runs under a Report reading.
SQL catalog addresses use the stored Parable and Plot slugs. Both segments begin
with a lowercase letter and contain lowercase letters, digits, and underscores
between groups. Hyphens and case-only replacements are refused. A new Plot’s
slug is the snake_case of its handle (revenueForecast is revenue_forecast)
unless its author sets one, the same rule a Pane’s name follows. Renaming a
handle does not move those addresses. A save that asks for a different slug
for an existing Plot is refused, and nothing in that save is kept. This is
also true for the first save of that Plot in a new Patch. A Plot deleted on a ref frees its address on
that ref, as a deleted Piece frees its handle, so a Plot that names that slug,
as an upload from another Parable does, can take it there; a new Plot that
names none never takes a deleted Plot’s address.
Publication plans Plot dependencies from the schemas of the definition it
publishes. A missing dependency or a cycle refuses publication. The cycle check
judges the Plots as their dependencies will stand once the Release is
published: two Plots in one Release that read each other are refused, and a
Release that turns a dependency around (A read B; now B reads A) is not. A
Plot can depend on a Plot of a Platform Parable, and of no other Parable:
parables.<parable>.<plot> resolves against the line’s own Plots and the
Plots of the Platform Parables the author may read, so a reference to any
other Parable’s Plot gets PV_REF_002, and Prepare and Publish refuse it.
Each Release pins the Release of each Platform Parable it reads, the
newest one in the Workspace when it is published
(Parable Pool).
Planning creates no rows and needs no earlier Report.
Publishing plans each Plot before it publishes the Release. The Release carries
every Plot’s published SQL and planned output schema. List published Plots
and Read a published Plot return the SQL and schema from that Release.
Planning adds no change to a Plot the publish did not edit.
Publish and Prepare plan every Plot before they answer, which takes minutes on
a large Parable. The web app gives either 300 seconds, where other API calls
get 60. When that limit ends a command, the web app cancels its request and
answers 504. A publish stopped while it plans has written nothing.
A publish or Prepare that Ponder runs as a tool has its own limit: Parable ends
a Ponder tool’s operation after 90 seconds for a Message’s call and 2 minutes
for a Run’s. A command stopped there answers 504 and says it ran
past the time limit of the call that sent it, because sending it again through
Ponder would stop at the same limit. A command whose request was cancelled
some other way answers that sending it again with the same idempotencyKey
returns its result if it finished and runs it once if it did not.
SQL checks and failures
Anyone with Primary or Publishing can save Plot SQL and read its findings. Save keeps the SQL when a check finds an error. The finding explains what needs to change. Prepare and the Plot’s Preview use the same SQL diagnosis when their inputs match. A Prepare finding names its Plot, so an agent can act without the Plot open beside the message. A function call with incompatible argument types is a SQL refusal, not a planner outage. For example,TRIM takes text. If its argument is a timestamp, remove
TRIM, or cast the argument to VARCHAR first. A wrong argument count needs a
different correction. Change the call to use the number of arguments in the
message. Neither failure needs a service restart.
Prepare keeps the result for each Plot. An invalid Plot does not make an
independent Plot look broken. A Plot that needs the failed Plot’s output says
which Plot it waits for. A circular read names the Plots in that cycle. Remove
one circular read before Prepare. A Plot that only reads a cycle is not a
member of that cycle.
Only a connection failure, a timeout, or a known shared capacity limit advises
a retry. An unexpected planner failure asks Parable staff to investigate.
It does not claim that the SQL is wrong or that the planner is unavailable.
SELECT * is valid when the source has known columns. A known disconnected
Provider can still supply its declared columns for a check. Save, Prepare,
and Preview accept that source. A source the platform does not know is a
finding in all three checks. Replacing * with column names does not make
an unknown source available.
Save does not replace Prepare. Save can use an earlier checked result for a
Plot that this Plot reads. The extra execution check also has a time limit.
Prepare checks every Plot against the Patch it will publish. Neither check
reads all Provider rows. Preview can still find a value that CAST cannot
convert. Use TRY_CAST if values that do not fit should become NULL.
A run of a Plot stores its output with the column types of the Plot’s plan. A
UNION column keeps a scalar type such as Identity.Name only when every
branch has it: when one Provider’s titles are names and another’s are plain
text, the merged title is text. A Plot that reads a Pipeline’s table,
parables.{parable}.{plot}, plans against the columns of that Pipeline’s plan.
A run changes neither the Plot nor its Release. The table’s freshness is the
time the Workspace’s last successful run of that Plot pinned its inputs. Each
Workspace keeps its own, including a Workspace that received the Plot in a
Delivery.
Incremental Plot tables
An incremental Plot keeps one Delta table for each epoch, atparables/{parable}/_incremental/{plot}/{epoch} under the
Workspace’s provider root. The table is partitioned by the start of each
interval, and also by the part when the Plot declares split. Each
interval run replaces only the rows of its own interval, so a new interval
adds rows and a restated interval replaces only its own. A source row that a
sync deletes leaves the interval when the interval runs again. The interval ledger,
query.plot_intervals, records each interval’s last commit: its Delta
version, its row count, the digest of its rows and the hash of the contract
that its run checked every row against.
The table holds three columns that a read never serves:
A full read and a paged read return the rows in the order of
(_plot_interval_start, _plot_split_part, _plot_ordinal). A Report run’s own
output orders by _plot_ordinal alone, which is unique in its table. In an
incremental table only the three columns together are unique.
A Report run does not run an incremental Plot. It pins the table at the
version of the newest interval commit
(Reports). Query
Layer reads the pinned version, and checks the commit digest and the row count
as it does for any stored output. It trusts the rows without a row check only
when the ledger still describes the pin and every interval carries the Plot’s
contract hash. Otherwise it checks each row against the Plot’s contract as it
reads. An interval that commits after the pin does not change what the pinned
run reads.
The ledger cannot vouch for a commit it never recorded. So an interval run
clears its interval’s row count in the ledger before it commits to Delta, and
records the new commit after it. A run that stops between the two leaves the
row count cleared, and every read of a pin of that epoch checks each row
until a later run records its commit.
Epochs
A publish that changes what the table’s rows depend on starts a new epoch: the Plot’s SQL or parameters, the Policies of its Pane, its output columns, or the declaration’sby, grain, sources or split. An edit of the SQL,
parameters or Pane Policies of a Plot view of the same Parable that the Plot
reads, directly or through other views, starts one too: the view is planned
inside the Plot. A change of quietMinutes,
maxDelayMinutes or onSourceChange keeps the epoch. Every interval of the
current epoch runs again into the new epoch’s table, at the new grain and
parts. Reports keep reading the current epoch while the new one builds, also
while one of its intervals failed and waits to run again. When every interval
of the new epoch is complete, the new epoch becomes the one that Reports read.
An edit back to the current Plot before then drops the new epoch.
An incremental Pipeline that reads this Pipeline reads its current epoch only.
When the new epoch becomes current, the reader’s intervals over the new
epoch’s range run again against it. The reader keeps its own table.
Before you publish, the change preview lists the Pipeline with the number of
intervals the new epoch runs and their estimated cost, and calls the cost out
when it is above the Workspace’s Refresh cost threshold.
Maintenance
Each interval run that commits to the table keeps its epoch in Plot maintenance. Compaction merges the small files of one interval and part, and never the files of two intervals, so the table keeps at least one file for each interval and part. VACUUM removes the files that no version of the last 7 days reads. It keeps the files of each version that a running Report run pinned, also when that version is older. A Report does not stay reproducible: a Report run pins the table only while it runs. Report run output GC never deletes an incremental table. When a Report run that pinned the table leaves retention, its result goes and the table stays. An old epoch’s table goes 15 minutes or more after a newer epoch replaced it, once no interval run writes it and no running Report run pins it. The current epoch’s table never goes.Dependency graph
A Plot dependency says that one Plot reads another. A Plot is its Parable and its key, so Plots with the same key in two Parables are two Plots. Publication checks the dependencies by the rules below. Report capture uses them to find what each member reads, and a Report run orders its members by them. A Plot that reads a Plot of a Platform Parable publishes a dependency between two Parables. Publish refuses any other dependency between Parables withPV_REF_002. Saving a Plot checks
for PV_CYC_001: a cycle
through the Plot being saved, judged from the SQL you save and what the
Parable’s other Plots read.
For each Plot that reads a Platform Parable’s Plot, publication also records the
columns it reads. This includes columns used only in a filter, a join, or a
grouping. The record comes before optimization. An unused column in a CTE still
counts when the SQL resolves it. A CTE that selects * records every upstream
column, even when its outer query selects only one. SELECT * records every
column of the upstream Plot at that Release.
SELECT count(*) FROM ... with no other column references records an empty list.
Republishing records the columns that the new SQL reads. Reads within the same composed Parable have no such list.
Older dependencies have an unknown list. A wholly unused CTE or a wildcard with
options such as EXCEPT, EXCLUDE, REPLACE, or RENAME also gets an unknown
list for the upstream Plots it reads. These forms can still require columns that
no longer appear in the returned plan. If their inputs cannot be identified
precisely, every upstream input gets an unknown list. Other queries keep their
exact lists. Unsupported SQL remains refused. An unknown list means that every
column may be read. An empty list means that no column is read. Publication does not
backfill older dependencies. This record does not change what a Page shows.
- Cycles. The check finds a Plot that reads itself and Plots that read
each other in a cycle.
Publication refuses either:
parable: plot dependency cycle: Plot … depends on itself, orparable: plot dependency cycle through Plot … in Parable …. - Report members. For each member, capture lists the members it reads,
directly or through views. A member that reads itself, directly or through a view, is
refused first, then members that read each other in a cycle. The refusal
names the members:
parable: plot dependency cycle: Hours by team reads Logged hours reads Hours by team, orparable: plot dependency cycle: Hours by team reads itself. A scheduled run refused this way records the refusal and its reason on the plan, and the occurrence is not tried again. - Report runs. A Report run runs a member after every member it reads.
- Downstream. The Plots downstream of a set of Plots are all the Plots that read one of them, directly or through other Plots. A Plot of the set is among them only when it reads another Plot of the set.
Output schema diff
When a run publishes a Plot’s output, it compares the new output schema with the one the Plot held and records the columns added, removed and retyped, and whether the change is breaking, additive or changes nothing. A removed or retyped column is breaking. Added columns alone are additive.Its Panes’ Plots
A Page’s or Pane’s Plots tab also lists the Plots of the Panes it declares, under Its Panes: each such Pane once, as a closed group with the number of Plots it owns, read only when it opens. Choosing one shows its SQL read-only, the reader’s draft included, with its Pane, its name and Read by. Open goes to that Plot in its own Pane’s Plots tab, where it changes. A Page owns no Plots, so its Plots tab lists its Panes’ Plots alone.Run history and status
A Plot’s editor has three tabs below its SQL. The Refresh control at the top right of the editor, Results and History share one selected reading. When the SQL cannot be changed, such as in a released Version, a Proposed set or an archived Parable, the band of the tabs shows a read-only badge with the reason in its tooltip, whether the tabs are folded or open. In Peeking and Pinging the SQL stays on screen and the badge names the mode, such as Peeking, not Publishing.- Results shows the selected reading. On Latest it holds Preview’s rows with their count and time. Above them it lists what Prepare would block this Plot for, if anything: Preview runs Prepare’s own checks on the edited SQL, judged by how the Plot runs (a Pane or a Report), and still shows the rows. Preview in the Refresh menu is a different thing: a stored Report run on a sample of the data (Reports), read like any other run. On a stored Report run it holds that run’s own rows, read without running the Plot. A run that is queued, running, paused at its budget, failed, cancelled or skipped shows that status instead of rows, and a failed run shows its message. A Plot that finished inside a run that is still moving, or that did not complete, has no readable rows either: only a complete run is readable. When the selected run does not include the Plot, because the Plot is not a Pipeline or the run has no result for it, Results says so and reads nothing. Preview runs on Latest only, so it is unavailable while a stored run is selected.
- History lists every Refresh (a stored Report run), newest first. Each row is named
by when the run happened, in your time zone and clock: 12:31pm ET today,
Sep 29, 9:00am ET before, with the whole moment on hover. Results names
a stored run the same way, as in Refresh Sep 29, 9:00am ET · 1,204 rows.
Each row shows this Plot’s status, whether the run was Manual, Scheduled,
fired after its sources (After Okta synced) or a Refresh by Parable
(Parable started it to repair data after a platform update), Refresh
everything when it was forced, and for which Report, Started by (the person who pressed
Refresh, or Schedule, Sources or Parable), when the Plot started
and last changed, how long it ran, how long it waited to start, its row count, its
spend in US dollars and paid calls against the caps captured when the run
started, and the catalog address with the run’s short id. A row count the reader may not see reads
Hidden, never 0. Choosing a row selects that Refresh in the Refresh menu
and the address (
refresh); choosing one in the menu selects its row. See Run a Report now. - On a Workspace’s line, the History of an incremental Pipeline starts with Intervals (PARABLE-8221). Each epoch of its stable table shows how many of its intervals are Pending, Running, Complete and Failed. A Building epoch also shows how many are complete, because Reports read the Current epoch until every interval of the new one is complete (Epochs). Below the epochs, the newest 200 intervals each show their state, when the last run started and how long it took, the rows it wrote, when a waiting interval runs next, the attempt, and the message and code of a failed run. The list reads again every 15 seconds while an interval waits or runs or an epoch builds. A row count reads Hidden when a filter Policy or a Provider Policy narrows what you read, because the interval runs read as the line’s Accountable seat.
- When every interval of an incremental Pipeline waits to run again (a restate, PARABLE-8260), Every interval runs again shows above the epochs. It says why: a source the Pipeline does not read by time changed, a shared setting or Pane row it reads changed, or both. It shows the estimated cost of the paid AI calls against the Workspace’s Refresh cost threshold, and when the restate was held. Estimating means the estimate still runs. Held means the estimate is above the threshold, or it ended without an answer. Approve shows only to a person with the budget override permission and the Primary seat, as Raise budget does. It asks you to confirm the estimated cost first. Approved shows until the next sweep, within about a minute, runs every interval again. If the restate changed before your approval, the list reads again and shows it as it stands.
- Backfill, beside Intervals, runs every interval of a range of days again, such as the history from before the Pipeline became incremental (PARABLE-8691). It spends the Refresh budget, so it shows only to a person with the budget override permission and the Primary seat, as Raise budget does. You choose From and To in your own time zone, and the dialog counts the intervals before you confirm. It also estimates the paid AI calls of those intervals and shows the cost against the Workspace’s Refresh cost threshold (PARABLE-8741). Backfill waits for the estimate while it counts. No answer means that the estimate ended without an answer, and Try again starts it again. The real cost can be a little higher than the estimate, because intervals that run at the same time can each pay for one call. The intervals run in parallel, up to half of the Workspace’s query-run slots, and their commits take turns, so readers see part of the history until every interval completes. A range ends by now, and one backfill marks at most 100,000 interval runs. One Pipeline takes at most 20 backfills in 24 hours (PARABLE-8871). After that, the dialog says when you can backfill it again, before you confirm. Ponder suggests the range and points you to Backfill. It does not start one.
- Details lists the catalog address
parables.{parable}.{plot}, whether Report runs include the Plot, its published caps and its output columns.
Security
A Plot is governed by its Pane’s Policies for the same Release or Patch. Source inspection usesview.plots; source edits use edit.plots. The rows follow
view.preferences, and the filter Policies that cover it narrow them.
Permission to read
data need not disclose its query.
List published Plots and Read a published Plot return each Plot’s SQL.
A person sees a Plot there only with an active Workspace membership, a seat on
the Parable, and the Pane’s view.preferences and view.plots grants.
Neither returns the settings of the Panes that a Plot’s SQL reads by name.
Read the Plot’s rows to get them applied, or read a Pane’s settings, which
follow that Pane’s view.preferences.
Using a React renderer requires access to the Pane and its values. It can be delivered
without granting its source-editor view, but executable JavaScript necessarily
reaches the browser. CSS has the same limitation. These restrictions cannot
promise secrecy for delivered code or styles.
A Plot read may carry SQL of its own, a constructed preview, which runs in
place of the Plot’s saved SQL. The server runs it only for someone who could
save that SQL, by the three checks a save makes: the Parable edit permission
(tenant.parables.manage), a seat that
edits Pieces (Publishing or Primary), and edit.plots or create.plots
from the Plot’s Pane Policies. Anyone else, a Viewer included, is refused
with HTTP 403 and PA-AU-019 before any query starts, whether the request
comes from the web app, the API or Ponder. The reason says which check
failed: that only people who can edit the Parable’s Plots can run their own
SQL on them, or that it needs edit.plots or create.plots on the Plot’s
Pane. Policies cannot admit a seat that edits no Pieces: a grant that names a
Peeking or Pinging person, or a Role they hold, shows them edit.plots in the
Pane’s capabilities, but the server still refuses their SQL. The checks use
the reader’s own seat, as every Plot read does, so a participation mode below
it changes nothing. A Pane’s preview reaches them from the Piece editor’s
canvas and from a Page in Ponder’s chat, and the Pane gets the server’s code
and reason; the picture Ponder takes of a Page and a Lab Page refuse it without asking the server.
Code in a Page can send a preview for any Pane’s Plot in the Parable, as run
can, and these checks limit what it runs.
Under
View as, the seat and grants of
the person being previewed decide, and the permission is the viewer’s own. A
read without SQL runs the saved SQL and needs only view.preferences.
SQL you run inside a Parable without saving it, from a Lab Page or with
Ponder’s SQL tool, works for anyone who can read the Parable: a seat on it and
a role that reads Parables. It reads that Parable’s Plots by their catalog
addresses, parables.<parable>.<plot>, each under its Pane’s Policies, so a
reader gets the rows the Panes would show them and nothing more, and a Pane
whose Policies refuse the reader refuses the SQL. generate_series and
range work there too. A Plot read as a view still reads its own sources. A
Plot that takes inputs or runs as a Pipeline is read from a stored Refresh
that matches its current output. With none, or when your Patch edited its
SQL, that Plot has no data there yet: the SQL, and a Pane whose Plot reads
it, is refused with HTTP 409 and PD-RESULT-002, which names the Plot and
says it has data once its Report runs, after the Patch is published. It is
not an access refusal.
Only someone who could edit the Parable’s Plots, with the Parable edit
permission and Publishing or Primary on it, can read anything else from
there, such as Provider Pools. A Plot of a Platform Parable the Parable reads
is read as one of its own Plots, at the Release and the Refresh the read
reads. Anyone else’s SQL
that names something other than the Parable’s Plots is refused with HTTP 403
and PA-AU-019 before it runs. If your role reads Provider Pools, as Provider
Pools Reader, Data Access and Workspace Administrator do, run the SQL outside
the Parable instead. Under View as, SQL reads only the Parable’s Plots, as the
person being previewed, whoever may edit. Under View as on the Parable beside
a Platform Parable’s tab, the tab’s SQL runs as you, not as the person being
previewed. It still reads only the tab’s Plots and the subject.* relations,
and the server builds those relations as the person being previewed. A refusal for your role, your seat
or a Policy, a Pane’s included, is HTTP 403 with PA-AU-019. SQL that names
a Plot whose Refresh a Report run stored is refused with HTTP 403 and
QL-TN-007 when source permissions or a Provider Policy narrow what you read
of a Provider table behind it: the Refresh holds rows you cannot read, and
other SQL over that Plot gets the same answer. A commitId
that names a Patch nobody has published yet is refused with HTTP 409 and
PA-AU-028. SQL sent with a Plot read stays limited to people who could save
it, as above, because it replaces the Plot’s SQL.
A constructed preview and a named read both apply their Pane’s Policies and
their actual inputs’ restrictions. Restrictions that must apply to every read
of a Provider table belong in that table’s
Provider Policy. Invalid or unsupported
filters refuse execution rather than returning unfiltered rows.
For a Provider table no Provider Policy narrows for the reader, simple
conditions reach storage: a comparison of a column with a value, a column
IN a list of values, and AND and OR of these. A condition on a
partition column, such as the dt load date, lets Parable skip partitions
that cannot match. A condition on any other column, such as an event time or
a key, lets Parable skip files and blocks of rows whose value ranges cannot
match. How much that saves depends on how the data was written. The query
still applies the complete filter to the rows it reads. For a table a
Provider Policy narrows for the reader, the query’s own conditions and limits
do not run ahead of the Policy’s conditions, which Parable applies as it
reads the table and can use to skip data. Only a direct condition qualifies:
a column inside a cast or function, or a NOT IN list, skips nothing.
A source read can run in several parts at once. While it runs, the source’s
access is rechecked at least once a second and again as each part
finishes. A part’s final check is always one that began after the part read
its last rows. A revoked grant or a changed
Policy fails the query before it returns a complete result; a query that streams
rows may already have delivered up to a second of rows read after the change.
SQL that reads another Plot’s stored rows checks them against that Plot’s
output contract before any Policy, filter, limit, or aggregate applies. Where
the check runs depends on what wrote the rows:
- A Report run writes a Pipeline’s output. The run holds the Plot’s contract
and checks every row against it before it writes. A run whose rows break the
contract writes nothing and fails with
QR-QU-011, and the Plot keeps the output of its last successful run. - The read of those rows makes the same check, whoever wrote them. It checks
every value in each column the query reads whose contract says more than the
column’s type,
such as a literal, a scalar format, a bound, a required value, or a Date’s
precision. This includes columns used by Policies, filters, joins, grouping
and aggregation. A column the query never reads is not read from
storage or checked, so
COUNT(*)reads no column.
COUNT(*) reads none. A table
with no record, with a record for another contract, or whose last write kept
earlier rows is checked as above. So is every read of a Report output that
keeps embed() vectors beside its table: the record does not cover them. Rows written before a scalar’s rule became stricter are served unchecked
until the Plot runs again.
A view’s rows cross the same two boundaries as stored rows: the values of
each column a query reads are checked against the view’s output contract, and
the view’s Pane Policies (view.preferences) apply for the reader before any
predicate of the reading query, in every Plot or Pane that reads it, directly
or through other views. The view’s filter Policies apply where it is read
inside a Plot they do not also cover, and its own filters wherever it is
read. A filter that covers both applies to the outer Plot’s rows. A view computes only the columns a
query reads. The reading query’s own filters run on the checked rows, so a
filter never hides a value that breaks the contract. The reader runs the view’s
SQL under their own access to its sources, as for any Plot. Reading a view’s
rows does not need view.plots: its SQL never reaches the reader, and a view
that fails to plan is named
without its SQL. A view that so far exists only in a Patch
follows its Pane’s Policies as that draft sees them. In a Report run, the
run’s principal applies each view’s Policies when the run is planned, and a
reader of the run’s output must still pass each view’s Policies.
A Report run reads its inputs as its principal: the person who chose
Refresh, or the Workspace for a scheduled run. SQL outside a Report reading
that names a Pipeline reads that run’s output, so the
reader must still have the run’s access to every source it read, the same
check a Report reading makes. The run’s artifacts.* and workspace.* tables
have no Policies, so they add nothing to this check. Before the first row, each
source’s current Policies for the reader must cover the filters the run was
produced under. A Policy that adds or changes a filter the run did not have, or
an input the reader can no longer see, refuses the read with Report data or
its current input authority is unavailable. A source filter that uses
request_principal() admits only the run’s own principal. While the read runs,
the check runs again at least once a second and as each part of the
read ends, the same cadence as a source read. A part’s final check is always
one that began after the part read its last rows. A Policy that changes during the read stops it before the query returns a
complete result; a query that streams rows may already have delivered up to a
second of rows read after the change. A count or an empty result is checked the
same way.
Stability
A Report run is a graph of its member Plots. Capture records which members each member reads, from the Plot dependencies planned when the Parable was published, by the rules in Dependency graph. A member runs only after every member it reads succeeded, and it reads the table that member wrote in this run, at the version it committed; every other input is read as of the run’s AS OF time. One query plan of a run holds at most 50 levels, and a view counts inside the plan that reads it, so a Plot that composes many views stays under that limit by storing the steps it builds on as members. A Plot only read live is not held to the limit. Members that read each other in a cycle are refused. A member may read a view of its own Parable, which the run computes inside its own plan, and a Plot of its own Parable outside the Report. A member can also read a Pipeline of a Platform Parable, directly or through a view. Capture pins it to the newest complete Refresh of that Parable that completed by the run’s AS OF time and fits the Platform Parable’s Release that this run’s Release pinned, so a Refresh of that Parable that completes while this run waits to plan does not change what it reads. The member reads that Refresh’s table as it completed, on every plan and restart, and its output names that Refresh (Reports). Publish refuses a member that reads a Plot of any other Parable, directly or through a view (PV_REF_002).
A Report run plans with the input schemas of the definition it captured.
Only retention deletes a stored Report output. Each output is written once, and
a read refuses an output that no longer matches what its run recorded.
A historical read does not replay Provider input. A historical result whose Provider
filter uses request_principal() requires the captured principal, including
email. Source filters using now(), current_timestamp, current_date, full
parable_context(), or another execution-dependent function refuse both Report
materialization and historical reads. Use Current data for those filters.
A Release never changes. A read of a stored output also checks the
output against the Release whose Report run stored it. Every read still checks
the reader’s seat, membership and the Pane’s Policies, before the read runs and
again after it answers, with the run the reply came from. A read that expects
another contract than its Plot’s is refused with a conflict.