Provisions a single saved Databricks SQL query (
databricks_query) against thedatabricks/databricksprovider~> 1.117.0.
- 📝 Creates one
databricks_query— a saved SQL/Lakeview query bound to a specific SQL warehouse. - 🧮 Supports widget-style query parameters (
{{ key }}markers) via adynamic "parameter"block — text, numeric, date, date-range, enum (dropdown), and query-backed dropdown widget types. - 🚫 Never accepts a credential, host, or account ID; never creates the warehouse it runs against.
- 🌍 Workspace-plane only — this resource cannot be used with an account-level provider.
💡 Why it matters: a saved query is the reusable unit behind dashboards, alerts, and ad hoc analyst work. Its
warehouse_id,display_name, andquery_textare all schema-required, andparent_pathis force-new — moving a query's workspace folder destroys and recreates it.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
WAREHOUSE["terraform-databricks-sql-warehouse"]
style WAREHOUSE fill:#1B3139,color:#fff,stroke:#1B3139,stroke-width:1px
CATALOG["terraform-databricks-catalog"]
style CATALOG fill:#F2F2F2,color:#1B3139,stroke:#CCCCCC,stroke-width:1px
SCHEMA["terraform-databricks-schema"]
style SCHEMA fill:#F2F2F2,color:#1B3139,stroke:#CCCCCC,stroke-width:1px
THIS["terraform-databricks-sql-query"]
style THIS fill:#FF3621,color:#fff,stroke:#1B3139,stroke-width:1px
ALERT["terraform-databricks-sql-alert"]
style ALERT fill:#1B3139,color:#fff,stroke:#1B3139,stroke-width:1px
WAREHOUSE -->|"id becomes warehouse_id"| THIS
CATALOG -->|"name becomes catalog (optional)"| THIS
SCHEMA -->|"name becomes schema (optional)"| THIS
THIS -->|"id becomes query_id"| ALERT
ℹ️
terraform-databricks-sql-warehouseandterraform-databricks-sql-alertare seeded modules in this same authoring batch and do not yet have a committed README/.tfat the time this README was written — the diagram and the end-to-end composition example (§ Example Library) reflect their planned contracts from this library's catalog table, not a verified cross-moduleterraform plan.terraform-databricks-sql-warehousewrapsdatabricks_sql_endpoint, not a resource literally nameddatabricks_sql_warehouse— the module name and the underlying Terraform resource name genuinely differ; this module's ownwarehouse_idvariable references that module'sidoutput by module name throughout this README.
flowchart TB
subgraph INPUTS["var.*"]
CORE["display_name / query_text / warehouse_id"]
CTX["catalog / schema / description / parent_path / owner_user_name"]
POLICY["apply_auto_limit / run_as_mode / tags"]
PARAMS["parameters (map, keyed by parameter name)"]
end
KEYSTONE["databricks_query.this"]
style KEYSTONE fill:#1B3139,color:#fff,stroke:#1B3139,stroke-width:1px
subgraph INTERNAL["dynamic blocks inside the keystone"]
PBLOCK["dynamic parameter block, one per map key"]
SUBBLOCK["text_value / numeric_value / date_value / date_range_value / enum_value / query_backed_value, one per parameter"]
end
subgraph OUTPUTS["outputs"]
ID["id"]
NAME["display_name"]
end
CORE --> KEYSTONE
CTX --> KEYSTONE
POLICY --> KEYSTONE
PARAMS --> PBLOCK
PBLOCK --> SUBBLOCK
SUBBLOCK --> KEYSTONE
KEYSTONE --> ID
KEYSTONE --> NAME
Resource inventory: one resource, databricks_query.this. No separate child resource type —
parameter (and its six value-type sub-blocks) is a repeated nested block on the keystone itself,
rendered via dynamic "parameter" over var.parameters, a keyed map(object(...)).
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
databricks/databricks |
~> 1.117.0 |
| Provider block | None — the caller's root module configures provider "databricks" {} |
tags / custom_tags |
Supported — databricks_query.tags is a plain list(string) (see below) |
timeouts |
Not confirmed present on databricks_query in the pinned schema — none added |
Schema notes that bite:
databricks_query(notdatabricks_sql_query) is the correct, current resource this module wraps. The provider's own registry documentation statesdatabricks_query"supersedesdatabricks_sql_query" and provides a migration guide.databricks_sql_querystill exists in the pinned~> 1.117.0schema and uses a visibly different, older argument shape (data_source_id/name/queryinstead ofwarehouse_id/display_name/query_text). The compiledterraform providers schema -jsonfor this pinned provider version does not carry a machine-readabledeprecated: trueflag on either resource — that information lives in the registry's rendered prose documentation, not the wire schema. This README therefore callsdatabricks_sql_querythis library's house-designated legacy/superseded predecessor, not a schema-verified deprecation, and a reader who findsdatabricks_sql_queryin older tutorials should treat this distinction as the reason this module doesn't use it.tagsondatabricks_queryis a plainlist(string)— a genuinely different shape from three sibling modules that all handle "tags" differently:terraform-databricks-jobandterraform-databricks-pipelineboth modeltagsasmap(string), whileterraform-databricks-sql-warehouse(wrappingdatabricks_sql_endpoint) models it as a nestedtags { custom_tags { key, value } }block remapped from amap(string)caller variable. A reader moving between these SQL/BI and compute modules should not assume a shared tags contract — each module'stags/custom_tagsvariable is typed to match its own resource's actual schema, consistent with this library's house rule that Databricks has no universal tags contract.display_name,query_text, andwarehouse_idare the three schema-Required attributes — all three are non-optional, non-defaulted fields in this module'svariables.tf.parent_pathis force-new — the provider's own documentation states "If changed, the query will be recreated."run_as_mode's legal values (OWNER,VIEWER) come from the provider's registry documentation, not the compiled machine schema —terraform providers schema -jsonfor this pinned version exposesrun_as_modeas a plain optional string with no enum metadata. This module'svalidation {}block is grounded in the registry doc text (confirmed against the live provider documentation), which is a verifiable but distinct source from the wire schema.
- Applying identity needs execution access to the target
warehouse_id, and — ifcatalog/schemaare set — the corresponding Unity CatalogUSE_CATALOG/USE_SCHEMAprivileges plusSELECTon any objectquery_textreferences. This is Databricks' general SQL-execution permission model, not an argument this module's own schema encodes directly. - Post-creation sharing (letting other users/groups view, run, edit, or manage this specific query)
is confirmed via the provider's own "Access Control" documentation for
databricks_query: a separatedatabricks_permissionsresource with asql_query_idargument and access levels includingCAN_RUN(minimum),CAN_VIEW,CAN_EDIT,CAN_MANAGE. This module does not create that resource itself — pair it withterraform-databricks-permissions. - No account-level permissions required — this is a workspace-plane resource.
- Workspace-level provider context — confirmed via the provider's own documentation: "This resource can only be used with a workspace-level provider!"
- The referenced
warehouse_idmust already exist. This module does not create the warehouse. - If
catalog/schemaare set, both must already exist in the target metastore.
terraform-databricks-sql-query/
├── providers.tf # required_providers only — no provider {} block
├── variables.tf # display_name, query_text, warehouse_id (required), parameters, tags,...
├── main.tf # databricks_query.this + dynamic "parameter" blocks
├── outputs.tf # id first, then display_name
├── SCOPE.md # cross-module contract
├── README.md # this file
└── examples/
└── basic/
└── main.tf # smallest real, runnable call
module "row_count_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Daily Row Count"
query_text = "SELECT count(*) AS row_count FROM analytics.raw.events"
warehouse_id = var.warehouse_id
}The caller's root module configures provider "databricks" {} (host + auth) and passes it in
implicitly; this module accepts neither.
Consumes:
| Input | Type | Source module |
|---|---|---|
warehouse_id |
string |
terraform-databricks-sql-warehouse output id |
catalog |
optional(string) |
terraform-databricks-catalog output name |
schema |
optional(string) |
terraform-databricks-schema output name |
Emits:
| Output | Description | Consumed by |
|---|---|---|
id |
Unique ID of the created query | terraform-databricks-sql-alert (query_id input), databricks_permissions (sql_query_id input) |
display_name |
Name of the saved query | Auditing / drift-detection tooling |
1 · Minimal parameterless query
module "row_count_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Daily Row Count"
query_text = "SELECT count(*) AS row_count FROM analytics.raw.events"
warehouse_id = var.warehouse_id
}2 · Text-widget parameter
module "region_lookup_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Sales by Region"
query_text = "SELECT * FROM analytics.raw.sales WHERE region = {{ region }}"
warehouse_id = var.warehouse_id
parameters = {
region = {
title = "Region"
text_value = { value = "us-east-1" }
}
}
}3 · Numeric-widget parameter
module "top_n_customers_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Top N Customers by Spend"
query_text = "SELECT * FROM analytics.gold.customer_spend ORDER BY spend DESC LIMIT {{ limit }}"
warehouse_id = var.warehouse_id
parameters = {
limit = {
title = "Row limit"
numeric_value = { value = 25 }
}
}
}4 · Date-widget parameter (dynamic value)
module "yesterday_events_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Events on a Given Day"
query_text = "SELECT * FROM analytics.raw.events WHERE event_date = {{ as_of_date }}"
warehouse_id = var.warehouse_id
parameters = {
as_of_date = {
title = "As-of date"
date_value = {
dynamic_date_value = "YESTERDAY"
precision = "DAY_PRECISION"
}
}
}
}ℹ️
dynamic_date_valueacceptsNOWorYESTERDAYper the provider's registry documentation — usedate_value.date_valueinstead for a fixed, non-relative date.
5 · Date-range-widget parameter (explicit range)
module "quarterly_report_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Quarterly Revenue Report"
query_text = "SELECT * FROM analytics.gold.revenue WHERE report_date BETWEEN {{ report_range.start }} AND {{ report_range.end }}"
warehouse_id = var.warehouse_id
parameters = {
report_range = {
title = "Report period"
date_range_value = {
precision = "DAY_PRECISION"
date_range_value = {
start = "2026-01-01"
end = "2026-03-31"
}
}
}
}
}6 · Enum (dropdown) parameter
module "status_filter_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Orders by Status"
query_text = "SELECT * FROM analytics.raw.orders WHERE status = {{ status }}"
warehouse_id = var.warehouse_id
parameters = {
status = {
title = "Order status"
enum_value = {
enum_options = "pending\nshipped\ndelivered\ncancelled"
values = ["pending"]
}
}
}
}ℹ️
enum_optionsis newline-delimited per the provider's registry documentation, not alist(string)— this module passes the string through unmodified.
7 · Query-backed dropdown parameter
module "customer_drilldown_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Customer Order Drilldown"
query_text = "SELECT * FROM analytics.gold.orders WHERE customer_id = {{ customer }}"
warehouse_id = var.warehouse_id
parameters = {
customer = {
title = "Customer"
query_backed_value = {
query_id = var.customer_lookup_query_id
multi_values_options = {
separator = ","
}
}
}
}
}ℹ️
query_backed_value.query_idreferences another saved query'sid— typically an earlierterraform-databricks-sql-querymodule instance'sidoutput.
8 · Unity Catalog–scoped query
module "curated_sales_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Curated Sales Summary"
query_text = "SELECT * FROM sales_summary"
warehouse_id = var.warehouse_id
catalog = "analytics"
schema = "curated"
}9 · Unbounded result set (auto-limit disabled)
module "full_export_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Full Customer Export"
query_text = "SELECT * FROM analytics.gold.customers"
warehouse_id = var.warehouse_id
apply_auto_limit = false
}
⚠️ Secure default istrue(1,000-row cap). Only disable it for a query whose caller genuinely needs the full, unbounded result set — an unbounded query against a large table can drive up warehouse compute cost.
10 · run_as_mode variants
module "dashboard_backing_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Dashboard Backing Query"
query_text = "SELECT * FROM analytics.gold.kpis"
warehouse_id = var.warehouse_id
run_as_mode = "OWNER"
}
module "analyst_ad_hoc_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Analyst Ad Hoc Query"
query_text = "SELECT * FROM analytics.raw.events"
warehouse_id = var.warehouse_id
run_as_mode = "VIEWER" # secure default; explicit here for clarity
}💡
run_as_modeis a closed two-value enum (OWNER,VIEWER) enforced by this module's ownvalidation {}block, grounded in the provider's registry documentation rather than the compiled schema (see "Schema notes that bite").
11 · Tags for UI filtering
module "tagged_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Monthly Cohort Retention"
query_text = "SELECT * FROM analytics.gold.cohort_retention"
warehouse_id = var.warehouse_id
tags = ["finance", "monthly", "retention"]
}ℹ️
tagsis a plainlist(string)on this resource — not amap(string). See "Schema notes that bite" for the three-way inconsistency across sibling modules.
12 · Explicit workspace folder and owner
module "shared_finance_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Shared Finance Query"
query_text = "SELECT * FROM analytics.gold.revenue"
warehouse_id = var.warehouse_id
parent_path = "/Shared/Finance/Queries"
owner_user_name = "finance-reporting-svc@financialpartners.com"
}
⚠️ parent_pathis force-new — moving this query to a different workspace folder later destroys and recreates it.
13 · for_each-driven multi-query creation at scale
locals {
regional_queries = {
"us-east" = "us-east-1"
"us-west" = "us-west-2"
"eu" = "eu-west-1"
}
}
module "regional_sales_queries" {
for_each = local.regional_queries
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Sales Summary — ${each.key}"
query_text = "SELECT * FROM analytics.gold.sales WHERE region = '${each.value}'"
warehouse_id = var.warehouse_id
}ℹ️
for_eachis applied at the caller's root-module level — this module itself has no child collection to iterate over; each instance creates exactly one query.
14 · Minimal least-privilege baseline (recommended starting point)
module "baseline_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Baseline Query"
query_text = "SELECT 1"
warehouse_id = var.warehouse_id
# apply_auto_limit left at its secure default: true
# run_as_mode left at its secure default: "VIEWER"
}🏗️ 15 · End-to-end composition — warehouse → query → alert
module "reporting_warehouse" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-warehouse.git?ref=v1.0.0"
name = "reporting-warehouse"
cluster_size = "Small"
}
module "failed_jobs_query" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-query.git?ref=v1.0.0"
display_name = "Failed Jobs Last 24h"
query_text = "SELECT count(*) AS failure_count FROM analytics.gold.job_runs WHERE status = 'FAILED' AND run_date >= current_date - 1"
warehouse_id = module.reporting_warehouse.id
}
module "failed_jobs_alert" {
source = "git::https://github.com/microsoftexpert/terraform-databricks-sql-alert.git?ref=v1.0.0"
display_name = "Failed Jobs Threshold Alert"
query_id = module.failed_jobs_query.id
condition = {
op = "GREATER_THAN"
operand = {
column = { name = "failure_count" }
}
threshold = {
value = { double_value = 5 }
}
}
}ℹ️
terraform-databricks-sql-warehouseandterraform-databricks-sql-alertare not yet committed modules at the time this README was written — this composition wires their planned contracts per this library's catalog table (terraform-databricks-sql-warehouse'sidoutput into this module'swarehouse_id; this module'sidoutput intoterraform-databricks-sql-alert'squery_id), not a verified cross-moduleterraform plan.
| Variable | Type | Default | Notes |
|---|---|---|---|
display_name |
string |
— (required) | |
query_text |
string |
— (required) | Not parsed as SQL by terraform validate |
warehouse_id |
string |
— (required) | From terraform-databricks-sql-warehouse output id |
parent_path |
string |
null |
Force-new |
owner_user_name |
string |
null |
|
apply_auto_limit |
bool |
true |
Secure default |
catalog |
string |
null |
|
schema |
string |
null |
|
description |
string |
null |
|
run_as_mode |
string |
"VIEWER" |
Secure default; OWNER | VIEWER |
tags |
list(string) |
[] |
Not map(string) — see Schema notes that bite |
parameters |
map(object(...)) |
{} |
Keyed by parameter name; one *_value sub-object per entry |
Full variable declarations
variable "display_name" {
type = string
}
variable "query_text" {
type = string
}
variable "warehouse_id" {
type = string
}
variable "parent_path" {
type = string
default = null
}
variable "owner_user_name" {
type = string
default = null
}
variable "apply_auto_limit" {
type = bool
default = true
}
variable "catalog" {
type = string
default = null
}
variable "schema" {
type = string
default = null
}
variable "description" {
type = string
default = null
}
variable "run_as_mode" {
type = string
default = "VIEWER"
# validation: must be "OWNER" or "VIEWER"
}
variable "tags" {
type = list(string)
default = []
}
variable "parameters" {
type = map(object({
title = optional(string)
text_value = optional(object({
value = string
}))
numeric_value = optional(object({
value = number
}))
date_value = optional(object({
date_value = optional(string)
dynamic_date_value = optional(string)
precision = optional(string)
}))
date_range_value = optional(object({
dynamic_date_range_value = optional(string)
precision = optional(string)
start_day_of_week = optional(number)
date_range_value = optional(object({
start = string
end = string
}))
}))
enum_value = optional(object({
enum_options = optional(string)
values = optional(list(string))
multi_values_options = optional(object({
prefix = optional(string)
separator = optional(string)
suffix = optional(string)
}))
}))
query_backed_value = optional(object({
query_id = string
values = optional(list(string))
multi_values_options = optional(object({
prefix = optional(string)
separator = optional(string)
suffix = optional(string)
}))
}))
}))
default = {}
}| Output | Description | Sensitive? |
|---|---|---|
id |
Unique ID of the created query | No |
display_name |
Name of the saved query | No |
- Three-way
tagsshape inconsistency across sibling SQL/BI and compute modules. This module'stagsis a plainlist(string).terraform-databricks-jobandterraform-databricks-pipelineboth modeltagsasmap(string).terraform-databricks-sql-warehouse(wrappingdatabricks_sql_endpoint) models it as a nestedtags { custom_tags { key, value } }block. None of these are wrong — each matches its own resource's actual provider schema — but a reader moving between modules should re-check the type rather than assume consistency. databricks_queryvs. the legacydatabricks_sql_query. This module exclusively usesdatabricks_query, the provider's current resource for saved SQL queries, which its own documentation says "supersedes"databricks_sql_query. The legacy resource has a visibly different required-argument shape (data_source_id/name/query). The pinned provider's compiled schema does not carry a machine-readable deprecation flag for either resource — treat the "legacy/superseded" framing as house guidance grounded in the provider's own migration-guide prose, not aschema.json-verified fact.parent_pathis force-new. Moving a query to a different workspace folder destroys and recreates it — this is a real operational consideration for a saved query with existing viewers or downstream alerts.- No
for_each, no separate child resources.parameter(and its six value-type sub-blocks) is a repeated nested block on the keystone itself, rendered via a singledynamic "parameter"block keyed byvar.parameters' map keys — there is no independentdatabricks_query_parameterresource in the provider schema. - Exactly one
*_valuesub-object per parameter is a documented convention, not a schema-enforced constraint. This module does not add avalidation {}block forcing mutual exclusion amongtext_value/numeric_value/date_value/date_range_value/enum_value/query_backed_value— the same judgment call asterraform-databricks-alert'scondition.threshold.valueoneof andterraform-databricks-cluster'sautoscale/num_workersprecedence.
| Concern | Secure default | Opt-out (caller must set explicitly) |
|---|---|---|
| Result-set size | apply_auto_limit = true (1,000-row cap) |
Set apply_auto_limit = false explicitly for a query that genuinely needs its full, unbounded result set |
| Run-as identity | run_as_mode = "VIEWER" (runs with the viewer's own permissions, not the owner's) |
Set run_as_mode = "OWNER" explicitly when the query must run with a consistent, elevated identity regardless of viewer |
Both defaults override an undocumented or permissive provider-level ambiguity toward the safer choice, consistent with this library's "the empty call must produce the safe resource" rule.
CANDIDATE Secure-by-default table row (not yet added to the master table — flagging for a future session):
databricks_query.apply_auto_limitdefaulting totrueandrun_as_modedefaulting to"VIEWER"are new secure defaults introduced by this module; this library's master table does not yet have rows for either concern.
cd terraform-databricks-sql-query
terraform init -backend=false
terraform validate
terraform fmt -checkPin consumers to an immutable tag — ?ref=v1.0.0 — never a branch. This module is plan-only; a
human applies from CI after review.
terraform validate / terraform fmt -check catch: missing display_name/query_text/
warehouse_id, the run_as_mode enum validation, malformed parameter object shapes, and
malformed HCL. They do not catch: whether warehouse_id actually exists, whether query_text
is valid SQL against the target catalog/schema, whether the applying identity actually has execution
access to the warehouse, or any real Databricks SQL API-side constraint. Those require an actual
plan/apply against a live workspace, out of scope for this authoring process.
$ terraform output
display_name = "Daily Row Count"
id = "01ef8a3b-1c2d-4e5f-9a0b-123456789abc"
| Symptom | Cause | Fix |
|---|---|---|
Apply fails with a permissions error even though terraform validate passed |
Applying identity lacks execution access to warehouse_id, or lacks USE_CATALOG/USE_SCHEMA/SELECT on referenced objects |
Confirm the identity holds the required warehouse and Unity Catalog privileges |
| Apply attempts to replace the query unexpectedly | parent_path was changed |
parent_path is force-new; treat any change as a deliberate migration, not a routine edit |
A copy-pasted example from an older tutorial references data_source_id/name/query and fails to plan against this module |
The tutorial used the legacy databricks_sql_query resource, not databricks_query |
This module only wraps databricks_query; translate data_source_id → warehouse_id, name → display_name, query → query_text per the provider's own migration guide |
A parameter renders with no visible effect in the UI widget |
More than one *_value sub-object was set on the same parameter, or the sub-object's shape doesn't match the intended widget type |
Set exactly one *_value sub-object per parameter entry |
| Other users can't view or run a query this module created | This module does not manage post-creation access control | Pair with terraform-databricks-permissions (sql_query_id = this module's id) |
databricks_queryprovider resourceterraform-databricks-sql-warehouse(upstream, provideswarehouse_id)terraform-databricks-sql-alert(downstream, consumes this module'sid)- This module's
SCOPE.md
💙 "Infrastructure as Code should be standardized, consistent, and secure."