A typed Data Factory dataset describing a table in a Snowflake schema, targeting
hashicorp/azurerm ~> 4.0.
- ❄️ Creates one
azurerm_data_factory_dataset_snowflake— a named description of a table in a Snowflake schema that pipelines read from and write to. - 🧬 The Data Factory dataset family's biggest outlier. Its
schema_columnblock is{name, type, precision, scale}— there is nodescriptionfield — where ten of the twelve siblings use{name, type, description}. - 🔤 Its column types are 32 Snowflake SQL types that share not one value with the fifteen Data
Factory types the siblings use.
Stringis legal there and refused here;VARCHARis legal here and refused there. - 🔢 Carries
precisionandscale, which no other dataset resource has — validated only as non-negative integers, and never checked against each other or the column's type. - 🗂️ Has a separate
schema_name, which the SQL Server Table, MySQL and PostgreSQL datasets lack entirely.
💡 Why it matters: nothing about this resource's column schema can be copied from a sibling in either direction. The harness proves it exhaustively — all 32 Snowflake types and all 15 Data Factory types driven against both this module and a sibling, each accepted by exactly one side.
If this module saved you time:
- ⭐ Star the repository — it is the cheapest signal that this work is worth continuing.
- 💼 Connect on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
flowchart TB
RG["terraform-azurerm-resource-group"]
ADF["terraform-azurerm-data-factory"]
LS["a Snowflake linked service, which chooses the account, warehouse and database"]
THIS["terraform-azurerm-data-factory-dataset-snowflake"]
TRIO["the schema-identical trio: sql-server-table, mysql, postgresql"]
BIN["terraform-azurerm-data-factory-dataset-binary"]
PIPE["a Data Factory pipeline"]
TBL["a table in a Snowflake schema"]
RG -->|"name"| ADF
ADF -->|"id"| THIS
ADF -->|"id"| TRIO
ADF -->|"id"| BIN
LS -->|"linked_service_name, a bare NAME"| THIS
THIS -->|"schema_column of name, type, precision, scale"| PIPE
TRIO -->|"schema_column of name, type, description"| PIPE
BIN -->|"no schema_column block at all"| PIPE
THIS -->|"32 SNOWFLAKE SQL types, sharing NOT ONE value"| TRIO
PIPE -->|"reads only at run time, never here"| TBL
classDef this fill:#0078D4,stroke:#004578,color:#ffffff,stroke-width:2px
classDef keystone fill:#004578,stroke:#00243d,color:#ffffff,stroke-width:2px
classDef sibling fill:#eef3f8,stroke:#b9c8d8,color:#1b2733
class THIS this
class ADF keystone
class RG,LS,TRIO,BIN,PIPE,TBL sibling
Three edges, three different column contracts: this resource's {name, type, precision, scale}, the trio's
{name, type, description}, and the Binary dataset's absence of the block altogether.
flowchart TB
subgraph INPUTS["Inputs"]
NAME["name (force-new)"]
ADFID["data_factory_id (force-new)"]
LSNAME["linked_service_name, a bare NAME"]
SCH["schema_name, a SEPARATE argument most siblings lack"]
TBL["table_name, unqualified"]
COLS["schema_column of name, type, precision, scale, with NO description"]
META["parameters, additional_properties, annotations, description, folder"]
end
THIS["azurerm_data_factory_dataset_snowflake.this"]
subgraph OUTPUTS["Outputs"]
OID["id and name"]
OQT["qualified_table_name, a DISPLAY value only"]
ODOT["table_name_looks_qualified, the shape a sibling needs and this one does not"]
OCASE["schema_name_is_lower_case and table_name_is_lower_case"]
ONUM["numeric_column_definitions_look_wrong, plus its three components"]
OFACTS["the outlier facts, including the disjoint type vocabulary"]
end
NAME --> THIS
ADFID --> THIS
LSNAME --> THIS
SCH --> THIS
TBL --> THIS
COLS --> THIS
META --> THIS
THIS --> OID
SCH --> OQT
TBL --> OQT
TBL --> ODOT
SCH --> OCASE
TBL --> OCASE
COLS --> ONUM
THIS --> OFACTS
classDef this fill:#0078D4,stroke:#004578,color:#ffffff,stroke-width:2px
classDef sibling fill:#eef3f8,stroke:#b9c8d8,color:#1b2733
class THIS this
class NAME,ADFID,LSNAME,SCH,TBL,COLS,META,OID,OQT,ODOT,OCASE,ONUM,OFACTS sibling
| Resource | Count | Notes |
|---|---|---|
azurerm_data_factory_dataset_snowflake.this |
1 | The keystone. A real ARM child of the factory. |
schema_column |
dynamic, 0..n |
{name, type, precision, scale} — no description. |
timeouts |
dynamic, 0..1 |
All four keys exist. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None. The caller configures the provider, including the mandatory features {} block. |
Schema notes that bite — verified against the live provider source, not inferred from the schema:
- 🔴 THE COLUMN TYPE SETS ARE DISJOINT. This resource accepts 32 Snowflake SQL types; ten siblings accept 15 Data Factory types; they share not one value. Both are compared case-sensitively.
- 🔴 THE
schema_columnBLOCK HAS NOdescriptionFIELD. A column copied from any sibling is a type error at parse time, not a validation failure — the attribute does not exist on the object. - 🔴 FOUR TYPE VALUES ARE NOT PLAIN WORDS.
DOUBLE PRECISIONcontains a space;TIMESTAMP_LTZ,TIMESTAMP_NTZandTIMESTAMP_TZcontain underscores. A list rebuilt by matching letters and digits alone silently loses all four and looks complete. - 🔴
precisionandscaleare validated only as integers ≥ 0. They are never compared with each other or with the type, so ascalelarger than itsprecisionplans and applies cleanly and is rejected by Snowflake at run time. ⚠️ schema_nameis a separate argument here. The SQL Server Table, MySQL and PostgreSQL datasets have none — a schema there goes insidetable_name. A dottedtable_namecopied from one of those reaches Snowflake as a single identifier.⚠️ Snowflake folds unquoted identifiers to upper case.publicbecomesPUBLICunless the object was created with quotes. This module reports the case rather than normalising it.⚠️ Onlynameanddata_factory_idforce replacement.⚠️ Timeouts default to 30m / 5m / 30m / 30m.- ℹ️ The requires-import error names this resource correctly — the SQL Server Table dataset's names a type that does not exist, and the JSON dataset's names the delimited-text type.
- ℹ️ The registry page runs
FLOATandFLOAT4together without a separator in its type list. Both are real, distinct values in the provider source.
| Permission | Scope | Why |
|---|---|---|
Microsoft.DataFactory/factories/datasets/write |
the Data Factory | Create and update. |
Microsoft.DataFactory/factories/datasets/read |
the Data Factory | Refresh and plan. |
Microsoft.DataFactory/factories/datasets/delete |
the Data Factory | Destroy — see the warning below. |
Data Factory Contributor |
the Data Factory | The built-in role containing all three. |
🔒 No permission on Snowflake is required or used, and none is grantable through Azure RBAC — Snowflake has its own role model entirely outside Azure. Access belongs to the linked service, so whoever can write datasets can describe any table in whatever database and warehouse that linked service points at.
💰 Creating a dataset consumes no Snowflake credits. It starts no warehouse and runs no query. Credits are consumed when a pipeline uses the dataset, which is outside this module.
- An existing Data Factory, and its Resource ID.
- A Snowflake linked service already configured in that factory, and its name. It chooses the account, warehouse and database. Nothing here verifies it.
- A schema and table for the dataset to describe — or
parameters. Neither is enforced. - A dataset name meeting Microsoft's Data Factory naming rules. The provider checks only non-emptiness.
- The
Microsoft.DataFactoryresource provider registered in the subscription.
terraform-azurerm-data-factory-dataset-snowflake/
├── providers.tf # required_version + the pinned azurerm; no provider block
├── variables.tf # 12 typed inputs, 14 validations
├── main.tf # the keystone, the numeric analysis, two dynamic blocks
├── outputs.tf # 64 outputs; id first
├── README.md # this file
├── SCOPE.md # the cross-module contract
├── LICENSE # MIT
└── .gitignore
provider "azurerm" {
features {}
}
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_name = "ANALYTICS"
table_name = "ORDERS"
}ℹ️ The schema has its own argument here. Do not write
ANALYTICS.ORDERSintotable_name— that is the shape three siblings need and this resource does not.
Consumes
| Input | Type | Typical source |
|---|---|---|
data_factory_id |
string |
terraform-azurerm-data-factory → id |
linked_service_name |
string |
a Snowflake linked service's name |
schema_name, table_name |
string, optional |
the caller |
Emits (selected — 64 in total)
| Output | Description |
|---|---|
id |
The dataset's Resource ID. |
qualified_table_name |
schema.table — a display value only. |
table_name_looks_qualified |
A dot in table_name — a sibling's shape, not this one's. |
numeric_column_definitions_look_wrong |
The one output to assert on. |
columns_with_scale_exceeding_precision |
Accepted at plan, rejected by Snowflake. |
the_schema_column_type_set_is_SNOWFLAKE_SQL_not_data_factory |
Constant. Two disjoint vocabularies. |
the_schema_column_block_here_has_NO_description_field |
Constant. A copied column is a type error. |
1 · The minimum useful call
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_name = "ANALYTICS"
table_name = "ORDERS"
}💡 Upper case on purpose — Snowflake folds unquoted identifiers to upper case when objects are created.
2 · A dataset that resolves to nothing
module "resolves_to_nothing" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_empty"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
# no table, no parameters
}
check "dataset_points_somewhere" {
assert {
condition = !module.resolves_to_nothing.describes_no_specific_table
error_message = "ds_empty names no table and exposes no parameters; no pipeline can resolve it."
}
}3 · A column list copied from a sibling will not plan
# ❌ NOT ACCEPTED — twice over
module "copied_from_a_sibling" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_copied"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_column = [
{ name = "ORDER_ID", type = "Int64", description = "Primary key." },
]
}
⚠️ Two independent failures:descriptiondoes not exist on this block (a type error at parse time), andInt64is a Data Factory type this resource does not accept. Useprecision/scaleand a Snowflake type.
4 · The same list, written for Snowflake
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_name = "ANALYTICS"
table_name = "ORDERS"
schema_column = [
{ name = "ORDER_ID", type = "NUMBER", precision = 38, scale = 0 },
{ name = "AMOUNT", type = "NUMBER", precision = 18, scale = 2 },
{ name = "CUSTOMER", type = "VARCHAR" },
{ name = "PLACED_AT", type = "TIMESTAMP_NTZ" },
]
}🔒
TIMESTAMP_NTZcarries underscores, andDOUBLE PRECISIONcarries a space. Copy the type list; a list rebuilt by matching letters and digits silently loses all four such values.
5 · A scale larger than its precision — accepted here, rejected by Snowflake
module "silently_wrong" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_silently_wrong"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_column = [
{ name = "AMOUNT", type = "NUMBER", precision = 2, scale = 9 },
]
}
check "numeric_columns_are_coherent" {
assert {
condition = !module.silently_wrong.numeric_column_definitions_look_wrong
error_message = "A column's scale exceeds its precision; Snowflake will reject it at run time."
}
}
⚠️ The provider validates each withIntAtLeast(0)and never compares them. This plans and applies cleanly.columns_with_scale_exceeding_precisionnames the offender.
6 · A precision on a type that has no use for one
module "harmless_but_odd" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_odd"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_column = [
{ name = "CUSTOMER", type = "VARCHAR", precision = 5 },
]
}
output "reported" {
value = module.harmless_but_odd.columns_with_precision_on_a_non_numeric_type # ["CUSTOMER"]
}ℹ️ Accepted and ignored by Snowflake. Reported rather than refused: which types honour precision is Snowflake's rule, not the provider's, and refusing would reject a configuration the provider accepts.
7 · A scale with no precision beside it
module "default_precision" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_default_precision"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_column = [
{ name = "AMOUNT", type = "NUMBER", scale = 4 },
]
}
⚠️ Legal, and almost never intended — Snowflake applies its default precision, which is rarely what someone specifying a scale had in mind.columns_with_scale_but_no_precisionreports it.
8 · A dotted table name is a sibling's shape, not this one's
module "wrong_shape" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_wrong_shape"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
table_name = "ANALYTICS.ORDERS" # the schema belongs in schema_name
}
check "table_name_is_not_qualified" {
assert {
condition = !module.wrong_shape.table_name_looks_qualified
error_message = "table_name contains a dot; on this resource the schema belongs in schema_name."
}
}
⚠️ Correct on the SQL Server Table, MySQL and PostgreSQL datasets, where the schema has nowhere else to go. Here it reaches Snowflake as a single identifier. Reported, never refused — a quoted Snowflake identifier may legitimately contain a dot.
9 · Identifier case, reported and not corrected
module "lower_case" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_lower"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_name = "analytics"
table_name = "orders"
}
output "case_check" {
value = {
schema = module.lower_case.schema_name_is_lower_case # true
table = module.lower_case.table_name_is_lower_case # true
}
}ℹ️ Snowflake stores
analyticsasANALYTICSunless it was created with quotes. This module does not normalise the value — upper-casing it would silently break every quoted-identifier deployment, which is the harder case to debug.
10 · A parameterised dataset
module "any_table" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_any_table"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
parameters = {
schemaName = "ANALYTICS"
tableName = ""
}
}💡 With
parametersset,describes_no_specific_tableisfalseeven with no table.
11 · Many tables from one map
locals {
tables = {
orders = { schema = "ANALYTICS", table = "ORDERS" }
customers = { schema = "ANALYTICS", table = "CUSTOMERS" }
ledger = { schema = "FINANCE", table = "LEDGER" }
}
}
module "tables" {
for_each = local.tables
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_${each.key}"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_name = each.value.schema
table_name = each.value.table
folder = "bronze/snowflake"
}
check "no_table_name_is_qualified" {
assert {
condition = alltrue([for m in module.tables : !m.table_name_looks_qualified])
error_message = "A table_name contains a dot; the schema belongs in schema_name."
}
}💡 The provider takes no lock on the Data Factory for this resource, so these are created concurrently and safely.
12 · `folder` is not a Snowflake schema
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_name = "ANALYTICS" # WHERE THE TABLE IS, in Snowflake
folder = "bronze/snowflake" # where the DATASET appears, in the portal
}
⚠️ Two arguments on this resource sound like "a container the table lives in", and only one of them is about the database. Swapping them applies cleanly and points the dataset at the wrong place.
13 · Annotations, description and the escape hatch
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
schema_name = "ANALYTICS"
table_name = "ORDERS"
description = "Order header rows, read hourly by the curated pipeline."
annotations = ["bronze", "hourly"]
additional_properties = {
"structure" = "[]"
}
}ℹ️ This resource has a dataset-level
descriptionand no per-column one — itsschema_columncarriesprecisionandscalewhere the rest of the family carriesdescription.
14 · Custom timeouts
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.snowflake_linked_service_name
table_name = "ORDERS"
timeouts = {
create = "45m"
read = "10m"
update = "45m"
delete = "45m"
}
}ℹ️ Defaults are 30m / 5m / 30m / 30m. Terraform silently discards an object key the type does not declare.
15 · 🏗️ End-to-end composition
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-analytics-eastus"
location = "eastus"
}
module "adf" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory.git?ref=v1.0.0"
name = "adf-analytics-eastus"
resource_group_name = module.rg.name
location = module.rg.location
identity = {
type = "SystemAssigned"
}
}
# No linked-service module exists in this suite yet, so the linked service is
# declared directly. Its connection string carries the Snowflake credential and
# chooses the ACCOUNT, WAREHOUSE and DATABASE -- keep it in a secret store and
# pass a reference; never inline it.
resource "azurerm_data_factory_linked_service_snowflake" "warehouse" {
name = "ls_snowflake_analytics"
data_factory_id = module.adf.id
connection_string = var.snowflake_connection_string # sensitive; supplied out of band
}
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-snowflake.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = module.adf.id
linked_service_name = azurerm_data_factory_linked_service_snowflake.warehouse.name
schema_name = "ANALYTICS"
table_name = "ORDERS"
folder = "bronze/snowflake"
schema_column = [
{ name = "ORDER_ID", type = "NUMBER", precision = 38, scale = 0 },
{ name = "AMOUNT", type = "NUMBER", precision = 18, scale = 2 },
{ name = "PLACED_AT", type = "TIMESTAMP_NTZ" },
]
annotations = ["bronze", "hourly"]
}
check "wiring_is_consistent" {
assert {
condition = !module.orders.numeric_column_definitions_look_wrong
error_message = "A numeric column definition is incoherent."
}
assert {
condition = !module.orders.table_name_looks_qualified
error_message = "table_name contains a dot; the schema belongs in schema_name."
}
assert {
condition = !module.orders.describes_no_specific_table
error_message = "The dataset resolves to nothing."
}
}
output "dataset_id" {
value = module.orders.id
}
output "reads_from" {
value = module.orders.qualified_table_name
}🔒 The Snowflake credential belongs to the linked service, provisioned out of band. This module accepts none and emits none. Note that
sensitive = trueredacts plan output and does not encrypt state — the protection that matters is an encrypted, access-controlled backend.
Required: name, data_factory_id, linked_service_name.
What it points at: schema_name, table_name, parameters.
Shape: schema_column ({name, type, precision, scale}), additional_properties.
Metadata: description, folder, annotations.
Tail: timeouts. There is no tags variable — the resource supports none.
Full input schemas
| Name | Type | Default | Notes |
|---|---|---|---|
name |
string |
— | Force-new. Non-empty only; a leading / is refused. |
data_factory_id |
string |
— | Force-new. Anchored Resource-ID validator. |
linked_service_name |
string |
— | A bare name. Chooses the account, warehouse and database. |
schema_name |
string |
null |
A separate argument most of the family lacks. |
table_name |
string |
null |
Unqualified — the schema goes in schema_name. |
schema_column |
list(object({ name, type, precision, scale })) |
[] |
No description. 32 Snowflake types, case-sensitive. |
parameters |
map(string) |
{} |
Supplied per pipeline run. |
additional_properties |
map(string) |
{} |
Unvalidated top-level keys. |
annotations |
list(string) |
[] |
Not tags. |
description |
string |
null |
The dataset's description. Empty string refused. |
folder |
string |
null |
The authoring tree — not a Snowflake schema. |
timeouts |
object({ create, read, update, delete }) |
null |
30m / 5m / 30m / 30m. |
| Output | Description | Notes |
|---|---|---|
id |
The dataset's Resource ID. | First, by convention. |
name, data_factory_id, data_factory_name |
Identity and parent. | Name parsed from the end of the ID. |
resource_group_name, subscription_id |
Where the factory lives. | |
linked_service_name |
The linked service, as supplied. | Chooses account, warehouse, database. |
the_linked_service_chooses_the_account_warehouse_and_database |
Constant. | Why this dataset is so small. |
this_resource_is_the_dataset_familys_biggest_outlier |
Constant. | The summary. |
the_schema_column_block_here_has_NO_description_field |
Constant. | A copied column is a type error. |
the_schema_column_type_set_is_SNOWFLAKE_SQL_not_data_factory |
Constant. | Two disjoint vocabularies. |
four_of_the_type_values_are_not_plain_words |
Constant. | A space and three underscores. |
this_resource_has_a_separate_schema_name_argument |
Constant. | |
schema_name, table_name, has_schema_name, has_table_name |
What it points at. | |
qualified_table_name |
schema.table. |
A display value only. |
describes_no_specific_table |
No table and no parameters. | Assert on this. |
table_name_looks_qualified |
A dot in table_name. |
Assert on this. |
schema_name_is_lower_case, table_name_is_lower_case |
Identifier folding. | Reported, not corrected. |
parameters, parameter_count |
The run-time inputs. | |
schema_column_count, has_schema_columns, schema_column_names, untyped_schema_column_names |
The column schema. | |
columns_with_precision, columns_with_scale |
Fields no sibling has. | |
columns_with_precision_on_a_non_numeric_type |
Accepted and ignored. | |
columns_with_scale_exceeding_precision |
Rejected by Snowflake at run time. | The certain error. |
columns_with_scale_but_no_precision |
Legal, rarely intended. | |
numeric_column_definitions_look_wrong |
Any of the three. | Assert on this. |
precision_and_scale_are_validated_only_as_non_negative_integers |
Constant. | |
uses_additional_properties, additional_property_count |
The escape hatch. | |
annotations, annotation_count, has_annotations |
A list, not tags. | |
description, has_description, folder, has_folder |
Metadata. | |
folder_and_schema_name_mean_different_things |
Constant. | |
force_new_fields, fields_that_can_change_after_creation, fields_azure_returns_on_read |
The change surface. | |
import_address, the_import_guard_names_this_resource_correctly |
Imports. | Two siblings get this wrong. |
this_dataset_moves_no_data_by_itself, creating_this_consumes_no_snowflake_credits |
Constants. | |
the_module_cannot_see_which_pipelines_use_this |
Constant. | Read before destroying. |
the_module_cannot_verify_the_table_exists, no_credential_is_configured_here |
Constants. | |
destroying_the_factory_destroys_this_dataset_too, destroying_this_does_not_touch_the_table |
Constants. | |
lifecycle_prevent_destroy_is_not_available_to_a_module_caller |
Constant. | |
this_is_a_real_azure_resource_not_a_composite, the_provider_takes_no_lock_on_the_data_factory |
Constants. | |
this_resource_supports_no_azure_resource_tags |
Constant. | Why there is no tags variable. |
no_secret_is_accepted_or_emitted_by_this_module |
Constant. |
No output is sensitive, and none can be.
Two column vocabularies that share nothing. Ten of the twelve Data Factory dataset resources accept
fifteen Data Factory types — Byte, Int64, String and so on. This one accepts thirty-two Snowflake SQL
types — NUMBER, VARCHAR, GEOGRAPHY. Not one value appears in both sets, and both are compared
case-sensitively, so a column list is refused in whichever direction it is copied. The offline harness proves
this exhaustively rather than asserting it: every one of the 47 values is driven against both this module and
a Data Factory-typed sibling, and each must be accepted by exactly one side.
The block is a different shape as well as a different vocabulary. There is no description field here —
precision and scale sit in its place. That makes a copied column a type error at parse time, which is
the better failure: it names the attribute rather than producing a confusing validation message.
Four type values are not plain words. DOUBLE PRECISION has a space; three TIMESTAMP_* variants have
underscores. Any list rebuilt by pattern-matching letters and digits loses exactly those four and looks
complete — which is a mistake this library made once while extracting the set, and the reason the module
carries the list verbatim.
precision and scale are barely validated. The provider applies IntAtLeast(0) to each and never
compares them to each other or to the type. So scale > precision plans cleanly and Snowflake refuses it at
run time; a precision on a VARCHAR is accepted and ignored; a scale with no precision takes Snowflake's
default. The module reports all three separately, because only the first is certainly an error — collapsing
them would overstate the other two.
The schema has its own argument here, and does not on three siblings. A dotted table_name is the
correct shape on the SQL Server Table, MySQL and PostgreSQL datasets and the wrong one here, where it reaches
Snowflake as a single identifier. qualified_table_name is emitted for display and documented as such,
because writing it back into table_name is exactly the mistake.
Identifier case is reported, not corrected. Snowflake folds unquoted identifiers to upper case, so
public becomes PUBLIC — unless the object was created with quotes, in which case the lower-case value is
right. Normalising would silently break the quoted case, which is the harder one to diagnose.
Only identity forces replacement, and the linked service is not force-new — so moving a dataset to a different Snowflake account, warehouse and database is an in-place attribute change no destroy-scan will surface.
| Concern | This module's default | Opt-out |
|---|---|---|
| Secrets | None accepted, none emitted. The connection string lives on the linked service. | Not available — by design. |
| Snowflake access | No permission required or used, and none is grantable through Azure RBAC. | — |
| Snowflake credits | None consumed. A dataset starts no warehouse. | — |
| Column types | Enforced against the provider's case-sensitive 32. | None — the set is closed. |
| A Data Factory type | Refused, with the disjointness named in the error message. | None. |
A per-column description |
Refused by the type — the attribute does not exist. | Use the dataset-level description. |
scale > precision |
Reported, not refused — the provider accepts it. | Ignore the output. |
| A precision on a non-numeric type | Reported, not refused. | Ignore the output. |
A dotted table_name |
Reported, not refused — a quoted identifier may contain a dot. | Ignore the output. |
| Identifier case | Reported, never normalised. | Ignore the output. |
tags |
Not offered — the resource supports none. | Tag the factory. |
🔒 There is no risky toggle to close here: the resource holds no credential and touches no data. The exposures are the delete, the numeric definitions the provider does not check, and the Snowflake reach the linked service confers — the module documents the first and reports the second.
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module with ?ref=v1.0.0 — never a branch. This library is plan-only: a human applies from CI.
terraform plan and the module's own validation {} blocks cover, offline and without credentials —
note it is plan and not terraform validate, which through a module call evaluates no variable values and
reports success:
- all 32 Snowflake types accepted at their exact casing, including the space and underscore values;
- every Data Factory type refused, with the disjointness named in the message;
precisionandscalerefused when negative or fractional, and accepted at zero;- the anchored Resource-ID shape of
data_factory_id, and the refusal of an ID passed aslinked_service_name; - every derived flag — the three numeric mismatches and their roll-up,
table_name_looks_qualified, both identifier-case flags anddescribes_no_specific_table— each with one fixture per branch throughterraform console.
The disjointness is executed, not asserted. The harness drives all 47 type values against both this module and a Data Factory-typed sibling — 94 module-runs — and requires each value to be accepted by exactly one side.
Only a real plan or apply can tell you whether the linked service exists, whether the table exists,
whether the identifier case matches, or whether a numeric definition Snowflake dislikes will be rejected.
dataset_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-analytics-eastus/providers/Microsoft.DataFactory/factories/adf-analytics-eastus/datasets/ds_orders"
reads_from = "ANALYTICS.ORDERS"
linked_service_name = "ls_snowflake_analytics"
has_schema_name = true
describes_no_specific_table = false
table_name_looks_qualified = false
schema_name_is_lower_case = false
schema_column_names = ["ORDER_ID", "AMOUNT", "PLACED_AT"]
columns_with_precision = ["ORDER_ID", "AMOUNT"]
columns_with_scale_exceeding_precision = []
numeric_column_definitions_look_wrong = false
force_new_fields = ["name", "data_factory_id"]
| Symptom | Cause | Fix |
|---|---|---|
every schema_column type must be one of the THIRTY-TWO SNOWFLAKE SQL TYPES on "Int64" |
A Data Factory type. The two sets share no values. | Use a Snowflake type — NUMBER here, not Int64. |
An unsupported-attribute error on description inside schema_column |
This block has no description field. |
Remove it; use precision/scale, or the dataset-level description. |
"varchar" is refused but "VARCHAR" works |
Case-sensitive, and these values are upper-case. | Copy the list from the documentation. |
DOUBLE PRECISION or TIMESTAMP_NTZ seems missing from a generated list |
A pattern matching letters and digits alone drops the space and the underscores. | Copy the list; do not regenerate it. |
| A pipeline fails on a numeric column that planned cleanly | scale exceeds precision; the provider never compares them. |
Check columns_with_scale_exceeding_precision. |
| A precision appears to do nothing | It is set on a type Snowflake does not apply it to. | Check columns_with_precision_on_a_non_numeric_type. |
A NUMBER column has an unexpected precision |
A scale was given with no precision; Snowflake used its default. |
Check columns_with_scale_but_no_precision. |
| A pipeline cannot find the table, and the names look right | Snowflake folds unquoted identifiers to upper case. | Check schema_name_is_lower_case and table_name_is_lower_case. |
| A configuration copied from a MySQL or PostgreSQL dataset resolves to nothing | Its table_name carried the schema; here that is one identifier. |
Split it: schema into schema_name. |
| A dataset applies but no pipeline can use it | No table and no parameters. | Check describes_no_specific_table. |
| The dataset suddenly reads from a different database | linked_service_name changed — an in-place update. |
Review in-place dataset changes, not only replacements. |
A timeouts key seems to have no effect |
Terraform silently discards an undeclared object key. | Compare against object({ create, read, update, delete }). |
azurerm_data_factory_dataset_snowflakeazurerm_data_factory_dataset_mysql— a Data Factory-typed siblingazurerm_data_factory_dataset_binary— noschema_columnblock at all- Azure Data Factory datasets
- Snowflake connector
SCOPE.md— this module's cross-module contract- Sibling modules:
terraform-azurerm-data-factory,terraform-azurerm-data-factory-dataset-mysql,terraform-azurerm-data-factory-dataset-postgresql,terraform-azurerm-data-factory-dataset-sql-server-table
💙 "Infrastructure as Code should be standardized, consistent, and secure."

