How to Use Jinja Templates in Semantic Models for Row-Level Security in Dat

You implement row-level security in Dat by embedding Jinja 2 expressions inside the model field of your semantic model YAML, which gets rendered during the pre-build phase with security context variables to inject static SQL filters.

Dat is an open-source data agent framework that uses semantic models to define virtual tables for natural language-to-SQL translation. By using Jinja templates in semantic models for row-level security, you can dynamically filter data at the model level without writing custom application logic, ensuring that every query respects tenant or user boundaries.

Understanding Semantic Models and the model Field

In Dat, a semantic model is declared in a project-level YAML file. The critical field is model, which holds the SQL that defines the virtual table for the model.

The model string can contain Jinja 2 expressions using the {{ … }} and {% … %} syntax. During the pre-build phase, Dat parses the project, loads the YAML, and renders the template with a map of variables that includes any runtime security context (e.g., the current user, tenant, or request-scoped parameters).

semantic_models:
  - name: sales
    description: Sales facts
    model: |
      SELECT *
      FROM raw_sales
      {% if row_level_security %}
      WHERE region_id = {{ user.region_id }}
      {% endif %}

The Jinja Rendering Pipeline

The rendering process occurs in distinct steps to ensure security filters are baked into the model definition before any queries execute.

Step 1: Project-Level Variable Substitution

First, DatProjectUtil.validate() calls JinjaTemplateUtil.render() to process the raw project YAML. This step substitutes project-level variables such as ${project_name}.

// dat-sdk/src/main/java/ai/dat/core/utils/DatProjectUtil.java
DatProjectUtil.validate() → JinjaTemplateUtil.render()

Step 2: Semantic Model Rendering

Next, PreBuildValidator.validate() iterates through each SemanticModel and renders the model field again, this time with the security context variables map.

// dat-sdk/src/main/java/ai/dat/boot/PreBuildValidator.java
for (SemanticModel semanticModel : semanticModels) {
    // `variables` contains the security context (e.g., tenant_id)
    semanticModel.setModel(
        JinjaTemplateUtil.render(semanticModel.getModel(), variables));
}

Step 3: SQL Generation and Validation

Finally, SemanticModelUtil.semanticModelSql() builds the final SQL that will be sent to the database. The built SQL is validated using SELECT 1 FROM (<rendered_sql>) to ensure the rendered security filter does not break the query syntax.

// Validation step
SELECT 1 FROM (<rendered_sql>)

If validation fails, a ValidationException is thrown, preventing a broken security rule from being deployed.

Implementing Row-Level Security with Jinja Variables

Because rendering happens once per build, the security filter is baked into the model definition and thereafter enforced by the database engine for all downstream queries (e.g., ASKDATA agents, OpenAPI calls).

1. Pass Security Variables

When you start a project (dat run …), supply a JSON file or command-line overrides that define a variables map.

dat run --variables '{"user": {"id": 42, "region_id": 7}}'

2. Jinja Resolves Placeholders

In the example above, {{ user.region_id }} becomes 7 during the pre-build phase.

3. SQL Contains a Static Filter

The built model SQL is essentially:

SELECT * FROM raw_sales WHERE region_id = 7

This filter is automatically applied whenever the model is queried, providing row-level security without additional application logic.

Key Source Files and Implementation Details

The Jinja rendering and security implementation relies on the following core files in the junjiem/dat repository:

Summary

  • Jinja templates in the model field of Dat semantic models enable dynamic SQL generation based on runtime security context.
  • The JinjaTemplateUtil.render() method processes templates during the pre-build phase, substituting variables like {{ user.region_id }} with actual values.
  • Row-level security is enforced by baking static filters into the model SQL at build time, ensuring all downstream queries (including ASKDATA agents) respect tenant or user boundaries.
  • The rendering pipeline validates SQL syntax before deployment, preventing broken security rules from reaching production.

Frequently Asked Questions

What is the syntax for Jinja templates in Dat semantic models?

Dat uses standard Jinja 2 syntax. Use {{ variable_name }} for variable interpolation and {% if condition %} for control flow. For example: WHERE tenant_id = {{ tenant_id }} or {% if row_level_security %}WHERE user_id = {{ user.id }}{% endif %}.

How do I pass security variables when running a Dat project?

Pass a JSON map using the --variables flag when executing dat run. For example: dat run --variables '{"user": {"id": 42, "region_id": 7}, "tenant_id": 12}'. These variables become available in the Jinja context during the pre-build rendering phase.

When does the Jinja template rendering occur in the Dat build process?

Rendering occurs during the pre-build phase, specifically in PreBuildValidator.validate(). This happens after project loading but before SQL compilation, ensuring that security filters are resolved and validated (via SELECT 1 checks) before the model is stored in the build state.

Can I use complex logic like loops in Jinja templates for row-level security?

Yes, the Jinjava engine (wrapped by JinjaTemplateUtil) supports full Jinja 2 syntax including for loops, if statements, and macros. However, for row-level security, simple variable interpolation and conditional blocks are most common, as they generate static SQL filters that databases can optimize effectively.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →