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:
-
dat-core/src/main/java/ai/dat/core/semantic/data/SemanticModel.java– Defines theSemanticModelPOJO, including themodelproperty that holds the SQL template. -
dat-core/src/main/java/ai/dat/core/utils/JinjaTemplateUtil.java– Wrapper around HubSpot’s Jinjava engine that performs the actual template rendering viarender(String template, Map<String, Object> variables). -
dat-sdk/src/main/java/ai/dat/boot/PreBuildValidator.java– Validates and renders semantic models before the build, callingJinjaTemplateUtil.render()for each model’s SQL template. -
dat-sdk/src/main/java/ai/dat/core/utils/DatProjectUtil.java– Loads the project YAML and applies global Jinja rendering for project-level variables. -
dat-agents/dat-agent-agentic/src/main/java/ai/dat/agent/agentic/Text2SqlContentInjector.java– Demonstrates runtime injection of Jinja-rendered models into natural language-to-SQL pipelines.
Summary
- Jinja templates in the
modelfield 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
ASKDATAagents) 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →