Platform guide
PDQ follows a layered data platform architecture where data flows through four distinct stages β from raw source extraction to quality-monitored warehouse models. Each layer has a clear responsibility and well-defined handoff to the next.
DATA SOURCES βββΊ INGEST βββΊ DLS βββΊ DWA
β
βΌ
QPI
| Layer | Full Name | Responsibility |
|---|---|---|
| INGEST | Ingestion | Extract data from source systems (databases, APIs, files) and land it in the data lake |
| DLS | Data Lake Service | Organize, archive, validate, and publish raw data through storage zones |
| DWA | Data Warehouse Automation | Model, map, and transform data into structured warehouse models |
| QPI | Quality Performance Indicator | Monitor data health, mapping coverage, and pipeline quality |
How the Layers Connectβ
Source Systems β INGEST
Source systems are registered with a connection type (Database, API, File, Custom, or Manual). Each system can have one or more source exports that define how data is extracted β what to query, which columns to include, how often to run, and what output format to produce.
INGEST β DLS
Exported data lands in the Landing Zone. From there, the DLS pipeline archives it to the Raw Zone, processes it through DLS Workers, validates it into the Trusted Zone, and profiles it in the Profile Zone.
DLS β DWA
Trusted data is synced/published into the Published Zone, where it becomes available for warehouse modeling. The DWA layer maps source fields to model attributes through a three-tier strategy: Stage β Base β Core.
DWA β QPI
Quality indicators are computed across all layers. Mapping coverage percentages, field-level governance flags, and data profiling results provide continuous feedback on pipeline health.
Zone Architectureβ
Data passes through a series of storage zones, each with a specific purpose:
ββββββββββββ ββββββββββββββββ βββββββββββββββ βββββββββββββββββ
β Landing β βββΊβ Raw Archive β βββΊβ Trusted β βββΊβ Published β
β Zone β β Zone β β Zone β β Zone β
ββββββββββββ ββββββββββββββββ βββββββββββββββ βββββββββββββββββ
Temporary Immutable Validated Query-ready
staging audit trail & cleaned tables
Zone names are configurable in Settings and follow a path convention:
{ZoneName}/{System}/{Filename}/{YYYY}/{MM}/{DD}/
Model Tiers (DWA)β
The warehouse uses a three-tier modeling strategy:
| Tier | Purpose | Example |
|---|---|---|
| Stage Model | Filters raw data for one batch | Deduplication and ordering of history |
| Base Model (Ensemble) | Integrate data from multiple sources with consistent rules | Merge CRM + ERP customer records |
| Core Model (Integrated) | Final deduplicated business entities for consumption | Clean Customer, Order, Product tables |
Key Terminologyβ
| Concept | Description |
|---|---|
| Connection | The technical link to a source system (credentials, URLs, paths) |
| Source Export | A configured extraction job from a source system (DB query, API call, file fetch, MQ listener) |
| Sourcefile | The definition of an ingested file's structure β fields, types, keys, and governance flags |
| Mapping | A link between a sourcefile field and a model attribute, optionally with transformations |
| Model | A target warehouse table with typed attributes, business keys, and relationships |
| Business Key | One or more attributes that uniquely identify a record in a model |
| CDC | Change Data Capture β track only new, modified, or deleted records between runs |
| Data Profiling | Automated quality checks and validation against the data contract |
Deep Dive by Layerβ
- INGEST
- DLS
- DWA
- QPI
The Ingest layer is responsible for pulling data out of source systems and landing it in the data lake. It handles the what, when, and how of data extraction.
Connection Typesβ
Before you can create exports, a connection must be configured for your source system. The connection type decides which fields the connection carries; within a type, the runtime runs exactly one operator class per source system, and the operator decides which of those fields are actually read.
| Connection Type | Source | Operators |
|---|---|---|
| Database (DB) | SQL databases reached with SQL | PostgreSQL, SQL Server, Oracle, MySQL |
| API | HTTP endpoints returning JSON | Generic REST, Heartpace, Salesforce Service Cloud, Talkdesk, SharePoint (Graph), Microsoft Teams, Google Drive / Sheets |
| File | File storage systems | Local disk, Amazon S3, Azure Data Lake Gen2, SFTP |
| Custom | Message queues and bespoke sources | AWS SQS, Azure Service Bus, Redis, plus per-customer connectors |
| Manual | No automated extraction | Data uploaded manually |
The queue operators ship under the Custom contract, so a queue's coordinates travel as key=value connection properties rather than as dedicated fields. Nothing about the queue behaviour changes because of it.
Three connection settings shape every export made from that connection:
| Setting | Effect |
|---|---|
AddServiceColVal | When set, every exported record gets a __service column carrying this value. Empty disables the column. |
ColumnDelimiter | Field separator when CDC writes CSV output. Ignored for JSON. |
LineDelimiter | Record separator passed to CDC. Every operator except the generic database one forces a newline regardless of this value. |
The full connection field reference β credentials, authentication methods, storage settings and queue coordinates β is in the Ingest developer guide.
Export Typesβ
Each source system can have one or more source exports. The export type matches the connection type, and fields an operator ignores are shown but marked as unused.
Database Exports
Extract data using SQL queries from connected databases. The query is streamed in 25 000-row chunks and written as newline-delimited JSON.
| Setting | Description |
|---|---|
databaseName | Reference to the connected database. Read-only, taken from the connection. |
fromClause | Table, view, function or stored procedure to query. Must be schema-qualified (dbo.Customers) β the metadata lookup splits on the dot. |
whereClause | Optional SQL condition. Inserted verbatim, so it must include the WHERE keyword. An incremental filter is appended with AND. |
sqlOverride | Full custom SQL query β replaces the generated query entirely. Time tokens are still substituted, but the incremental filter is not appended; build it into the override yourself. |
inclTables | Extra tables whose column metadata is gathered for type coercion. Defaults to the from clause. |
inclColumns | Joined with commas straight into the SELECT clause, so aliases and expressions work. ["*"] selects everything. |
filterColumn | Enables incremental loading. The comparison is numeric, so the column must be an increasing number. |
lastFilterValue | Watermark from the previous run, posted back to the platform after each run. 0 on the first run. |
Two modes:
- Standard Mode β build the query from
fromClause+whereClause+ column selection - SQL Override Mode β write a complete custom SQL statement that bypasses the generated query
Database exports also compute a __checksum per record, which enables the fast CDC comparison path.
API Exports
Extract data from HTTP endpoints. Output is always newline-delimited JSON β outputFormat is ignored here.
| Setting | Description |
|---|---|
apiUrl | Base API URL. Read-only, taken from the connection. |
apiPath | Endpoint path, appended to the base URL. |
filterCondition | Key=value pairs sent as request parameters β a query string, or a JSON body when the connection's method header is post. |
Filter conditions are string arrays like ["user_status=active", "date_from=2024-01-01"]. Time tokens are substituted in the values, and a value containing braces is parsed as JSON, so a JSON request body can be configured as a filter condition.
Errors: a 401 triggers one token refresh and a single retry. Any other status of 400 or above fails the workload, as does a response with no detectable content type.
Operator specialisations handle pagination and job models the generic operator cannot:
| Operator | Pagination model |
|---|---|
| Heartpace | limit/offset, driven by the response's meta block |
| Salesforce Service Cloud | Follows nextRecordsUrl until the result set reports itself done |
| Talkdesk | Asynchronous report jobs, polled every 10 seconds for up to 50 minutes |
| SharePoint (Graph) | Recursive folder traversal, ordered newest-first with early termination |
| Teams, Google Drive | A single request returning a values array whose first row is the header |
File Exports
Extract data from file storage systems. Matching files are copied locally, then either converted with DuckDB or passed through untouched.
| Setting | Description |
|---|---|
filePath | Source directory prefix, appended to the connection's base path. Time tokens are substituted here, so a partitioned source can be addressed with [__SCHEDULEDTIME_AS_HIVE__]. |
fileName | A substring match against the file name β not a glob. |
filterCondition | File selection and reader options (below). |
outputFormat | json or csv convert the files; binary uploads them untouched and skips CDC. Converted output is written gzipped. |
File selection keys:
| Key | Default | Description |
|---|---|---|
delete | False | Remove source files after a successful copy. There is no undo. |
use_filename_timestamps | False | True matches the timestamp in the file name; False filters on last-modified time. |
file_timestamp_format | [YYYY][MM][DD] | Timestamp pattern inside file names. The finest part present also sets the search granularity. |
start_timestamp | now | ISO datetime β start of the time window. |
end_timestamp | now | ISO datetime β end of the time window. |
find_partitions | False | Amazon S3 only. Lists sub-prefixes under the base path and searches each. Not partition-aware β for a year=/month=/day= layout, put a Hive token in filePath instead. |
Reader keys:
| Key | Default | Description |
|---|---|---|
excel_sheet, excel_range | β | Sheet name and cell range for .xlsx files. |
csv_has_header | true | First row is the header. |
csv_sample_size | 10000 | Rows sampled for type detection. |
json_sample_size | 10000 | Rows sampled for schema inference. |
json_format | auto | auto, newline_delimited, array, and the other DuckDB JSON formats. |
json_union_by_name | true | Union schemas across files instead of requiring identical ones. |
file_encoding | utf-8 | CSV encoding. |
Format is detected in the order .xlsx β .parquet β JSON β CSV; files matching none of them fall back to a binary copy. All matched files are read in one pass, so they must share a schema unless json_union_by_name covers the difference.
Queue Exports
Turn a message stream into batch files. One workload run drains the queue and then ends, so the export stays on the normal schedule instead of running its own loop.
The queue itself is configured on the connection; the export adds only the batch bounds, through filterCondition:
| Key | Default | Description |
|---|---|---|
max_batch_bytes | 31457280 (30 MB) | Close the batch file at this uncompressed size. |
max_batch_seconds | 900 (15 min) | Close the batch file after this much wall-clock time. |
max_batches_per_run | 0 (unlimited) | Cap a hot queue so it cannot starve other workloads. |
Behaviour worth knowing:
- An empty queue writes nothing. No zero-byte file is created and no downstream processing runs.
- CDC is bypassed. Events are consumed once and never updated, so set
enableCDC: 0to avoid a pointless baseline file. - Delivery is at-least-once. Acknowledgement happens after the batch is flushed to disk, so a crash mid-run redelivers rather than loses.
- Malformed messages are forwarded, written with a
__parseErrormarker, rather than left on the queue as poison. - Batch files are named
<alias>_<suffix>_batch<NNNNNN>.txt.
This family targets high-volume streams of small events. Land large payloads in blob storage and ingest them as a file export, letting the queue carry only the pointer.
Custom Exports
Minimal configuration for proprietary integrations. The connection's properties are the only configuration channel, filterCondition is substituted and handed to the connector, and outputFormat becomes the output file extension.
Schedulingβ
All export types support scheduling:
| Setting | Description | Example |
|---|---|---|
scheduleCycle | Frequency | never, hourly, daily, monthly |
scheduleCycleInterval | Multiplier | 2 with hourly = every 2 hours |
scheduleDoNotStartBeforeTime | Earliest allowed start | 02:00:00 (don't start before 2 AM) |
never disables an export without deleting it.
Export Strategiesβ
Choosing the right export strategy is critical for balancing data freshness against cost and volume.
Full Export
Extracts the entire dataset every time. Simple but expensive for large tables.
When to use:
- Small reference/dimension tables
- Source systems that don't support change tracking
- Initial loads or periodic full refreshes
How to configure:
- No special settings needed β this is the default behavior
- Leave
stopAtRow: -1for no row limit
Incremental Export (Watermark)
Reads only the rows the source has added since the last run. Database sources only.
When to use:
- Large transaction tables where full exports are too costly
- Tables carrying a reliably increasing key or sequence
How to configure:
- Set
filterColumnto an increasing numeric column β the comparison is numeric, so a text or date column will not work - Leave
lastFilterValuealone; the agent posts the new watermark back after each run, starting from0
How it works:
- The generated query gains a
filterColumn > lastFilterValuecondition - The column's maximum is tracked per chunk and returned as the new watermark
- A run truncated by
stopAtRowdoes not advance the watermark, so nothing is skipped next time sqlOverridebypasses this entirely β the filter is not appended to a custom query
Change Data Capture (CDC)
Compares this run's extract against the previous one and marks each record as changed, deleted or unchanged. Available for every source type that produces rows.
When to use:
- Sources with no change indicator of their own
- Any source where downstream needs to know about deletions
How to configure:
- Set
enableCDC: 1on the export (0exports everything every run) - Mark the fields to track with
includeInCDC: 1in the sourcefile structure - Use a
validFromfield to identify which version of a record is current - Set
sortOrderon the versioning field (positive = descending, most recent first)
How it works:
- The previous extract is kept as a baseline file named
<alias>_latestversion.txtand compared in DuckDB - Records are emitted with a
__cdccolumn ofchange,deleteorequal - Where a
__checksumis present β database sources β an indexed anti-join replaces the full comparison, which is significantly faster on wide tables - If the column set changed between runs, the whole new load is returned and flagged as new
- Output is split at roughly 2 GB per file; every part is uploaded
No baseline file means a full load, so the first run after enabling CDC always exports everything. Each run also unions in a small sample of the new data as a deliberate re-seed, so CDC output is never a pure delta.
stopAtRow forces CDC off for that run and discards the truncated output rather than keeping it as the baseline β a capped extract compared against a full baseline would read every row it did not write as a delete. It is a smoke-test knob, not a throttle.
Binary file exports and queue exports bypass CDC entirely.
Sliding Window
A time-bounded extraction that moves forward with each run, because the window is expressed as time tokens that re-render every run rather than as fixed dates.
When to use:
- File ingestion where filenames or partition paths carry timestamps
- APIs with date-range parameters
- Scenarios where you need the last N days/hours
How to configure (file exports):
- Set
use_filename_timestamps: Trueif the timestamp is in the file name, andfile_timestamp_formatto match it β otherwise the window is matched against last-modified time - Express the window with tokens rather than dates:
filterCondition: ["start_timestamp=[__SCHEDULEDTIME_MINUS_1_DAYS__]", "end_timestamp=[__SCHEDULEDTIME__]"]
- For a partitioned source, address the partition from
filePathwith[__SCHEDULEDTIME_AS_HIVE__]
How to configure (API exports): Use the same tokens in the request parameters:
filterCondition: ["date_from=[__LASTEXECUTION__]", "date_to=[__SCHEDULEDTIME__]"]
One run reads one window. [__SCHEDULEDTIME__] is the slot the run belongs to rather than the clock time, so a retry reads the same window β but a missed day means replaying that schedule, not widening the window.
Column Selectionβ
Control which columns appear in the exported data.
Include & Exclude Modes
Include Mode (default):
By default, inclColumns: ["*"] β all columns are included. To include only specific columns:
inclColumns: ["CustomerID", "Name", "Email", "CreatedDate"]
exclColumns: []
Exclude Mode: Include everything except specific columns:
inclColumns: ["*"]
exclColumns: ["InternalNotes", "TempFlag", "DebugData"]
Rules:
- If
inclColumnscontains"*", all fields are included exclColumnsacts as a blacklist applied on top of the inclusion list β deny wins- The UI prevents leaving no included fields β it defaults back to
["*"] - Column names are case-sensitive and must match the source exactly
On database exports the two lists behave differently. inclColumns is joined into the SELECT clause, so it is the only way to drop a column; exclColumns merely suppresses type coercion and does not remove anything from the output.
Data Transformationsβ
Substitutions
Substitutions derive or default a column while the export is being written. Each one is defined as { column, alias, replace, type }:
| Property | Description |
|---|---|
column | The source column the substitution reads |
alias | Optional target column. Without it, the source column is overwritten |
replace | The expression or literal used as the replacement |
type | transform β replace is an expression evaluated against the cell value, applied only when the value is not null. null β replace is the literal default used when the value is null |
Typical uses are null replacement and masking sensitive values before they leave the source.
replace is evaluated as code by the agent. Treat export definitions as trusted configuration, not as user input.
Output Format & File Naming
Output Format (outputFormat):
| Format | Description |
|---|---|
json | Newline-delimited JSON. The default, and the only format the plain API operators write |
csv | Delimited text, using the connection's ColumnDelimiter |
binary | Files uploaded untouched. Skips conversion and CDC; file sources only |
TABLE, ICEBERG and JSON are target formats, chosen on the sourcefile in DLS β they are not export output formats.
Dynamic File Naming (Suffix):
The suffix appended to the output file name is either a literal string or a time token:
| Token | Value |
|---|---|
[__SCHEDULEDTIME__] | The slot this run belongs to β unchanged by a late start or a retry |
[__EXECUTIONTIME__] | Actual execution time, in the agent's timezone |
[__LASTEXECUTION__] | Previous successful run's scheduled time (1900-01-01T00:00:00 on the first run) |
The same tokens work in request parameters, file paths, where clauses and the ends of a file-selection window. They take an optional offset and an optional format:
[__<TIME>[_MINUS_<N>_<UNIT>][_AS_<FORMAT>]__]
<UNIT> is SECONDS, MINUTES, HOURS, DAYS or WEEKS; there is no _PLUS_, since an export reads a window that has already happened. <FORMAT> is TIMESTAMP, DATETIME, DATE, DATECOMPACT, TIME, ISO, EPOCH, EPOCHMS or HIVE, or a literal pattern built from [YYYY] [YY] [MM] [DD] [HH] [MI] [SS]. A value with no _AS_ segment inherits the export's dateTimeFormat, which defaults to TIMESTAMP β or DATETIME for database sources, which suits SQL literals.
HIVE renders a partition path such as year=2026/month=8/day=13 rather than a datetime, for reading sources laid out that way. It belongs in a file path and cannot be used as the export-wide format. See the Ingest developer guide for the grain and padding variants.
Row Limiting:
Set stopAtRow to limit the size of an export: -1 is no limit, and any positive number stops the run there. What it counts depends on the source:
| Source | Unit |
|---|---|
| Database, API, custom | Rows |
| File, converted | Rows written β every matching file is still downloaded |
| File, binary | Files β the only case where the unit is not rows |
| Queue | Messages, counted across the whole run |
Because a capped run also disables CDC, stopAtRow is for testing a new export rather than for limiting a production one.
Technical Columns
Every export adds columns of its own alongside the source data:
| Column | Added when | Value |
|---|---|---|
__service | AddServiceColVal is set on the connection | That value |
__scheduledAt | Always | The run's scheduled time, falling back to export time |
__exportedAt | Always | When the export started writing |
__checksum | Database sources | Hash of the record, enabling the fast CDC path |
__cdc | CDC is enabled | change, delete or equal |
__parseError | A queue message could not be parsed | The parse error, with the raw body preserved |
Destination Pathsβ
Exported data lands in configured zone paths:
| Zone | Source | Purpose |
|---|---|---|
| Landing Zone | From Settings (landingZoneName) | Temporary staging |
| Raw Zone | From Settings (rawZoneName) | Permanent archive |
| Trusted Zone | From Settings (trustedZoneName) | Validated data |
Default path pattern: {system}/{filename}/{YYYY}/{MM}/{DD}/
Sourcefile Structureβ
Once data is ingested, it's described by a sourcefile β the schema definition for the incoming data.
Key Properties
| Property | Description |
|---|---|
sourceFilename | Unique identifier |
system | Source system reference |
fileType | CSV, JSON, or XML |
fileEncoding | UTF-8, UTF-16, Windows-1252 |
columnDelimiter | Delimiter for CSV files |
targetMethod | replace, append or upsert in the UI; the API accepts more β see the DLS field reference |
targetFormat | TABLE, ICEBERG or JSON |
targetNormalization | single, lists, or lists_objects |
fileCompressionType | Compression type (gzip levels 1β9 or none) |
enableEncryption | Encryption flag |
Hierarchical File Structure
For nested data (e.g. JSON with arrays), the sourcefile supports a hierarchical structure:
Level 0 (OBJECT): Root
βββ customer.id (keyFieldIndicator=1)
βββ customer.name
βββ customer.loadDate (validFrom=1)
Level 1 (LIST): orders (useForSplittingRecords=1)
βββ order.orderId
βββ order.amount
βββ order.status
Each level can be an OBJECT (single record) or LIST (array that gets split into rows).
Field-Level Properties
| Property | Description |
|---|---|
fieldKey | Source column name (case-sensitive) |
fieldAlias | Optional renamed column name |
dataType | Varchar, Integer, Decimal, Boolean, Time, Date, Timestamp |
keyFieldIndicator | 1 = part of primary key |
keyOrder | Position in composite key |
fieldOrder | Column ordering in target |
excludeField | 1 = skip this field entirely |
excludeFromProfiling | 1 = don't run quality checks |
sensitive | 1 = PII/sensitive data flag |
includeInCDC | 1 = track changes for this field |
validFrom | 1 = SCD Type 2 versioning field (max one per level) |
fieldDomain | Governance classification category |
Best Practicesβ
Choosing an Export Strategy
| Scenario | Recommended Strategy |
|---|---|
| Small dimension table (<100K rows) | Full export with CDC, daily |
| Large transaction table with an increasing key | Incremental watermark on filterColumn |
| Large table with no change indicator | Full export with CDC β the checksum path keeps the comparison cheap |
| File drop with timestamped filenames | Sliding window on start_timestamp / end_timestamp |
Partitioned lake (year=/month=/day=) | Hive token in filePath, one partition per run |
| API with pagination + date filters | Sliding window through request parameters |
| Message queue | Batch bounds on the connection, CDC off |
| Initial data load | Full export, then switch to incremental |
Column Selection Tips
- Start with
inclColumns: ["*"]and useexclColumnsto remove unwanted columns - On database exports, drop columns from
inclColumnsinstead βexclColumnsdoes not remove them there - Exclude large text/blob columns that aren't needed downstream
- Exclude internal/debug columns that add noise
- Remember: column names are case-sensitive
Scheduling Considerations
- Use
scheduleDoNotStartBeforeTimeto avoid running during peak hours - For dependent exports, stagger schedules (e.g. dimension tables before fact tables)
- Use
monthlycycle for slowly-changing reference data - Use
hourlywith a small interval for near-real-time needs
The DLS layer manages how data flows through storage zones β from raw landing to validated, query-ready tables. It handles archiving, processing, validation, and publishing.
Zone Architectureβ
ββββββββββββ ββββββββββββββββ βββββββββββββββ βββββββββββββββββ
β Landing β βββΊβ Raw Archive β βββΊβ Trusted β βββΊβ Published β
β Zone β β Zone β β Zone β β Zone β
ββββββββββββ ββββββββββββββββ βββββββββββββββ βββββββββββββββββ
Landing Zone
- Purpose: Temporary staging area where exported data first arrives
- Lifecycle: Files are processed and moved to Raw Archive β not retained long-term
- Path format:
{landingZoneName}/{system}/{filename}/ - No date tokens β landing is a transient buffer
Raw Archive Zone
- Purpose: Immutable, permanent record of every file that was ingested
- Lifecycle: Write-once, never modified β serves as the audit trail
- Path format:
{rawZoneName}/{system}/{filename}/{YYYY}/{MM}/{DD}/ - Date-partitioned for efficient historical queries
Trusted Zone
- Purpose: Validated, cleaned, and standardized data ready for modeling
- Lifecycle: Updated by DLS Workers after validation passes
- Path format:
{trustedZoneName}/{system}/{filename}/{YYYY}/{MM}/{DD}/ - Quality gate β data only reaches Trusted after passing profiling checks
Published Zone
- Purpose: SQL-optimized tables ready for direct querying and warehouse consumption
- Lifecycle: Synced from Trusted via the Publish step
- Controlled by:
publishedZoneNameandpublishedModelSchemain Settings
Zone Configurationβ
All zone names are configurable in Settings β Data Lake Settings:
| Setting | Description | Example |
|---|---|---|
landingZoneName | Landing zone label | landing |
rawZoneName | Raw archive label | raw |
trustedZoneName | Trusted zone label | trusted |
publishedZoneName | Published zone label | published |
Storage Settings
| Setting | Description |
|---|---|
storageIntegration | Snowflake storage integration name (required for external stages) |
externalVolume | External volume for data staging |
formatFile | Format definition file (JSON/text) |
tableCatalog | Snowflake catalog specification |
publishedModelSchema | Target schema for published models |
The DLS Pipelineβ
Landing βββΊ Raw Archive ββ βΊ DLS Workers (Γ3) βββΊ Trusted + Profile
β
βΌ
Sync/Publish
β
βΌ
Published Zone
DLS Workers β three parallel worker processes handle data transformation and validation:
- Parse incoming file formats (CSV, JSON, XML)
- Apply data type coercion and normalization
- Run profiling checks when
enableProfileris active - Write validated data to Trusted Zone
- Write profiling statistics to Profile Zone
Sync/Publish Step β bridges the gap between DLS (Trusted Zone) and DWA (Published Zone):
- Generates loading SQL for the target format (TABLE or ICEBERG)
- Applies the configured
targetMethod(replace, append, or upsert) - Creates or updates tables in the Published schema
Publish Patternsβ
Target Methods
The targetMethod on a sourcefile controls how data is loaded into published tables:
| Method | Behavior | When to Use |
|---|---|---|
| replace | Drop and recreate the target table with new data | Small tables, full refreshes, reference data |
| append | Add new rows to existing table | Event/log data, append-only patterns |
| upsert | Insert new rows, update existing rows by key | Dimension tables with SCD tracking, master data |
Target Formats
| Format | Description | Considerations |
|---|---|---|
| TABLE | Standard Snowflake table | Best for most use cases, full SQL support |
| ICEBERG | Apache Iceberg table format | Better for multi-engine access, time travel, schema evolution |
| JSON | Semi-structured storage | When structure is unknown or highly variable |
SQL Generation
PDQ generates the loading SQL automatically based on your configuration:
- Sourcefile structure defines the columns and types
- Target method determines INSERT/MERGE/CREATE OR REPLACE behavior
- Mappings control which fields map to which model attributes
- Settings provide schema, catalog, and storage integration details
You can preview the generated SQL before execution to verify it matches your intent.
Publish Considerationsβ
Data Normalization
The targetNormalization setting controls how nested/hierarchical data is flattened:
| Mode | Behavior | Example |
|---|---|---|
single | One table per sourcefile | Flat CSV β single target table |
lists | Split arrays into separate tables | JSON with nested arrays β parent + child tables |
lists_objects | Split arrays and nested objects | Deeply nested JSON β fully normalized table set |
Path Token Support
Zone paths support date tokens for time-based partitioning:
| Token | Value |
|---|---|
{YYYY} | Four-digit year |
{MM} | Two-digit month |
{DD} | Two-digit day |
{HH} | Two-digit hour |
Example: trusted/SAP/customers/{YYYY}/{MM}/{DD}/
Landing Zone paths do not support date tokens β they use a flat path.
Compression & Encryption
| Setting | Options |
|---|---|
fileCompressionType | None, or gzip (levels 1β9) |
fileCompressionLevel | 1 (fastest) to 9 (smallest) |
enableEncryption | Enable/disable encryption at rest |
Supported file encodings: UTF-8 (default, recommended), UTF-16, Windows-1252
Best Practicesβ
Zone Design
- Keep Raw Archive immutable β never modify or delete files in the Raw Zone. It's your recovery path.
- Use date partitioning β always include
{YYYY}/{MM}/{DD}in Raw and Trusted paths for efficient querying and lifecycle management. - Name zones clearly β use descriptive names that make it obvious where data is in its lifecycle.
Publishing Strategy
| Scenario | Recommended Approach |
|---|---|
| Small reference table (<100K rows) | replace method, TABLE format |
| Large fact table (millions of rows) | append method with CDC upstream |
| Slowly changing dimension | upsert method with business keys |
| Multi-engine analytics (Spark + Snowflake) | ICEBERG format |
| Unknown/evolving schema | JSON format initially, migrate to TABLE once stable |
Performance Considerations
- Profiling adds compute cost β enable
enableProfilerfor critical data, consider disabling for high-volume, well-understood sources - Compression saves storage β use gzip level 6 as a good balance between speed and size
- Choose upsert carefully β it requires well-defined business keys and is slower than append for large volumes
- Partition by date β it enables efficient pruning and makes cleanup/retention policies easier to implement
Data Governance
- Mark PII fields as
sensitive: 1in the sourcefile structure - Use
excludeFromProfiling: 1for fields that should not be analyzed - Assign
fieldDomainclassifications for compliance tracking - Exclude internal/debug fields with
excludeField: 1to keep published data clean
The DWA layer is where raw data becomes structured business knowledge. It handles data modeling, source-to-target mapping, transformations, and relationship management.
Three-Tier Model Strategyβ
βββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β Stage Model β βββΊβ Base Model β βββΊβ Core Model β
β (Normalize) β β (Integrate) β β (Consume) β
βββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
Stage Model (Normalization)
- Purpose: Standardize raw data from different sources into a common format
- What happens here: Column renaming, date format normalization, data type casting
- Configuration:
stageModelName,stageModelSchema,stageModelColumnin Settings
Base Model (Ensemble / Integration)
- Purpose: Merge data from multiple sources using consistent business rules
- What happens here: Deduplication, business key matching, cross-source consolidation
- Configuration:
baseModelName,baseModelSchemain Settings
Core Model (Integrated / Consumption)
- Purpose: Final, clean business entities ready for analytics and reporting
- What happens here: Publish unified
Customer,Order,Producttables - Configuration:
coreModelName,coreModelSchemain Settings
Additionally, Datamart Models can be configured for specific analytical use cases via datamartModelNames and datamartModelSchemas in Settings.
Creating Modelsβ
A model represents a target warehouse table. Each model has:
| Property | Description |
|---|---|
object | Unique model name |
alias | Display name |
description | Business purpose |
objectType | Specific (single source) or Combined (multi-source) |
loadingPattern | How data is loaded (see below) |
attributes | Columns/fields with types and keys |
relationships | Foreign keys to other models |
Loading Patterns
| Pattern | Behavior | When to Use |
|---|---|---|
full | Replace entire table each load | Small dimensions, reference data |
incremental | Append only new/changed records | Large fact tables, event logs |
transaction | Each row is an immutable event | Financial transactions, audit logs |
dayspan | Partition by date range | Time-series data, daily snapshots |
none | No automated loading | Manual or externally managed |
Model Attributesβ
Attributes define the columns in your model:
| Property | Description |
|---|---|
attribute | Column name |
alias | Display name (optional) |
dataType | Varchar, Integer, Decimal, Boolean, Time, Date, Timestamp |
isBusinessKey | Y / N β is this part of the unique identifier? |
businessKeyOrder | Priority in composite keys (1, 2, 3β¦) |
description | Business definition |
Business Keys
Business keys uniquely identify a record and are critical for deduplication and upsert operations.
Tips:
- Mark attributes as business keys with
isBusinessKey: "Y" - Use
businessKeyOrderto define the priority in composite keys - Drag rows to reorder key priority in the UI
- Map business keys before attributes β they must exist before dependent mappings
Example β Composite business key:
Customer model:
#1 SystemCode (businessKeyOrder: 1)
#2 CustomerID (businessKeyOrder: 2)
Mappings β Source to Targetβ
Mappings connect sourcefile fields to model attributes. They are the core mechanism for data transformation in DWA.
Mapping Properties
| Property | Description |
|---|---|
source | Source field reference |
target | Target model attribute |
isKeyMapping | Yes = business key mapping |
keyOrder | Key priority order |
filter | SQL WHERE condition for this mapping |
optionalCalculation | SQL expression for transformation |
sortOrder | Sort priority (negative = ASC, positive = DESC) |
useFieldNameAsValue | Yes = use the field name itself as the value |
mappingNumber | Group identifier for multi-mapping scenarios |
Mapping Types
| Type | Visual Indicator | Description |
|---|---|---|
| Key Mapping | Teal, animated | Business key β maps to unique identifier |
| Calculation Mapping | Green | Includes an SQL transformation |
| Relationship Mapping | Purple | Maps to a foreign key relationship |
| Standard Mapping | Gray | Simple field-to-attribute link |
Transformationsβ
Optional Calculations
Apply SQL expressions to transform data during mapping:
-- Case normalization
UPPER(CustomerName)
-- String manipulation
LEFT(OrderDate, 10)
CONCAT(FirstName, ' ', LastName)
-- Numeric calculations
ROUND(PriceUSD / 1.08, 2)
Amount * 0.25
-- Conditional logic
CASE WHEN Status = 'A' THEN 'Active' ELSE 'Inactive' END
-- Date formatting
TO_DATE(TransactionDate, 'YYYY-MM-DD')
Calculations use the target database's SQL dialect. Field names are case-sensitive and must match the source exactly.
Filters
Apply row-level conditions to include/exclude records for a specific mapping:
-- Only non-null values
IS NOT NULL
-- Pattern matching
LIKE 'ACTIVE%'
-- Numeric comparisons
> 0
-- Equality
= 'SALE'
Each filter applies to a single source field. For multiple conditions, create separate mapping entries.
Sort Order
Controls record ordering for CDC and versioning scenarios:
| Value | Direction | Use Case |
|---|---|---|
Positive (e.g. 1) | Descending | Most recent first (typical for LoadDate) |
Negative (e.g. -2) | Ascending | Oldest first |
0 | No sorting | Default |
Use Field Name as Value:
When useFieldNameAsValue: "Yes", the literal field name is used as the data value instead of the field's content. Useful for type indicators and enum-like columns.
Relationships (Foreign Keys)β
Models can reference other models through relationships:
| Property | Description |
|---|---|
toObject | Target model |
toAttribute | Target attribute (the referenced key) |
relationType | 1:N (one-to-many) or M:M (many-to-many) |
relationRole | Describes the role of the relationship |
Visual indicators in the mapping diagram:
- Purple edges with
RELbadge for relationship mappings - 1:N shown as purple dashed lines
- M:M shown as magenta dashed lines
Modeling Scenariosβ
Multi-Source Customer Master
You have customer data in Salesforce, Marketo, and your ERP. Goal: unified Customer model.
Step 1 β Stage Model (normalize each source):
Salesforce: Contact.FirstName β Stage.FirstName (UPPER(FirstName))
Marketo: Lead.first_name β Stage.FirstName (UPPER(first_name))
ERP: CUST.FNAME β Stage.FirstName (UPPER(FNAME))
Step 2 β Base Model (integrate with dedup):
Business Key: CustomerID (mapped from each source's unique ID)
Filter: IS NOT NULL (exclude test/incomplete records)
Loading Pattern: incremental
Step 3 β Core Model (final dimension):
Business Key: CustomerID (composite if needed)
ValidFrom: LoadDate (validFrom=1, sortOrder=1 for DESC)
Loading Pattern: incremental with CDC
Early-Arriving Data (Master Not Yet Loaded)
Transactions normally arrive before the master data they point at β an order for a customer who is not in the customer extract yet, a ticket for an asset registered in a system that runs on a slower schedule. The transaction is not held back, rejected, or parked for repair.
How it works:
- Relationships are carried by the business key, not by a surrogate resolved at load time. The child record stores the parent's business key, so the reference is complete the moment the child lands.
- The parent model gets a record for that key immediately, carrying the key and nothing else. The relationship resolves; only the descriptive attributes are missing.
- When the master source loads, its attributes attach to the same business key. The key-only record becomes a full record β same key, same relationships, now with its attributes.
- Because the core is historised, the fill-in is a new version rather than an overwrite. When the master data became known stays visible and auditable.
Day 1 Order 5591 β Customer "C-4417" Customer C-4417: key only, no attributes
Day 3 Customer master loads Customer C-4417: name, segment, address
Order 5591 unchanged β it was never wrong
No manual intervention: the child load is not re-run, no reconciliation job resolves keys afterwards, and there is no placeholder record to find and clean up later.
| Question | Answer |
|---|---|
| Does the transaction load fail or wait? | Neither. It loads, with its relationship intact |
| Do I re-run anything when the master arrives? | No β the next master load completes the record |
| How do I see what is still pending? | A record carrying a business key and no attributes |
| Enforced foreign keys? | The key-only parent row is what keeps the child load legal on targets that enforce foreign keys β see target platforms |
A QPI check written as "zero orphans" will report pending keys as a failure while the master is still outstanding. Where early-arriving data is normal, scope the check to keys older than the master source's own schedule rather than to any unresolved key at all.
Hierarchical JSON with Nested Arrays
Source: API returning customers with embedded order arrays.
Sourcefile structure:
Level 0 (OBJECT): Root
βββ customer.id (keyFieldIndicator=1)
βββ customer.name
βββ customer.loadDate (validFrom=1)
Level 1 (LIST): orders (useForSplittingRecords=1)
βββ order.orderId
βββ order.amount
βββ order.status
Mapping strategy:
customer.id β Customer.CustomerKey(isKeyMapping=Yes)customer.name β Customer.Namecustomer.loadDateβ sort by DESC for latest versionorder.orderId β Order.OrderKey(isKeyMapping=Yes)order.statusβ filter!= 'CANCELLED'customer.id β Order.CustomerID(relationship mapping)
Calculated Fields
Source: Raw sales data needing business calculations.
| Source Field | Target Attribute | Calculation |
|---|---|---|
amount | TaxAmount | amount * 0.10 |
priceUSD | PriceEUR | ROUND(priceUSD / 1.08, 2) |
transactionType | TransactionType | filter: = 'SALE' |
rawDate | TransactionDate | TO_DATE(rawDate, 'YYYY-MM-DD') |
SCD Type 2 (Slowly Changing Dimension)
Track historical changes to dimension records.
All fields and relationships are by default tracking history, hence SCD Type 2 compatible.
Result: Each change creates a new version. The sort order ensures the latest version is identifiable.
Sensitive Data Handling
Handle PII and governance requirements in your model.
| Field | Configuration | Effect |
|---|---|---|
ssn | sensitive: 1, excludeFromProfiling: 1 | Masked in profiles, flagged as PII |
creditCard | sensitive: 1, excludeField: 1 | Excluded from target entirely |
email | sensitive: 1, fieldDomain: "PII" | Tracked for governance compliance |
internalId | excludeField: 1 | Skipped β not needed downstream |
Batch Editingβ
When you need to apply the same configuration to many fields, use the Batch Edit Panel:
- Select multiple attributes with checkboxes
- Apply filter, calculation, sort order, or field name settings in bulk
- Useful for applying a common transformation across a large sourcefile
Mapping Diagramβ
The visual mapping diagram provides a drag-and-drop canvas for creating and reviewing mappings:
Views & Features
Views:
- Tree View β traditional hierarchical editor for detailed field-by-field work
- Diagram View β React Flow-based canvas showing the full mapping picture
Diagram Features:
- Color-coded edges by mapping type (key, calculation, relationship, standard)
- Dependency graph β orange borders highlight fields involved in calculation formulas
- Search & filter β find fields by name, path, or data type with auto-zoom
- Export β download as PNG (1920Γ1080) or SVG vector with timestamp
- Mini-map β overview of the entire mapping structure
Best Practicesβ
Model Design
- Start with business keys β define unique identifiers before adding attributes
- Use composite keys sparingly β 2β3 fields max for maintainability
- Write clear descriptions β they serve as documentation for downstream consumers
- Choose loading patterns carefully β
fullfor small tables,incrementalfor large ones
Mapping Strategy
- Map keys first, attributes second β business keys must exist before dependent mappings
- Use calculations for normalization β standardize formats (dates, casing) at the mapping level
- Apply filters at the mapping level β not in the export WHERE clause β for model-specific exclusions
- Group related mappings β use
mappingNumberto organize multi-mapping scenarios
Performance & Governance
- Avoid over-mapping β only map fields that are actually needed in the model
- Use excludeField β remove noise columns before they enter the modeling pipeline
- Profile selectively β exclude high-volume, well-understood fields from profiling
- Flag sensitive fields early β mark PII in the sourcefile structure, not as an afterthought
- Assign field domains β use classification categories for compliance tracking
- Document relationships β add
relationRoledescriptions to explain why models are linked
The QPI layer provides continuous monitoring of your data platform's health. It tracks mapping coverage, field-level governance, data profiling, and pipeline completeness β giving you a single pane of glass for data quality.
Platform-Level Metricsβ
The dashboard displays five key counters that give an instant pulse check:
| Metric | What It Measures | Why It Matters |
|---|---|---|
| Systems | Total connected source systems | Are all expected sources registered? |
| Source Files | Total sourcefile definitions | Are all expected data feeds defined? |
| Fields | Total fields across all sourcefiles | Is the data contract complete? |
| Mappings | Total source-to-model mappings | Are sources connected to the warehouse? |
| Models | Total warehouse models | Is the target schema fully defined? |
A sudden drop in any counter may indicate a configuration issue or accidental deletion.
Mapping Coverageβ
The most important quality indicator. Mapping coverage measures what percentage of a sourcefile's fields are mapped to a model.
Calculation: coverage = (mappedFieldCount / fieldCount) Γ 100
| Coverage | Status | Visual | Meaning |
|---|---|---|---|
| 80β100% | Healthy | Green | Most fields are mapped and modeled |
| 40β79% | Warning | Yellow | Significant unmapped fields β review needed |
| 0β39% | Critical | Red | Mostly unmapped β likely incomplete setup |
Where it appears:
- Data Lineage Flow β edges between sourcefiles and models are color-coded by coverage
- Sourcefile Nodes β progress bars show coverage percentage per file
- Animated edges β connections below 80% coverage are animated to draw attention
Connection Healthβ
Each source system's connection type and export status is monitored:
| Indicator | Values | What to Watch |
|---|---|---|
| Connection Type | DB, API, File, Custom, Manual | Missing connections show as "undefined" |
| Has Exports | Yes / No | Systems without exports aren't producing data |
| Export Count | Number of configured exports | Expect at least one per active system |
In the Architecture Diagram, systems with exports connect via solid arrows through the Ingest Agent, while systems without exports connect with dashed arrows (direct transfer).
Field-Level Governanceβ
Every field in a sourcefile carries governance flags that serve as quality indicators:
| Flag | Purpose | Monitor For |
|---|---|---|
keyFieldIndicator | Marks primary/business keys | Missing keys = no deduplication possible |
sensitive | Flags PII/sensitive data | Untagged PII = compliance risk |
excludeField | Removes field from pipeline | Over-exclusion may drop needed data |
excludeFromProfiling | Skips quality checks | Too many exclusions = blind spots |
validFrom | SCD Type 2 versioning field | Missing = no historical tracking |
fieldDomain | Governance classification | Unclassified fields = governance gap |
These flags are not only indicators β they are instructions. The generator reads them when it builds the target tables: keyFieldIndicator becomes a primary key on the publish table and a unique constraint on the core table, fieldDomain becomes a category tag on the column, sensitive becomes a sensitivity tag and, on platforms that support it, a column mask. A field classified once on the source is classified everywhere the platform carries it, including on the normalised child tables that inherit its key.
That is why an unclassified field is a governance gap rather than a documentation gap: nothing downstream will invent the classification for you, and nothing will apply it retroactively to tables already created.
β What lands in the target environment
Data Profilingβ
What profiling validates
When enableProfiler is active on a sourcefile, the system runs continuous quality checks against the data contract:
- Data type conformance (are integers actually integers?)
- Null/empty field ratios
- Value distribution patterns
- Schema drift detection (new or missing columns)
Configuration:
- Enable per sourcefile via
enableProfiler: 1 - Exclude specific fields with
excludeFromProfiling: 1 - Results are written to the Profile Zone in the DLS pipeline
Trade-offs:
- Profiling increases compute resource usage
- Disable for high-volume, well-understood, stable sources
- Always enable for new integrations until the data contract is validated
Model Relationshipsβ
The Data Lineage Flow tracks how models connect to each other:
| Relationship Type | Visual | What to Monitor |
|---|---|---|
| 1:N (One-to-Many) | Purple dashed lines | Missing = isolated models with no joins |
| M:M (Many-to-Many) | Magenta dashed lines | Excessive = possible modeling issue |
Additional Tasks (Extended Monitoring)β
Configure pre- and post-processing tasks
For monitoring needs beyond built-in indicators, configure Additional Tasks:
| Property | Description |
|---|---|
Name | Task identifier |
Type | pre (before main processing) or post (after) |
RunCommand | Executable command to run |
Dependency[] | Other tasks that must complete first |
SortOrder | Execution sequence |
Use cases:
- Data freshness checks β verify that data arrived within expected time windows
- Row count validation β compare actual vs expected record counts
- Schema drift detection β alert when source structure changes unexpectedly
- Threshold alerts β flag when metrics exceed acceptable ranges
- Post-load aggregation β compute summary statistics after each load
Dashboards & Visualizationsβ
Architecture Diagram
The end-to-end pipeline visualization showing all four layers:
Data Sources (gray) β Ingest (teal) β DLS (blue) β DWA (purple)
What to monitor:
- Are all expected systems present?
- Do all systems have connections and exports?
- Is the full pipeline chain intact (no broken links)?
Data Lineage Flow
A detailed view showing how data flows from systems through sourcefiles to models:
Systems β Sourcefiles β Models β Related Models
Three-column layout:
- Left: Source systems with sourcefile counts
- Center: Sourcefiles with coverage progress bars
- Right: Target models with attribute counts and relationships
What to monitor:
- Red/yellow edges indicating low mapping coverage
- Animated edges drawing attention to incomplete mappings
- Orphaned models (no incoming mappings)
- Orphaned sourcefiles (no outgoing mappings)
Source Detail Panel
Click any system in the Architecture Diagram to see:
- Connection type and status
- All sourcefiles for that system
- Field counts per sourcefile
- Mapping counts per sourcefile
- Expandable path structure with field details
Monitoring Checklistsβ
Daily Checks
| Check | Where | What to Look For |
|---|---|---|
| Platform counters | Dashboard Stats | Unexpected drops in any counter |
| Mapping coverage | Data Lineage Flow | New red/yellow edges |
| System connections | Architecture Diagram | Missing or broken connections |
| Export status | Architecture Diagram | Systems without exports (dashed lines) |
Weekly Checks
| Check | Where | What to Look For |
|---|---|---|
| Governance coverage | Field Editor | Fields missing sensitive or fieldDomain flags |
| Profiling results | Profile Zone | Anomalies in data type conformance or null ratios |
| Model completeness | Model Editor | Models with few or no attributes |
| Relationship health | Data Lineage Flow | Orphaned or disconnected models |
On New Integration
| Step | Action |
|---|---|
| 1 | Verify system appears in Architecture Diagram |
| 2 | Confirm connection type is correct |
| 3 | Check exports are configured and scheduled |
| 4 | Enable enableProfiler on all new sourcefiles |
| 5 | Mark sensitive fields with sensitive: 1 |
| 6 | Assign fieldDomain classifications |
| 7 | Create mappings and verify coverage reaches 80%+ |
| 8 | Validate profiling results after first load |
Best Practicesβ
Coverage Targets
| Sourcefile Type | Target Coverage | Rationale |
|---|---|---|
| Core business data (customers, orders) | 90β100% | Critical for analytics |
| Reference/dimension data | 80β100% | Needed for joins and lookups |
| Event/log data | 60β80% | Many fields may be metadata/noise |
| Staging/temporary sources | 40β60% | May be partially relevant |
Profiling Strategy
- Enable by default for all new sourcefiles
- Disable selectively for stable, high-volume sources after validation
- Never disable for sources with PII β compliance requires ongoing validation
- Exclude specific fields rather than disabling profiling entirely
Governance Priorities
- Tag PII first β
sensitive: 1on all personally identifiable fields - Classify fields β assign
fieldDomainfor compliance tracking - Define business keys β every model needs at least one
- Document relationships β add descriptions to explain why models are linked
- Review exclusions β make sure
excludeFieldisn't hiding needed data