Skip to content

Commit 72441cc

Browse files
committed
Merge branch 'main' into feat/invalidate-cleanup-snapshots
2 parents 6f07b64 + fe6eabb commit 72441cc

32 files changed

Lines changed: 1219 additions & 60 deletions

File tree

docs/concepts/audits.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,38 @@ AUDIT (name price_is_not_null);
158158
SELECT * FROM @this_model
159159
WHERE price IS NULL;
160160
```
161+
### Standalone audits
161162

163+
Standalone audits are defined independently rather than being attached to a specific model. They specify the models they depend on using the `depends_on` property.
164+
165+
Unlike model-level audits, standalone audits can be used to validate data across one or more models without being associated with a single model.
166+
167+
Standalone audits run as scheduled nodes during both `sqlmesh plan` and `sqlmesh run`.
168+
169+
```sql linenums="1"
170+
AUDIT (
171+
name assert_item_price_is_not_null,
172+
dialect spark,
173+
standalone TRUE,
174+
depends_on (
175+
sushi.items
176+
)
177+
);
178+
179+
SELECT *
180+
FROM sushi.items
181+
WHERE
182+
ds BETWEEN @start_ds AND @end_ds
183+
AND price IS NULL;
184+
```
185+
186+
In this example, the audit checks that the `price` column in `sushi.items` does not contain `NULL` values for the selected date range.
187+
188+
Standalone audits can declare dependencies using the `depends_on` property. SQLMesh can often infer dependencies directly from the audit query, but using `depends_on` is recommended when inference isn't sufficient.
189+
190+
!!! note
191+
192+
Standalone audits are non-blocking only. Because they are not associated with a single model, SQLMesh cannot determine which model should be blocked if the audit fails.
162193
## Built-in audits
163194
SQLMesh comes with a suite of built-in generic audits that cover a broad set of common use cases. Built-in audits are blocking by default, but they all have non-blocking counterparts which you can use by appending `_non_blocking` - see [Non-blocking audits](#non-blocking-audits).
164195

docs/concepts/plans.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ For context, every model has a start date. The start can be specified in [the mo
109109

110110
Because the prod environment supports business operations, prod plans ensure every model is backfilled from its start date until the most recent completed time interval. Due to that restriction, the `plan` command's `--start` and `--end` options are not supported for regular plans against prod. The options are supported for [restatement plans](#restatement-plans) against prod to allow re-processing a subset of existing data.
111111

112+
!!! note "Explicit execution time"
113+
114+
"The most recent completed time interval" is measured relative to the plan's *execution time*, which defaults to now. If you pass an explicit `--execution-time` that is later than the intervals already loaded in prod, the plan extends its end date up to that time and backfills the intervals in between.
115+
116+
For example, if a daily model in prod is loaded through 2025-12-25, running `sqlmesh plan --execution-time '2025-12-28'` backfills the missing 2025-12-26 and 2025-12-27 intervals.
117+
112118
Non-prod plans are typically used for development, so their models can optionally be backfilled for any date range with the `--start` and `--end` options. Limiting the date range makes backfills faster and development more efficient, especially for incremental models using large tables.
113119

114120
#### Model kind limitations

docs/guides/linter.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ Here are all of SQLMesh's built-in linting rules:
7474
| `invalidselectstarexpansion` | Correctness | The query's top-level selection may be `SELECT *`, but only if SQLMesh can expand the `SELECT *` into individual columns |
7575
| `noselectstar` | Stylistic | The query's top-level selection may not be `SELECT *`, even if SQLMesh can expand the `SELECT *` into individual columns |
7676
| `nomissingaudits` | Governance | SQLMesh did not find any `audits` in the model's configuration to test data quality. |
77+
| `nomissingunittest` | Governance | SQLMesh did not find any `unit tests` associated with the model to test |
7778

7879
### User-defined rules
7980

@@ -126,6 +127,14 @@ Error: Linter detected errors in the code. Please fix them before proceeding.
126127

127128
Use `sqlmesh lint --help` for more information.
128129

130+
You can pass `--local` to run lint without loading state from the configured state connection:
131+
132+
``` bash
133+
$ sqlmesh lint --local
134+
```
135+
136+
This can make linting faster in repositories where all referenced models are loaded from local files. In multi-repository setups, or when linting only a subset of projects, `--local` may cause additional linting errors because SQLMesh will not resolve references or schemas from models that exist only in remote state.
137+
129138

130139
## Applying linting rules
131140

@@ -258,4 +267,4 @@ You may specify that a rule's violation should not error and only log a warning
258267
)
259268
```
260269

261-
SQLMesh will raise an error if the same rule is included in more than one of the `rules`, `warn_rules`, and `ignored_rules` keys since they should be mutually exclusive.
270+
SQLMesh will raise an error if the same rule is included in more than one of the `rules`, `warn_rules`, and `ignored_rules` keys since they should be mutually exclusive.

docs/integrations/engines/redshift.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pip install "sqlmesh[redshift]"
2929
| `region` | The AWS region of the Amazon Redshift cluster | string | N |
3030
| `cluster_identifier` | The cluster identifier of the Amazon Redshift cluster | string | N |
3131
| `iam` | If IAM authentication is enabled. IAM must be True when authenticating using an IdP | dict | N |
32+
| `db_user` | The database user to authenticate as. Required when using IAM authentication | string | N |
3233
| `is_serverless` | If the Amazon Redshift cluster is serverless (Default: `False`) | bool | N |
3334
| `serverless_acct_id` | The account ID of the serverless cluster | string | N |
3435
| `serverless_work_group` | The name of work group for serverless end point | string | N |

docs/reference/cli.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,9 @@ Usage: sqlmesh lint [OPTIONS]
656656
657657
Options:
658658
--model TEXT A model to lint. Multiple models can be linted. If no models are specified, every model will be linted.
659+
--local Lint using only locally loaded project files without loading state. In multi-repository setups, or when
660+
linting only a subset of projects, this may cause additional linting errors because SQLMesh will not resolve
661+
references or schemas from models that exist only in remote state.
659662
--help Show this message and exit.
660663
661-
```
664+
```

docs/reference/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Formatting settings for the `sqlmesh format` command and UI.
114114
| `normalize` | Whether to normalize SQL (Default: False) | boolean | N |
115115
| `pad` | The number of spaces to use for padding (Default: 2) | int | N |
116116
| `indent` | The number of spaces to use for indentation (Default: 2) | int | N |
117-
| `normalize_functions` | Whether to normalize function names. Supported values are: 'upper' and 'lower' (Default: None) | string | N |
117+
| `normalize_functions` | How to normalize function name casing. `false` (default) preserves the casing of custom and audit function names as written; `"upper"` uppercases all function names; `"lower"` lowercases all function names; `true` defers to SQLGlot's generator default and uppercases all function names including custom ones; `null` (or omitting the key) is excluded during serialization and therefore takes the same `false` default path — it does **not** defer to SQLGlot's generator default. Note: SQLGlot built-in function names may still be canonicalized by the parser regardless of this setting. | string \| boolean \| null | N |
118118
| `leading_comma` | Whether to use leading commas (Default: False) | boolean | N |
119119
| `max_text_width` | The maximum text width in a segment before creating new lines (Default: 80) | int | N |
120120
| `append_newline` | Whether to append a newline to the end of the file (Default: False) | boolean | N |

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ dev = [
6565
# it depends on the 'InvalidCatalogIntegrationConfigError' class that only exists as of dbt-adapters==1.16.6
6666
# so we exclude it to prevent failures and hope that upstream releases a new version with the correct constraint
6767
"dbt-snowflake!=1.10.1",
68+
# fastjsonschema 2.22+ uses PEP 604 type hints and now requires Python >=3.10
69+
# (upstream #211 / #213). Keep 3.9 on the 2.21 line; pulled in via
70+
# dbt-bigquery → nbformat.
71+
"fastjsonschema<2.22; python_version<'3.10'",
6872
"dbt-athena-community",
6973
"dbt-clickhouse",
7074
"dbt-databricks",

sqlmesh/cli/main.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@
4444
LOCAL_ONLY_COMMANDS = ("format",)
4545

4646

47+
class _SQLMeshGroup(click.Group):
48+
def parse_args(self, ctx: click.Context, args: t.List[str]) -> t.List[str]:
49+
rest = super().parse_args(ctx, args)
50+
# Preserve the subcommand arguments because Click consumes them before invoking the group callback.
51+
protected_args = getattr(ctx, "_protected_args", None)
52+
if protected_args is None:
53+
protected_args = ctx.protected_args
54+
ctx.meta["subcommand_args"] = tuple(protected_args) + tuple(ctx.args)
55+
return rest
56+
57+
4758
def _sqlmesh_version() -> str:
4859
try:
4960
from sqlmesh import __version__
@@ -53,7 +64,7 @@ def _sqlmesh_version() -> str:
5364
return "0.0.0"
5465

5566

56-
@click.group(no_args_is_help=True)
67+
@click.group(cls=_SQLMeshGroup, no_args_is_help=True)
5768
@click.version_option(version=_sqlmesh_version(), message="%(version)s")
5869
@opt.paths
5970
@opt.config
@@ -118,6 +129,9 @@ def cli(
118129
load = True
119130
# Local-only gating must hold for any number of --paths, so it stays outside the block below.
120131
load_state = ctx.invoked_subcommand not in LOCAL_ONLY_COMMANDS
132+
# The parent callback constructs Context before Click invokes `lint`, so inspect its parsed args here.
133+
if ctx.invoked_subcommand == "lint" and "--local" in ctx.meta["subcommand_args"]:
134+
load_state = False
121135

122136
if len(paths) == 1:
123137
path = os.path.abspath(paths[0])
@@ -539,6 +553,7 @@ def diff(ctx: click.Context, environment: t.Optional[str] = None) -> None:
539553
)
540554
@click.option(
541555
"--min-intervals",
556+
type=int,
542557
default=None,
543558
help="For every model, ensure at least this many intervals are covered by a missing intervals check regardless of the plan start date",
544559
)
@@ -1199,6 +1214,12 @@ def environments(obj: Context) -> None:
11991214
multiple=True,
12001215
help="A model to lint. Multiple models can be linted. If no models are specified, every model will be linted.",
12011216
)
1217+
@click.option(
1218+
"--local",
1219+
is_flag=True,
1220+
expose_value=False,
1221+
help="Lint using only locally loaded project files without loading state.",
1222+
)
12021223
@click.pass_obj
12031224
@error_handler
12041225
@cli_analytics

sqlmesh/core/config/connection.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,7 @@ class RedshiftConnectionConfig(ConnectionConfig):
13351335
region: The AWS region where the Amazon Redshift cluster is located.
13361336
cluster_identifier: The cluster identifier of the Amazon Redshift cluster.
13371337
iam: If IAM authentication is enabled. Default value is False. IAM must be True when authenticating using an IdP.
1338+
db_user: The database user to authenticate as. Required when using IAM authentication.
13381339
is_serverless: Redshift end-point is serverless or provisional. Default value false.
13391340
serverless_acct_id: The account ID of the serverless. Default value None
13401341
serverless_work_group: The name of work group for serverless end point. Default value None.
@@ -1360,6 +1361,7 @@ class RedshiftConnectionConfig(ConnectionConfig):
13601361
region: t.Optional[str] = None
13611362
cluster_identifier: t.Optional[str] = None
13621363
iam: t.Optional[bool] = None
1364+
db_user: t.Optional[str] = None
13631365
is_serverless: t.Optional[bool] = None
13641366
serverless_acct_id: t.Optional[str] = None
13651367
serverless_work_group: t.Optional[str] = None
@@ -1397,6 +1399,7 @@ def _connection_kwargs_keys(self) -> t.Set[str]:
13971399
"region",
13981400
"cluster_identifier",
13991401
"iam",
1402+
"db_user",
14001403
"is_serverless",
14011404
"serverless_acct_id",
14021405
"serverless_work_group",

sqlmesh/core/config/format.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,21 @@ class FormatConfig(BaseConfig):
1212
normalize: Whether to normalize the SQL code or not.
1313
pad: The number of spaces to use for padding.
1414
indent: The number of spaces to use for indentation.
15-
normalize_functions: Whether or not to normalize all function names. Possible values are: 'upper', 'lower'
15+
normalize_functions: How to normalize function name casing.
16+
17+
* ``False`` (default) — preserves the original spelling of custom and audit
18+
function names. SQLGlot built-in functions (e.g. ``COUNT``, ``SUM``) may
19+
still be uppercased because the parser discards the original token.
20+
* ``"upper"`` — uppercases all function names, including custom audit
21+
references.
22+
* ``"lower"`` — lowercases all function names, including built-in ones.
23+
* ``True`` — defers to SQLGlot's generator default, which uppercases all
24+
function names including custom ones.
25+
* ``None`` — excluded from the serialized generator options by Pydantic's
26+
``exclude_none`` behaviour, so ``format_model_expressions`` falls back to
27+
its own ``False`` default. Setting this in YAML as ``null`` or omitting
28+
the key is therefore equivalent to ``false``; it does **not** defer to
29+
SQLGlot's generator default the way ``True`` does.
1630
leading_comma: Whether to use leading commas or not.
1731
max_text_width: The maximum text width in a segment before creating new lines.
1832
append_newline: Whether to append a newline to the end of the file or not.
@@ -22,7 +36,7 @@ class FormatConfig(BaseConfig):
2236
normalize: bool = False
2337
pad: int = 2
2438
indent: int = 2
25-
normalize_functions: t.Optional[str] = None
39+
normalize_functions: t.Union[str, bool, None] = False
2640
leading_comma: bool = False
2741
max_text_width: int = 80
2842
append_newline: bool = False

0 commit comments

Comments
 (0)