x-* annotations on the
fields say what each field means to Parable: which field is the key, which
version of a record wins, which field is a person’s email, and who can open
the row. Other declarations sit on the Tap and on the Provider Plugin.
Parable maintains every Provider Plugin. This page is the reference for what
each declaration does to the tables you query. Read the declarations on the
plugin of one connection:
connector.taps has schema, the Tap declarations, and
responseExtraction. Your Workspace keeps its own copy of each schema as
outputSchema on the Workspace Tap. Some annotations on that copy are yours
to change. See Taps.
From Tap schema to SQL
- Plan. Parable reads the schema and plans the tables of the Tap: the main table, child tables for arrays, the columns, the merge key, the ordering column, and the layout.
- Check. A sync checks each record against the schema. A value that does
not match lands as null, and the row records the reason in
_parable_validation. A record without its merge key goes to a quarantine table instead. - Merge. Parable merges each load into the tables by the merge key and the
ordering column, as
dataKindanddeletionSemanticssay. - Derive. Taps that are identity directories feed
providers.identity.*. Taps that carry access facts feedproviders.access.*. - Publish. The catalog lists each table and column, with its keys and foreign keys. You query the tables with SQL.
Annotations
An annotation is a key on one field of the schema, such as"x-transformDedupKey": true. Unless a section says otherwise, an
annotation applies to a field of the record’s root object.
Keys and ordering
The merge key. Parable merges the main table on one column:
- The first field marked
x-transformDedupKey, by field name. - Else the
outputFieldofresponseExtraction.syntheticPrimaryKey. - Else
responseExtraction.primaryKey. - Else a field named
id.
responseExtraction.primaryKey names. A Tap that declares a dataKind must
mark one. Every part of the merge key’s path is required in the schema,
because a row without its merge key goes to the quarantine table. A dedup key
inside a nested object identifies that object, not the row. It becomes the
merge key only when the root marks no field. Gong calls_extensive keys on
metaData.id this way.
A child table merges on parent_pk and ordinal: the parent’s key and the
element’s position in the array. A dedup key on a field of an array element
names the element’s own key. It does not change the child table’s merge key.
Ordering. When no field is marked x-transformOrdering, the Tap’s
sync.cursorField is the ordering column, if the schema declares that field.
Otherwise the table has no ordering column. When two rows have the same key,
the row with the greater ordering value wins. A missing value counts as the
oldest. When both values are missing, the later load wins. A child table
carries its root row’s ordering column.
Foreign keys. An object field marked x-transformForeignKey becomes one
<field>_id column that holds the referenced row’s ID. Notion
pages.created_by becomes created_by_id. A scalar field that already holds
the key keeps its own column, and the annotation only records the reference.
idField names the field of the target Tap that the value matches. It
defaults to id.
idField is the
target Tap’s key, or the person email of an identity directory. A field whose
type is the Tap’s own record type is a foreign key to the same table, with no
annotation: a comment’s parent comment becomes parent_id. A foreign key never crosses
Providers.
Structure
Parable decides the layout of each nested field by its shape:- An object with at most 20 fields, at most 3 levels deep, flattens into
parent__childcolumns.assignee.emailbecomesassignee__email. - A wider or deeper object is one struct column. Read one of its fields with
profile."userType"orprofile['userType']. - An array of objects at the top level becomes a child table,
{tap}__{field}. A deeper array is one list column. - An array of scalars is one list column.
- An object with no declared properties, or a map, is one JSON column.
x-json is the only layout annotation that a stored Tap schema keeps. A Tap
schema that sets x-flatten, x-struct, or x-explode is refused.
Identity
Identity annotations say which fields are about a person or an account. They do not make a row unique, and they do not change the columns. A field has at most one identity role, on a scalar value.
The typed form of
x-transformExternalUserId links two accounts. With
"by": "id", the value equals the x-transformAccountId of an account in the
named Provider. With "by": "email", it equals that account’s person email.
Linear users.gitHubUserId declares {"system": "github", "by": "id"}, so a
Linear user and the GitHub member with that ID are one person. The bare
true form names no Provider. It is a hint, and it links nothing. A plugin
uses the typed form only when the Provider sets the value from a verified
connection, such as an OAuth link.
Only a Tap with identityDirectory: true feeds providers.identity.*. Its
root table has an x-transformAccountId or an x-transformPersonEmail
field. An annotation on a field of a nested object counts, in a flattened
column or in a struct column. An annotation inside an array does not. When a
table has two fields with one role, the field nearest the root wins.
Event layout
A field with the
event_time or metadata_timestamp role has a date-time
type. On a Tap with dataKind: event, each table has at most one
event_time field.
Parable lays out each table of an event Tap by its event time. When no
field has the event_time role, a timestamp ordering column stands in. Each
write is sorted by event time, so a query for one day skips the files of
other days. A table that Parable creates this way is partitioned by
_parable_event_date, the UTC day of the row’s event time. An older table
keeps its partitioning until a rebuild. Filter on the column to read whole
days:
_parable_event_date. snapshot and changelog
tables have no event layout.
Wire format
The annotation is valid only on a date-time field. An integer ordering field
declares a date-time type with
x-temporal-format, so that it sorts as a
time.
Lineage
Both fields land as ordinary columns. GitHub copies the repository’s
full_name onto each pull request, so you can filter pull requests by
repository without a join.
Reader text
Reader text never changes the stored data. A change to it changes only what
the catalog says (
CATALOG_ONLY in
When a Tap schema changes).
Search
contextnames fields of the same row whose values go before each piece of text, such as the title.textandlabelapply to an array of objects.textis the path to each item’s text, andlabelis the path to one value for each item, such as the speaker.format: "adf"says that the text is an Atlassian Document Format document.format: "notion"withordergoes on a foreign key. Row search then reads the rows that name each referenced row, in that order, as one document.
Access
Sixteenx-access* annotations declare who can open each row in the
Provider. Source permissions describes each
one, with examples.
An access annotation never changes how a table is planned or stored.
Workspace only
Only your Workspace’s
outputSchema uses x-exclude.
Taps lists the
fields that you cannot exclude.
Declarations on the Tap and the plugin
These declarations are notx-* keys, but they change the tables in the
same way.
On the plugin
On the Tap
A synthetic key hashes the listed fields. Google
group_members keys each
membership on the group and the member:
outputField, the hash goes into the
primaryKey field. A single "*" entry hashes every top-level field of the
record.
Some reports have columns that only your Workspace knows, such as a custom
Workday report. syntheticPrimaryKey.fieldsConfigKey names a field of the
connection’s ingestionConfig that lists the key columns. The columns that
you list there replace fields for your Workspace. A record that lacks one
of those columns fails the read.
When a Tap schema changes
When a plugin’s Tap schema changes, or your Workspace changes itsoutputSchema, Parable compares the new table plan with the plan that serves
your queries now. Each table gets one class. A table with several changes
takes the class that needs the most work.
A rebuild rewrites a Tap’s stored tables from the records that Parable kept
from earlier syncs. It waits for approval. Until a rebuild finishes, queries
read the tables as they were before the change. Ask your Parable
representative to turn on rebuild approval for your Workspace.
Listing needs permission to view rebuilds. Approving and declining need
permission to approve them. A rebuild names its Tap, its
reasons, the
tables it rebuilds (rebuildTableIds), and its state:
An approval that sends an old
classificationHash fails with WA-TCT-031,
because the rebuild changed after you read it. Read it again and send the new
hash. Approving a rebuild that a later change replaced fails with
WA-TCT-032, and its details name the new rebuild. While another rebuild of
the same Tap runs, an approval fails with WA-TCT-033. A rebuild that is not
waiting for approval fails with WA-TCT-034.
The API Reference has the full request and
response shapes.