Skip to content
ABRQ DATADocs Abrq DIP · latest
Product page Request a trial
On this page

SQL Pipelines

Purpose#

SQL Pipelines is where you write transformations by hand. A task reads from a source, applies SQL you control, and writes into a destination table with an explicit load mode.

Unlike CDC (which mirrors) or streams (which pull an API), SQL Pipelines makes no assumptions about your model. You supply the query; Abrq DIP supplies the loading, the incremental bookkeeping, the retries, the drift handling, and the run history.

The surface lives at /etl.

How the surface is organised#

Projects group tasks. A project is the unit of ownership and access control: membership, roles, and environments are all per-project.

When you create a project two things happen automatically:

  • You become its Owner. The Owner role carries project administration — updating the project, deleting it, and managing its members.
  • A default dev environment is created. It is unprotected, and it is the only environment created for you. Everything else you add deliberately.

Inside a project, tasks are defined once and run in an environment. The environment supplies the connectors. This split is what makes the same task runnable against a scratch database and a production database — see Environments and promotion.

Before you begin#

  • Data connectors for the databases you will read from and write to. Create them first — see Data connectors.
  • Connector bindings on the environment you intend to run in. A task names a role (source, destination); the environment maps that role to a real connector. An unbound role fails the run loudly.
  • A destination you own. Abrq DIP writes rows into your table. It never runs ALTER against an operator-owned table — see Schema drift.

Required role#

Action Global role Project capability Typical project role
View projects, tasks, runs, logs viewer or higher project.view, task.view any member
Create / edit / delete a task editor or higher task.create, task.update, task.delete Developer (unprotected environments only), Maintainer, Owner
Run a task, cancel a run, replay quarantine editor or higher task.run Developer (unprotected environments only), Maintainer, Owner
Promote a task to another environment editor or higher task.promote (checked against the target environment) Maintainer, Owner
Manage environments editor or higher project.environment.manage Maintainer, Owner
Manage members, update or delete the project editor or higher project.member.manage, project.update, project.delete Owner

task.create, task.update, task.delete, task.run, connection.bind and schedule.manage are environment-gated: a Developer holds them in unprotected environments only, and is refused in a protected one with the reason protected_environment. Maintainers and Owners hold them everywhere. The global admin role is an implicit Owner on every project.

Note. The builder's validation probes (below) are gated on the global role ladder, not on project capabilities: editor or higher for preview, render-preview, drift, quality and contract checks; any authenticated user for SQL validation. They are read-only and never write to your data.

Create a project#

  1. Go to /etl and choose New project.
  2. Give it a name and description.
  3. Save.

You are now its Owner and it has a dev environment. Bind connectors to dev before you build your first task — see Environments and promotion.

Create a task#

  1. Open the project and choose New task.
  2. Pick the task type (see Task types).
  3. Set the source role and destination role — the names the environment resolves to connectors.
  4. Supply the logic: SQL for the SQL types, or a source table for table_copy.
  5. Set the destination schema and table.
  6. Pick the load mode and fill in whatever that mode requires (see Load modes).
  7. Set operational behaviour: timeout, retries, on_row_error, on_schema_drift.
  8. Validate with the probes, then save.

Task types#

Type What it does SQL Notes
sql_extract_load Runs your SELECT against the source connector and loads the result into the destination connector. required The cross-database workhorse: source and destination can be different servers or different engines.
sql_transform Runs SQL to reshape data that is already in the destination. required Use for staging-to-mart steps within one database.
table_copy Copies a named source table to a destination table with no SQL body. none The straight lift-and-shift. Fewest moving parts; also the fewest options (see the sample-run note below).

Load modes#

Mode Behaviour Requires
full_refresh Replaces the destination contents each run.
append Adds rows; never updates or deletes.
upsert Updates rows matching the keys, inserts the rest. unique/business keys
incremental Reads only rows newer than the stored watermark, then loads them. an incremental column
historical_upsert SCD Type 2. Never overwrites a current row: it expires the existing version and inserts a new one, keyed on natural keys and change-detected by a tracked-column hash. keys + history columns

For the full semantics of each mode — including exactly what "newer than the watermark" means and how the SCD2 hash is computed — see Load modes.

History columns (historical_upsert only)

historical_upsert needs four bookkeeping columns. The history columns mode decides who owns them:

Mode Meaning
auto Abrq DIP creates and names them for you.
custom You map existing destination columns to each of the four roles.

Default names in auto mode:

Role Default column
Current-version flag is_current
Version start row_effective_dt
Version end row_expired_dt
Change-detection hash record_hash

Pre- and post-SQL#

Two optional hooks run around the load, against the destination:

  • Pre-SQL runs before the load. Typical uses: truncate a staging table, create an index-free landing area, set a session parameter.
  • Post-SQL runs after a successful load. Typical uses: rebuild an index, refresh a materialized view, insert an audit row.

Keep both idempotent. A retried run runs them again.

Schema drift#

Drift is the source projection producing columns the destination table does not have.

The ETL vocabulary has exactly two values, and this is deliberate:

on_schema_drift Behaviour
block_and_alert Default. The run halts when the source projection has columns the destination lacks, and a drift alert is dispatched.
proceed The run projects to the column intersection — the drifted columns are dropped from the load — and warns.

Warning. ETL never ALTERs an operator-owned table. There is no auto_add option here, unlike CDC and file ingestion, which manage their own destination tables. If you want a new column loaded, add it to the destination yourself and the next run will pick it up.

Columns present at the destination but absent from the source are tolerated: the writer simply omits them. See Schema drift policies.

Row errors and quarantine#

on_row_error decides what happens when the destination rejects an individual row — a constraint violation, a type conflict, an over-long value.

on_row_error Behaviour
fail Default. One bad row fails the run.
quarantine Bad rows are set aside; the rest of the batch lands. Each quarantined row is stored with the error text the destination returned.

Quarantine is the right default for messy upstream data where partial progress beats a nightly failure. It is the wrong choice when the load must be all-or- nothing.

Quarantined rows are read back per run:

curl -s \
  -H "Authorization: Bearer <API_TOKEN>" \
  "https://<ABRQ_DIP_HOST>/api/v1/etl/runs/<RUN_ID>/quarantine?limit=200"

limit defaults to 200 and caps at 1000; rows come back oldest-first. Each record carries the row payload, the destination's error_text, a replay_status (pending, replayed, failed), and timestamps.

Once you have fixed the cause — widened a column, added a missing lookup row — replay the quarantined rows. Replay re-attempts either a list of specific rows or every row still pending, and reports how many were replayed, failed or skipped. Replaying requires task.run.

Data contracts#

A data contract is a list of columns the task promises the destination will have. It is enforcement, not documentation.

  • expected_columns — the promised column names.
  • enforcedefaults to on. When on, the contract gates the run.

The gate runs before any data is loaded or copied. If the destination table exists and any promised column is missing, the run is refused with a data- contract-breach error naming the destination table and the missing columns. It does not warn and continue.

Columns present at the destination but not declared in the contract are reported as extra and do not breach it. A contract asserts a floor, not an exact shape. Turn enforce off to keep the contract as a check you run by hand without gating runs.

Validate before you save#

The builder has six read-only probes. None of them writes to your data, and all of them can be run as often as you like.

Probe What it does What it returns
Validate SQL Parses the SQL for the source engine's dialect. No database connection. valid, error, dialect, normalized_sql, referenced_tables
Preview Executes the SELECT against the source and returns a bounded sample. columns, rows, row_count, truncated, masked_columns
Render preview Substitutes built-in template variables into the SQL so you can read what will actually run. variables, rendered_sql, unresolved
Check drift Compares the source projection against the destination table. destination_exists, has_drift, missing_at_dest, extra_at_dest, matched, note
Check quality Runs every enabled data-quality check (count queries only). total, passed, failed, errored, and a per-check breakdown
Check contract Compares the contract's promised columns against the destination. has_contract, destination_exists, satisfied, missing, extra, matched

Notes worth acting on:

  • Preview honours data masking. Columns your masking policy hides for you are listed in masked_columns and their raw values are never returned. A preview is not a way around masking.
  • Preview is bounded. The limit defaults to 50 rows and caps at 200; truncated tells you whether more rows existed.
  • unresolved in render preview is normal. Job parameters are supplied at job-run time, so they legitimately remain unsubstituted in the builder. What should worry you is a placeholder you expected to be a built-in showing up there — that is a typo.
  • Validate SQL always answers 200. Invalid SQL comes back as valid: false with a message, not as an HTTP error.
  • missing_at_dest is the drift that matters. extra_at_dest is tolerated.

Data-quality checks come in four types — not_null, unique, row_count, accepted_values — each at severity warn or error. In a check-quality result, errored counts failing checks at severity error.

Run a task and read the result#

  1. Open the task and choose Run.
  2. Pick the environment.
  3. Optionally switch on Sample run to bound the read — see Environments and promotion.
  4. Watch the run detail.

The run detail shows three things:

  • Logs — the ordered narrative of the run, including which connector each role resolved to, the rendered SQL, drift decisions, quarantine counts, and the failure with its cause when there is one.
  • Metrics — rows read, rows written, duration, and per-phase timings.
  • Quarantined rows — present when on_row_error = quarantine and the destination rejected rows. Each row shows its payload and the error text, and can be replayed from here.

Runs also appear in the cross-family monitor at Executions, alongside CDC, streams, file ingestions and exports.

Retries and timeouts#

  • Timeout bounds a single run. A run that exceeds it is stopped and marked failed.
  • Retry count and retry delay control automatic re-attempts after a failure. Retries repeat pre-SQL and post-SQL, which is why both should be idempotent.

Field reference#

Field Type Default Notes
Name text Identifies the task in lists, runs and logs.
Task type sql_extract_load, sql_transform, table_copy Immutable in spirit: changing it changes what the task means.
Source role source Resolved to a connector by the environment.
Destination role destination Resolved to a connector by the environment.
SQL text Required for the two SQL types; absent for table_copy.
Destination schema text The schema the task writes into.
Destination table text The table the task writes into. You own it.
Load mode see Load modes Decides the write strategy.
Keys column list Required by upsert and historical_upsert.
Incremental column column Required by incremental; also required for time-window sample runs.
History columns mode auto, custom auto historical_upsert only.
Pre-SQL text empty Runs against the destination before the load.
Post-SQL text empty Runs against the destination after a successful load.
Enabled boolean on A disabled task is skipped by jobs and schedules.
Timeout duration Bounds a single run.
Retry count integer Automatic re-attempts after failure.
Retry delay duration Wait between re-attempts.
on_row_error fail, quarantine fail See Row errors and quarantine.
on_schema_drift block_and_alert, proceed block_and_alert Two values only. ETL never ALTERs your table.

Failure modes#

What you see Cause Fix
Run fails immediately naming an environment and a connector role The environment has no connector bound to that role. Resolution is fail-loud and never falls back. Bind the role in that environment — see Environments and promotion.
Run blocked with a schema-drift error on_schema_drift = block_and_alert and the source projection has columns the destination lacks. Add the columns to your destination table, or switch to proceed to load the intersection.
Columns silently missing from loaded rows on_schema_drift = proceed — the run projected to the column intersection and warned. Read the run's warnings; add the columns to the destination if you want them.
Run refused before loading: data contract breach The contract has enforce on and the destination is missing a promised column. Add the column, or amend the contract's expected_columns.
Run fails on one bad row on_row_error = fail. Switch to quarantine if partial progress is acceptable.
Rows land but counts are short Rows were quarantined. Read the run's quarantined rows, fix the cause, replay.
Action denied with reason protected_environment You hold the Developer role and the target environment is protected. Ask a Maintainer or Owner, or work in an unprotected environment and promote.
Preview returns fewer columns than expected Data masking removed them for your identity; they are listed in masked_columns. Expected. Masking is not bypassable from the builder.
Render preview shows placeholders under unresolved Job parameters are substituted at job-run time, not in the builder. Expected — unless the placeholder was meant to be a built-in, in which case fix the spelling.
Watermark did not advance after a run The run was a sample run. Expected. Run normally when the load should count.
403 license_expired on run The deployment's license has expired. See Expiry and clock.

See also#