Skip to content

Portable Transformation Compiler Protocol

Available in ETLantic 0.14

etlantic.transform-compiler/1 is importable. Polars, PySpark, and Pandas claim portable-relational-kernel/1 and portable-relational/1. Third parties must pass run_portable_transform_conformance_suite for every advertised claim. Safe SQL and Rich Portable Analytics compiler claims remain 0.15+.

A portable transformation compiler translates a validated dtcs.transform-plan/2 (and readable v1) into backend-native expressions without changing its DTCS-defined meaning.

Boundary

DTCS TransformationPlan
      ├── support analysis (pure)
      ├── compilation (no data access)
CompiledTransform
      └── execution with runtime inputs and parameters

Support analysis and compilation belong to planning. Execution belongs to the runtime. A serialized PipelinePlan contains IR, requirements, compiler identity, and fingerprints—not live compiled objects or closures.

Protocol

@runtime_checkable
class PortableTransformCompiler(Protocol):
    @property
    def info(self) -> TransformCompilerInfo: ...

    def analyze(
        self,
        definition: TransformationPlan,
        *,
        context: TransformPlanningContext,
    ) -> TransformSupportReport: ...

    def compile(
        self,
        definition: TransformationPlan,
        *,
        context: TransformCompileContext,
    ) -> CompiledTransform: ...

    async def execute(
        self,
        compiled: CompiledTransform,
        *,
        inputs: Mapping[str, Any],
        parameters: Mapping[str, Any],
        context: TransformExecutionContext,
    ) -> TransformOutputBundle: ...

Plugins MAY separate compilation and execution into cooperating objects. SQL and orchestration plugins MAY compile artifacts for execution outside the local process.

Compiler information

TransformCompilerInfo(
    name="etlantic-polars",
    version="...",
    engine="polars",
    compiler_protocol="etlantic.transform-compiler/1",
    dtcs_plan_versions=("dtcs.transform-plan/2", "dtcs.transform-plan/1"),
    capabilities=TransformCapabilities(...),
)

Capabilities are an ETLantic adapter over the DTCS Engine Capability Model. They list exact registered Semantic Action/Function versions, portable types, semantic modes, maximum supported plan size, lazy/eager behavior, and artifact ownership. Plans and reports preserve the originating DTCS capability identifiers.

The compiler reports support against the published profiles, never a vague "PySpark compatible" flag:

DTCS profile Compiler claim
dtcs:profile/portable-relational-kernel/1 all required kernel actions, expressions, operators, and functions
dtcs:profile/portable-relational/1 full relational joins, unions, grouping, aggregation, ordering, deduplication, and limits
dtcs:profile/portable-window/1 experimental windows, functions, and row/range frames
dtcs:profile/portable-complex-types/1 experimental composite types and access operations

A compiler may advertise a subset as individual capabilities. It may advertise a profile only when every requirement and DTCS conformance fixture in that profile passes.

Polars / PySpark / Pandas claim matrix (0.13–0.14)

etlantic-polars, etlantic-pyspark, and etlantic-pandas must claim and pass the public conformance fixtures for:

Item 0.13–0.14 requirement
Profiles dtcs:profile/portable-relational-kernel/1 and dtcs:profile/portable-relational/1
Plan shape Accept dtcs.transform-plan/2 (and readable v1); /2 profile requirements are metadata aliases of /1
Kernel actions project, filter, with_fields, rename/drop, scalar expressions
Relational actions join, union, aggregate, sort, distinct, deduplicate, limit with exact modes
Modes Eager required; Pandas additionally lazy=False; join collisionPolicy fail only
Outside claim set Fail closed in analyze() / planning (PMXFORM3xx) with action/expression paths

They must not claim Rich Portable Analytics, windows, complex-values, reshape, relational-extended, or conversion profiles. Those belong to 0.15+.

Support reports

analyze() is deterministic and side-effect free. It returns one finding per unsupported or conditional requirement:

TransformSupportReport(
    supported=False,
    findings=(
        TransformSupportFinding(
            code="PMXFORM301",
            expression_path="outputs.result.project.full_name",
            requirement="function:dtcs:concat_ws",
            reason="function is not implemented",
        ),
    ),
)

It MUST NOT import arbitrary user modules, resolve secrets, acquire resources, read data, or contact a backend. Optional backend capability probing is a separate explicitly requested operation and cannot weaken fail-closed planning.

Analysis MUST cover each required profile, Semantic Action, Function, Operator, logical type, action mode, value-state behavior, ordering guarantee, and resource limit. A backend that supports join but not, for example, nullSafe, semi, or collision policy handling reports the exact unsupported mode rather than claiming generic join support.

Compiled transform

A compiled transform contains:

  • compiler and engine identity
  • source IR fingerprint
  • logical output mapping
  • required runtime parameter names
  • materialization and ownership requirements
  • backend-native plan held only in runtime memory or a safe generated artifact
  • explain metadata without secrets or row data

Compilation MUST be deterministic for equivalent IR, compiler version, and compile context. Backend-native objects MUST NOT be serialized into the portable PipelinePlan.

Execution output

Execution normalizes results into:

TransformOutputBundle(
    valid={"result": value},
    invalid={},
    side={},
    diagnostics=[],
    metrics=TransformMetrics(...),
)

The existing dataframe output bundle may be generalized or adapted rather than duplicated. Output port roles and logical identities must survive compilation.

Compiler responsibilities

A compiler MUST:

  • preserve normative portable semantics
  • reject unsupported operations before execution
  • validate identifiers and bind values safely
  • retain logical expression and output mappings
  • preserve validation and security-domain boundaries
  • expose collection, copy, and materialization requirements
  • emit structured, redacted diagnostics
  • enforce advertised limits

A compiler MUST NOT:

  • silently approximate an operation
  • inject UDFs or raw SQL as a fallback
  • collect lazy values during planning or compilation
  • embed secret values in compiled or explained artifacts
  • optimize across security, validation, retry, or materialization boundaries
  • report capabilities it does not pass in conformance tests

Backend expectations

Polars

Compile to pl.Expr and LazyFrame operations. Preserve laziness until the plan declares collection. Avoid row conversion between compatible Polars steps.

Pandas

Compile to dataframe and series operations. Declare eager execution, copying, index treatment, and unsupported operations precisely. Portable semantics must not depend on a meaningful Pandas index.

SQL

Lower into the safe ETLantic SQL IR before dialect compilation. Use bound parameters, validate identifiers, retain relation lineage, and prohibit trusted SQL fragments in portable definitions.

PySpark

Compile to native DataFrame and Column expressions. Preserve Catalyst-visible operations. Python and Pandas UDF fallback is forbidden unless a future, explicitly non-portable capability is selected.

Discovery

The proposed entry-point group is:

[project.entry-points."etlantic.transform_compilers"]
polars = "etlantic_polars:create_transform_compiler"

An existing engine plugin MAY expose its compiler through its primary plugin object. Discovery must still produce an explicit compiler descriptor so plans can record protocol and capability compatibility.

Production profiles require plugin allowlisting and version policy. Compiler discovery is a trust decision because Python entry points execute with host process privileges.

Conformance

0.14 ships the public suite:

from etlantic.testing import run_portable_transform_conformance_suite

run_portable_transform_conformance_suite(create_transform_compiler())

Also available as etlantic.testing.portable_transform_conformance.

Required fixture families (public suite):

  • capability accuracy
  • deterministic compilation
  • scalar and relational semantics
  • nulls, NaN, overflow, decimal, and casts
  • timestamps and ordering
  • joins and aggregations
  • multiple output roles
  • lazy/eager and ownership behavior
  • unsupported-operation diagnostics
  • bounded hostile IR
  • secret and parameter redaction
  • cross-engine result equivalence

Fixtures are selected from advertised capabilities. Claiming a profile or operation without its mandatory fixture family fails the suite. Window and complex-types fixtures remain experimental and require two independent conforming compilers before their DTCS profiles can graduate from experimental status.

Advertised capability coverage, not plugin name, determines which fixtures are mandatory.

Versioning

Compiler packages declare compatible core, plan-schema, and transform-protocol versions. Unknown IR major versions fail closed. Capability additions may be minor releases; changing an existing operation's meaning requires a new IR major version.