1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
//! Command-line argument parsing for `zhao`.
use std::path::PathBuf;
use clap::{Parser, Subcommand, ValueEnum};
/// zhao: a change-review and CI engine for data transformation projects.
#[derive(Debug, Parser)]
#[command(name = "zhao", version)]
pub struct Cli {
/// The command to run.
#[command(subcommand)]
pub command: Command,
}
/// A `zhao` subcommand.
#[derive(Debug, Subcommand)]
pub enum Command {
/// Runs the breaking-change gate: diffs the current project against a
/// Baseline and exits non-zero if any Rule fires at `error` Severity.
Check(CheckArgs),
/// Runs the identical engine as `check` -- Baseline resolution, diff,
/// Rule evaluation, report rendering -- but always exits zero,
/// regardless of what Severity outcomes are present. For local
/// inspection during development; use `check` for CI gating.
Diff(CheckArgs),
/// Answers "what's upstream/downstream of this model?" -- a
/// structural query over the current project's compiled state, not a
/// Baseline-vs-current diff. No `--state`, no git, no `dbt compile`.
Lineage(LineageArgs),
/// Previews a model's/seed's/source's query results -- a thin,
/// wrapper-aware wrapper around dbt's own `dbt show`, capped by a
/// configurable row limit so an unbounded preview can't trigger an
/// expensive full warehouse scan. Works against both dbt-core and dbt
/// Fusion projects.
Show(ShowArgs),
/// Replaces the current `zhao` binary with a release fetched from
/// GitHub Releases -- the only command that reaches the network at
/// all, and only to download the binary itself; it never sends
/// anything from your project (see issue #28; every other command
/// stays fully offline).
Update(UpdateArgs),
}
/// Arguments for `zhao update`.
#[derive(Debug, clap::Args)]
pub struct UpdateArgs {
/// A specific release tag to install (e.g. `v0.1.1`), pinning to
/// that exact release instead of the latest stable one. Mutually
/// exclusive with `--nightly` (which is really just a shorthand for
/// the tag `"nightly"`, always the current moving nightly build).
#[arg(conflicts_with = "nightly")]
pub version: Option<String>,
/// Installs the latest nightly build instead of the latest stable
/// release.
#[arg(long)]
pub nightly: bool,
}
/// Arguments for `zhao lineage`.
#[derive(Debug, clap::Args)]
pub struct LineageArgs {
/// The lineage target, in dbt's own selector syntax: a bare model
/// name (or `model.column` for column-level lineage) shows both
/// upstream and downstream; a `+` prefix shows only upstream
/// (ancestors); a `+` suffix shows only downstream (descendants).
/// Required for `--text`; optional for the default HTML output,
/// where omitting it embeds the whole project's lineage graph
/// instead of scoping to one target.
pub target: Option<String>,
/// The dbt project directory to query. Its current compiled manifest
/// is read from `<project-dir>/target/manifest.json` by default --
/// run `dbt compile` in the project before invoking `zhao lineage`
/// (or pass `--compile`) -- unless `--dbt-arg`/`--dbt-args` includes
/// a `--target-path` override, in which case the manifest is read
/// from `<project-dir>/<target-path>/manifest.json` instead. See
/// `crate::dbt_target::resolve_target_dir`.
#[arg(long, default_value = ".")]
pub project_dir: PathBuf,
/// Writes the self-contained, interactive HTML lineage graph to this
/// explicit path instead of the computed default under
/// `target/zhao/lineage_graphs/`. HTML is already the default output
/// mode -- this only overrides *where* it's written, not whether it's
/// produced; pass `--text` for the old plain-text report instead.
#[arg(long, conflicts_with = "text")]
pub html: Option<PathBuf>,
/// Prints the old plain-text report to stdout instead of the default
/// HTML export. A target is still required for `--text`, same as
/// text output always required one.
#[arg(long, conflicts_with = "html")]
pub text: bool,
/// Runs `dbt compile` in `--project-dir` before generating the
/// export, for a guaranteed-fresh view. Without it, the existing
/// `target/manifest.json` is read as-is, same as the default for
/// text output.
#[arg(long)]
pub compile: bool,
/// Disambiguates `target` when its bare model name matches more
/// than one model across different dbt packages (a real but
/// uncommon project shape -- multiple internal packages, dbt Mesh,
/// etc.) -- narrows resolution to the model in this package only.
/// The error for an ambiguous target without this flag lists every
/// matching model's full ID (`model.<package>.<name>`), which names
/// exactly what to pass here. Has no effect (and no error) when the
/// bare name is already unambiguous.
#[arg(long)]
pub package: Option<String>,
/// The intended CLI override for `zhao.yml`'s `log.level` (see issue
/// #35). Accepted and parsed now -- neither this flag nor `zhao.yml`'s
/// own `log.level` actually changes anything about the daily run log
/// yet, since its only defined content today (`Mirror`) is a literal,
/// unmodified copy of stdout, and `Debug` has no defined content of
/// its own to switch to -- reserved so a later ticket adding real
/// debug-level content doesn't also need another config-shape change.
#[arg(long = "log-level", value_enum)]
pub log_level: Option<LogLevelArg>,
/// One-off override for `zhao.yml`'s `log.retention_days` (see
/// issue #37): purges `target/zhao/logs/` files older than this
/// many days for this run only, without changing `zhao.yml`. With
/// neither this nor `zhao.yml`'s `log.retention_days` set, no
/// purging happens at all -- the default is "keep everything."
#[arg(long = "purge-logs")]
pub purge_logs: Option<u32>,
/// An extra argument to append to the `dbt compile` invocation
/// `--compile` runs -- repeat for multiple arguments, e.g.
/// `--dbt-arg --target --dbt-arg ci`. Appended verbatim, in the
/// order given; zhao never parses or validates these, dbt does --
/// except a `--target-path <dir>` pair (or `--target-path=<dir>`),
/// which zhao also reads back itself to locate the resulting
/// `manifest.json`, so `--compile`'s output can be isolated to a
/// directory other than the project's real `target/` without zhao
/// losing track of where to read it from afterward. Mutually
/// exclusive with `--dbt-args`.
#[arg(
long = "dbt-arg",
conflicts_with = "dbt_args",
allow_hyphen_values = true
)]
pub dbt_arg: Vec<String>,
/// A single dbt-invocation-shaped string to split (shell-word-style)
/// into individual arguments and append to the `dbt compile`
/// invocation `--compile` runs -- a convenience alternative to
/// repeating `--dbt-arg`, e.g. `--dbt-args "--target-path /tmp/zhao
/// --target ci"`. Same `--target-path` handling as `--dbt-arg`.
/// Mutually exclusive with `--dbt-arg`.
#[arg(
long = "dbt-args",
conflicts_with = "dbt_arg",
allow_hyphen_values = true
)]
pub dbt_args: Option<String>,
/// The executable/prefix zhao invokes for the `dbt compile`
/// subprocess `--compile` runs -- ordinarily just `"dbt"`, resolved
/// via `PATH`. Accepts a multi-word prefix (shell-word-split, same
/// as `--dbt-args`), so a project already using its own wrapper
/// instead of invoking `dbt` directly can point zhao at that
/// instead. Overrides `zhao.yml`'s `dbt-command` when given; with
/// neither set, defaults to `"dbt"`.
#[arg(long = "dbt-command", allow_hyphen_values = true)]
pub dbt_command: Option<String>,
}
impl LineageArgs {
/// Resolves the final, ordered list of extra arguments to append to
/// the `dbt compile` invocation `--compile` runs, from whichever of
/// `--dbt-arg`/`--dbt-args` was given (clap's `conflicts_with` on
/// both fields already guarantees at most one was) -- empty if
/// neither was. Shares its splitting logic with
/// [`CheckArgs::dbt_passthrough_args`].
pub fn dbt_passthrough_args(&self) -> Result<Vec<String>, String> {
split_dbt_passthrough_args(&self.dbt_arg, &self.dbt_args)
}
/// Splits `target` into the bare model name, an optional column name
/// (present for a `model.column` target), and the requested
/// [`zhao_core::lineage::Direction`], per dbt's own `+`-prefix/suffix
/// selector convention. `None` when no target was given at all
/// (only valid alongside `--html`, for the whole-project graph).
pub fn parse_target(&self) -> Option<(&str, Option<&str>, zhao_core::lineage::Direction)> {
let target = self.target.as_deref()?;
let has_prefix = target.starts_with('+');
let after_prefix = target.strip_prefix('+').unwrap_or(target);
let has_suffix = after_prefix.ends_with('+');
let name = after_prefix.strip_suffix('+').unwrap_or(after_prefix);
// `+target+` (both sides) means both directions in dbt's own
// selector syntax too, same as a bare target -- handled
// explicitly here rather than falling out of checking the prefix
// alone and leaving a trailing `+` stuck to the name.
let direction = match (has_prefix, has_suffix) {
(true, true) | (false, false) => zhao_core::lineage::Direction::Both,
(true, false) => zhao_core::lineage::Direction::Upstream,
(false, true) => zhao_core::lineage::Direction::Downstream,
};
// dbt model names never contain `.`, so the first `.` (if any)
// unambiguously separates the model from a column-level target.
let (model, column) = match name.split_once('.') {
Some((model, column)) => (model, Some(column)),
None => (name, None),
};
Some((model, column, direction))
}
}
/// `zhao show`'s `--output` values -- `Text` (the default) relays dbt's
/// own human-readable preview table, matching what running `dbt show`
/// directly in a terminal would print; `Json` passes `--output json`
/// through to the underlying `dbt show` and normalizes its result (see
/// `crate::show::extract_show_result`) into one stable `{"columns":
/// [...], "rows": [...]}` shape regardless of which engine (dbt-core or
/// Fusion) produced it -- the two engines' own JSON shapes differ (a
/// `{"node", "show"}` wrapper on dbt-core vs. a bare array on Fusion,
/// confirmed against real installs of both), so normalizing here is what
/// actually makes this a stable contract for a consumer like
/// `zhao-vscode-ext`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
pub enum ShowOutputFormat {
/// dbt's own human-readable preview table, relayed as-is.
#[default]
Text,
/// A normalized `{"columns": [...], "rows": [...]}` JSON shape.
Json,
}
/// Arguments for `zhao show`.
#[derive(Debug, clap::Args)]
pub struct ShowArgs {
/// The model/seed/source to preview, in dbt's own selector syntax --
/// a single bare name only (unlike `zhao lineage`'s `target`, no
/// `+`-prefix/suffix graph scoping: `dbt show` previews one resolved
/// relation's own query, not a graph slice).
pub target: String,
/// The dbt project directory to preview against.
#[arg(long, default_value = ".")]
pub project_dir: PathBuf,
/// Caps how many rows the underlying `dbt show` returns. Resolution
/// order: this flag, if given; else `zhao.yml`'s `show.default_limit`;
/// else a hardcoded fallback of 50 -- deliberately capped by default
/// so an unbounded preview never triggers an expensive full warehouse
/// scan by accident.
#[arg(long)]
pub limit: Option<u32>,
/// Output format -- see [`ShowOutputFormat`].
#[arg(long, value_enum, default_value_t = ShowOutputFormat::Text)]
pub output: ShowOutputFormat,
/// Disambiguates `target` when its bare name matches more than one
/// model/seed/source across different dbt packages. Same convention
/// as `zhao lineage --package`.
#[arg(long)]
pub package: Option<String>,
/// An extra argument to append to the underlying `dbt show`
/// invocation -- repeat for multiple arguments, e.g. `--dbt-arg
/// --vars --dbt-arg '{key: value}'`. Appended verbatim, after
/// `--select`/`--limit`/`--output`; zhao never parses or validates
/// these, dbt does. Mutually exclusive with `--dbt-args`. Same
/// convention as `zhao lineage --dbt-arg`.
#[arg(
long = "dbt-arg",
conflicts_with = "dbt_args",
allow_hyphen_values = true
)]
pub dbt_arg: Vec<String>,
/// A single dbt-invocation-shaped string to split (shell-word-style)
/// into individual arguments and append to the underlying `dbt show`
/// invocation -- a convenience alternative to repeating `--dbt-arg`.
/// Mutually exclusive with `--dbt-arg`.
#[arg(
long = "dbt-args",
conflicts_with = "dbt_arg",
allow_hyphen_values = true
)]
pub dbt_args: Option<String>,
/// The executable/prefix zhao invokes for the `dbt show` subprocess --
/// ordinarily just `"dbt"`, resolved via `PATH`. Same convention as
/// `zhao lineage --dbt-command`: overrides `zhao.yml`'s `dbt-command`
/// when given; with neither set, defaults to `"dbt"`.
#[arg(long = "dbt-command", allow_hyphen_values = true)]
pub dbt_command: Option<String>,
}
impl ShowArgs {
/// Resolves the final, ordered list of extra arguments to append to
/// the underlying `dbt show` invocation, from whichever of
/// `--dbt-arg`/`--dbt-args` was given. Shares its splitting logic with
/// [`LineageArgs::dbt_passthrough_args`]/[`CheckArgs::dbt_passthrough_args`].
pub fn dbt_passthrough_args(&self) -> Result<Vec<String>, String> {
split_dbt_passthrough_args(&self.dbt_arg, &self.dbt_args)
}
}
/// Arguments shared by `zhao check` and `zhao diff` -- both run the
/// identical engine and accept identical inputs; they differ only in
/// what they do with the result (a gate's exit code vs. always zero).
#[derive(Debug, clap::Args)]
pub struct CheckArgs {
/// Path to the Baseline's compiled dbt manifest (`manifest.json`). If
/// omitted, zhao resolves its own Baseline: it finds the merge-base
/// commit between `HEAD` and `--against`, checks it out into a
/// temporary git worktree, compiles it with `dbt`, and uses that as
/// the Baseline instead.
#[arg(long)]
pub state: Option<PathBuf>,
/// The dbt project directory to check. Its current compiled manifest
/// is read from `<project-dir>/target/manifest.json` -- run `dbt
/// compile` in the project before invoking `zhao check`.
#[arg(long, default_value = ".")]
pub project_dir: PathBuf,
/// The ref to resolve a git-native Baseline's merge-base against.
/// Ignored when `--state` is given. Overrides `zhao.yml`'s `against`
/// when given; with neither set, defaults to `"master"`. No
/// `default_value` here (unlike most flags) specifically so the
/// engine can tell "the user explicitly passed --against" apart from
/// "nothing was passed at all" -- otherwise a `zhao.yml`-configured
/// value could never be distinguished from clap's own default and
/// would always lose to it.
#[arg(long)]
pub against: Option<String>,
/// Output format.
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
pub format: OutputFormat,
/// Disables ANSI color codes in text output, regardless of what
/// auto-detection would otherwise decide. Has no effect on `--format
/// json`, which never contains color codes.
#[arg(long)]
pub no_color: bool,
/// An extra argument to append to every `dbt deps`/`dbt compile`
/// invocation zhao runs internally (git-native Baseline resolution
/// only) -- repeat for multiple arguments, e.g. `--dbt-arg --target
/// --dbt-arg ci`. Appended verbatim, in the order given; zhao never
/// parses or validates these, dbt does. Mutually exclusive with
/// `--dbt-args`.
#[arg(
long = "dbt-arg",
conflicts_with = "dbt_args",
allow_hyphen_values = true
)]
pub dbt_arg: Vec<String>,
/// A single dbt-invocation-shaped string to split (shell-word-style)
/// into individual arguments and append to every `dbt deps`/`dbt
/// compile` invocation zhao runs internally -- a convenience
/// alternative to repeating `--dbt-arg`, e.g. `--dbt-args "--target ci
/// --vars '{\"key\": \"value\"}'"`. Mutually exclusive with
/// `--dbt-arg`.
#[arg(
long = "dbt-args",
conflicts_with = "dbt_arg",
allow_hyphen_values = true
)]
pub dbt_args: Option<String>,
/// The executable/prefix zhao invokes for every `dbt` subprocess call
/// it makes internally (`dbt deps`/`dbt compile` for Baseline
/// resolution, `dbt run-operation` for `--check-relations`) --
/// ordinarily just `"dbt"`, resolved via `PATH`. Accepts a multi-word
/// prefix (shell-word-split, same as `--dbt-args`), so a project
/// already using its own wrapper instead of invoking `dbt` directly
/// (e.g. `"uv run dbt"`, or a custom wrapper like `"myshell custom-flag"`)
/// can point zhao at that instead. Overrides `zhao.yml`'s
/// `dbt-command` when given; with neither set, defaults to `"dbt"`.
#[arg(long = "dbt-command", allow_hyphen_values = true)]
pub dbt_command: Option<String>,
/// Upgrades the conditional schema-evolution flag (see the "Schema
/// evolution" report section) into a definitive one, or drops it
/// entirely, by actually checking whether each flagged model exists
/// in the configured target -- via the same connection `dbt run`
/// already needs, never a connection zhao holds itself. Opt-in,
/// since (unlike every other check zhao runs) this requires a real
/// connection; without it, the flag stays conditionally worded.
/// Silently unavailable (not an error) for any warehouse zhao
/// doesn't yet support checking against.
#[arg(long = "check-relations")]
pub check_relations: bool,
/// The name of the dbt target the `--defer` plan should defer to
/// (e.g. `"prod"`) -- purely a human-readable label shown alongside
/// the generated command, not passed to dbt as `--target`. Overrides
/// `zhao.yml`'s `defer.target` when given. Has no effect unless a
/// state path is also available (from `--defer-state` or
/// `zhao.yml`'s `defer.state`).
#[arg(long = "defer-target")]
pub defer_target: Option<String>,
/// The path to a compiled manifest to defer to -- when set (here or
/// via `zhao.yml`'s `defer.state`), the report's `--defer` plan
/// includes a ready-to-run `dbt ... --defer --state <path>` command
/// alongside the plan's build/defer Node lists, not just the lists
/// themselves. Overrides `zhao.yml`'s `defer.state` when given.
#[arg(long = "defer-state")]
pub defer_state: Option<PathBuf>,
/// The intended CLI override for `zhao.yml`'s `log.level` (see issue
/// #35). Accepted and parsed now -- neither this flag nor `zhao.yml`'s
/// own `log.level` actually changes anything about the daily run log
/// yet, since its only defined content today (`Mirror`) is a literal,
/// unmodified copy of stdout, and `Debug` has no defined content of
/// its own to switch to -- reserved so a later ticket adding real
/// debug-level content doesn't also need another config-shape change.
#[arg(long = "log-level", value_enum)]
pub log_level: Option<LogLevelArg>,
/// One-off override for `zhao.yml`'s `log.retention_days` (see
/// issue #37): purges `target/zhao/logs/` files older than this
/// many days for this run only, without changing `zhao.yml`. With
/// neither this nor `zhao.yml`'s `log.retention_days` set, no
/// purging happens at all -- the default is "keep everything."
#[arg(long = "purge-logs")]
pub purge_logs: Option<u32>,
/// Skips the check that the current project's compiled manifest
/// (`<project-dir>/target/manifest.json`) is newer than its own dbt
/// source files. Without this flag, zhao refuses to run against a
/// stale manifest -- e.g. one left over from a different branch after
/// a checkout, with `dbt compile` never rerun -- since diffing against
/// it would silently produce an incorrect report. Not recommended;
/// exists for cases like a manually supplied test fixture with no
/// real dbt project alongside it.
#[arg(long = "allow-stale-manifest")]
pub allow_stale_manifest: bool,
}
impl CheckArgs {
/// Resolves the final, ordered list of extra arguments to append to
/// every `dbt deps`/`dbt compile` invocation, from whichever of
/// `--dbt-arg`/`--dbt-args` was given (clap's `conflicts_with` on both
/// fields already guarantees at most one was) -- empty if neither was.
/// Shares its splitting logic with [`LineageArgs::dbt_passthrough_args`].
pub fn dbt_passthrough_args(&self) -> Result<Vec<String>, String> {
split_dbt_passthrough_args(&self.dbt_arg, &self.dbt_args)
}
}
/// The splitting logic shared by [`CheckArgs::dbt_passthrough_args`] and
/// [`LineageArgs::dbt_passthrough_args`], both of which accept the same
/// two mutually-exclusive CLI forms for a `dbt` passthrough-arguments
/// list: repeated `--dbt-arg` wins outright when given; otherwise a
/// single `--dbt-args` string is shell-word-split; otherwise empty.
fn split_dbt_passthrough_args(
dbt_arg: &[String],
dbt_args: &Option<String>,
) -> Result<Vec<String>, String> {
if !dbt_arg.is_empty() {
return Ok(dbt_arg.to_vec());
}
if let Some(raw) = dbt_args {
return shell_words::split(raw)
.map_err(|err| format!("could not parse --dbt-args {raw:?}: {err}"));
}
Ok(Vec::new())
}
/// `zhao check`'s output format.
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
/// A brief human-readable summary.
Text,
/// Machine-readable JSON.
Json,
}
/// The CLI-facing spelling of [`zhao_core::config::LogLevel`] -- a
/// separate type (rather than deriving `ValueEnum` on the `zhao-core`
/// type directly) so `zhao-core` doesn't need a `clap` dependency just
/// for this one flag.
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum LogLevelArg {
/// A literal mirror of whatever was already printed to stdout.
Mirror,
/// Reserved for a later ticket's richer, internal-only content.
Debug,
}
impl From<LogLevelArg> for zhao_core::config::LogLevel {
fn from(value: LogLevelArg) -> Self {
match value {
LogLevelArg::Mirror => zhao_core::config::LogLevel::Mirror,
LogLevelArg::Debug => zhao_core::config::LogLevel::Debug,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use zhao_core::lineage::Direction;
fn args(target: &str) -> LineageArgs {
LineageArgs {
target: Some(target.to_string()),
project_dir: PathBuf::from("."),
html: None,
text: false,
compile: false,
package: None,
log_level: None,
purge_logs: None,
dbt_arg: Vec::new(),
dbt_args: None,
dbt_command: None,
}
}
#[test]
fn bare_target_parses_as_both_directions() {
let a = args("dim_customers");
let (name, column, direction) = a.parse_target().expect("target should be present");
assert_eq!(name, "dim_customers");
assert_eq!(column, None);
assert_eq!(direction, Direction::Both);
}
#[test]
fn a_plus_prefix_parses_as_upstream_only() {
let a = args("+dim_customers");
let (name, column, direction) = a.parse_target().expect("target should be present");
assert_eq!(name, "dim_customers");
assert_eq!(column, None);
assert_eq!(direction, Direction::Upstream);
}
#[test]
fn a_plus_suffix_parses_as_downstream_only() {
let a = args("dim_customers+");
let (name, column, direction) = a.parse_target().expect("target should be present");
assert_eq!(name, "dim_customers");
assert_eq!(column, None);
assert_eq!(direction, Direction::Downstream);
}
/// `+target+` (both sides), same as dbt's own selector syntax, means
/// both directions -- same as a bare target, and critically not a
/// garbled name with a stray trailing `+` still attached.
#[test]
fn a_plus_prefix_and_suffix_together_parses_as_both_directions() {
let a = args("+dim_customers+");
let (name, column, direction) = a.parse_target().expect("target should be present");
assert_eq!(name, "dim_customers");
assert_eq!(column, None);
assert_eq!(direction, Direction::Both);
}
#[test]
fn a_dotted_target_splits_into_model_and_column() {
let a = args("dim_customers.customer_id");
let (name, column, direction) = a.parse_target().expect("target should be present");
assert_eq!(name, "dim_customers");
assert_eq!(column, Some("customer_id"));
assert_eq!(direction, Direction::Both);
}
#[test]
fn a_dotted_target_still_honors_plus_prefix_and_suffix() {
let a = args("+dim_customers.customer_id+");
let (name, column, direction) = a.parse_target().expect("target should be present");
assert_eq!(name, "dim_customers");
assert_eq!(column, Some("customer_id"));
assert_eq!(direction, Direction::Both);
}
}