diff --git a/.github/meta/merge-commit.txt b/.github/meta/merge-commit.txt new file mode 100644 index 0000000000..f2e765706b --- /dev/null +++ b/.github/meta/merge-commit.txt @@ -0,0 +1,24 @@ +Merge origin/main into feat/AI-7520-social-signup-authorize + +Resolve conflicts from upstream's TUI extraction (packages/opencode/src/cli/cmd/tui +→ packages/tui) and fork-features-as-plugins re-architecture, re-applying the +AI-7520 CLI onboarding onto the new structure: + +- dialog-provider: export/reorder PROVIDER_PRIORITY (Altimate LLM Gateway first), + brand the gateway option, add WARNLIST + DialogAltimateAuth, harden the OAuth + authorize/AutoMethod UX (try/catch → toast, inline green success + auto-close) +- dialog-model: keep the READY / NEEDS-SETUP restructure + Big Pickle, wired through + upstream's new DialogVariant flow; gateway leads the full list +- use-connected + home tips: fix undefined-cost mislabeling OpenCode as connected +- app.tsx: /connect (welcome picker), /auth (gateway), /logout (plugin command + + resetSetupComplete); gate the update-available dialog on onboarding readiness +- home.tsx: render the WelcomePanel in the home_logo slot +- provider-credentials plugin: add altimate.provider.logout +- providers.ts: Altimate LLM Gateway first in the login picker +- relocate altimate-onboarding + welcome-panel into packages/tui with relative imports + +Verified: `bun run typecheck` green in packages/tui and packages/opencode; the +onboarding/provider/model/app-lifecycle tests pass. Pre-existing sync*.test.tsx +failures come from origin/main's own merge (files untouched here). + +Co-Authored-By: Claude Opus 4.8 diff --git a/.gitignore b/.gitignore index 49fc9478af..54c7c0521e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ tsconfig.tsbuildinfo **/.github/meta/pr-body-*.md .bridge-merge-report.md /data/ + +# Rendered VHS demos — regenerable from .tape files, kept out of git +docs/media/*.gif diff --git a/docs/docs/reference/telemetry.md b/docs/docs/reference/telemetry.md index 0a65db1afb..e20e4dc3a7 100644 --- a/docs/docs/reference/telemetry.md +++ b/docs/docs/reference/telemetry.md @@ -47,8 +47,32 @@ We collect the following categories of events: | `schema_complexity` | Warehouse schema structural metrics from introspection — bucketed table, column, and schema counts plus average columns per table. No schema names or content. | | `validator_check` | A completion-gate validator ran on session end — validator name, `ok` boolean, step, retry count, `enforced` flag (false in shadow mode), and structured `details` (model counts, elapsed time, concurrency limit — no SQL or model content). Only emitted when `ALTIMATE_VALIDATORS_ENABLED=1` or `ALTIMATE_VALIDATORS_SHADOW=1`. See [Validators](../data-engineering/validators.md). | | `validator_retries_exhausted` | A session terminated with unresolved validator failures after exhausting the synthetic-retry budget — names of the failing validators (no failure body content). | - -Each event includes a timestamp, anonymous session ID, CLI version, and an anonymous machine ID (a random UUID stored in `~/.altimate/machine-id`, generated once and never tied to any personal information). +| `onboarding_started` | The first-run setup gate opened (fresh launch with no usable model). | +| `model_picker_shown` | The provider picker was displayed. `trigger` distinguishes the first run from `/connect`, from declining Big Pickle, and from the prompt gate. | +| `provider_selected` | A provider row was chosen (`altimate_gateway`, `anthropic`, `openai`, `google`, `big_pickle`, `search_all`). Recorded at the moment of choice, so a sign-in that is then cancelled still counts. | +| `big_pickle_confirm_shown` / `big_pickle_choice` | The Big Pickle interstitial was shown, and what the user decided (`accept`/`cancel`). | +| `gateway_device_code_issued` | The Altimate Gateway authorize URL was built and the browser open attempted. **Name note:** the flow is a browser loopback OAuth — there is no device code. The name follows the original event spec. | +| `gateway_auth_completed` / `gateway_auth_failed` | Gateway sign-in outcome. `reason` is `timeout`, `denied`, or `error` — never the underlying message, which can contain the instance name. An unrecognised callback state does not reject the pending attempt, so a CSRF mismatch surfaces as `timeout`. | +| `instance_connected` | Credentials received and saved. `time_to_connect_ms` runs from the start of the authorize call, so it includes the browser launch. No instance or tenant name is sent. | +| `onboarding_completed` | A model is ready and chat is live. | +| `scan_gate_shown` / `scan_gate_choice` | The "scan your environment?" gate appeared, and whether the user chose `scan` or `skip`. | +| `environment_scan_completed` | A `project_scan` finished — `has_dbt`, `has_warehouse`, `is_repo`, `connections_found`, and a bounded list of short `degraded` detection keys. No paths, hostnames, or connection details. Fires for every scan, including `/discover`, not just onboarding. | +| `sample_setup_completed` | The sample dbt project was materialised. `success`, `models`, `tables`, and `reused` — the tool is deliberately re-callable, so this is per invocation. The target path is never sent. | +| `activation_menu_shown` | The activation menu was (very likely) rendered. `variant` is `warehouse` or `no_data`. **Derived** — see the note below. | +| `activation_job_selected` / `first_job_completed` | Which activation job the user started and, where observable, finished. **Derived** — see the note below. | +| `first_prompt_sent` | The user's first typed message in an onboarding session. Slash commands are excluded, so the hidden `/onboard-connect` submission does not count. | +| `onboarding_abandoned` | The CLI exited during a first run without connecting. `last_stage` is the furthest point reached: `started`, `model_picker`, `provider_setup`, `big_pickle_confirm`, `gateway_auth`, or `connected`. Only emitted for a genuine first run — opening `/connect` as an existing user does not enter the funnel, and abandonment after setup completes is out of scope by definition. | + +### A note on the derived activation events + +`activation_menu_shown`, `activation_job_selected`, and `first_job_completed` are **inferred, not observed**. The activation menu is not a UI element: it is text the model writes from a prompt template, and the user picks a job by replying in free text. Nothing in the CLI can see either moment directly. + +They are therefore inferred from the closest deterministic signals — the menu from the command dispatch or the completed environment scan, the job from the first matching tool or skill invocation that follows. Treat the counts as **lower bounds**, and note two specific gaps: + +- The "something else" branch has no tool signature at all and is never counted. +- `first_job_completed` only fires for jobs with a real completion signal. Skill-driven jobs (downstream impact, SQL review, cost) load an instruction bundle and then do their work through other tools, so their completion is not observable and they are absent from this event rather than wrongly counted in it. + +Each event includes a timestamp, anonymous session ID, a per-launch correlation ID (`launch_id` — a random value regenerated every process start, not persisted and not derived from your machine or identity; it exists only to group events from the same run), CLI version, and an anonymous machine ID (a random UUID stored in `~/.altimate/machine-id`, generated once and never tied to any personal information). ## Delivery & Reliability diff --git a/packages/dbt-tools/test/fixture/.user.yml b/packages/dbt-tools/test/fixture/.user.yml new file mode 100644 index 0000000000..b4e9b5237a --- /dev/null +++ b/packages/dbt-tools/test/fixture/.user.yml @@ -0,0 +1 @@ +id: 03b8c7b7-4b26-4408-9876-88d05b7da4f5 diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/.gitignore b/packages/opencode/sample-projects/jaffle-shop-duckdb/.gitignore new file mode 100644 index 0000000000..32c4aaf794 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/.gitignore @@ -0,0 +1,12 @@ +# Build/run artifacts that must NOT be committed. `target/*` (not `target/`) +# so the re-include below actually works — git ignores the CONTENTS of target/ +# individually, then lets us un-ignore the one pre-compiled artifact we ship. +# Using `target/` here would exclude the directory as a whole and make the +# `!target/manifest.json` line inert (git will not descend into an ignored +# directory to re-include children). +target/* +!target/manifest.json + +dbt_packages/ +logs/ +.user.yml diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/README.md b/packages/opencode/sample-projects/jaffle-shop-duckdb/README.md new file mode 100644 index 0000000000..2db759346d --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/README.md @@ -0,0 +1,55 @@ +# Jaffle Shop — altimate-code starter sample + +Everything below runs against a local DuckDB file — no cloud warehouse, no +credentials, no network calls. + +## What's in here + +``` +dbt_project.yml dbt project config +profiles.yml DuckDB profile — path is project-relative +sample-manifest.json version metadata used by altimate-code to detect + stale copies on upgrade +models/ + staging/ + stg_customers.sql renames raw customer columns to snake_case + stg_orders.sql renames raw order columns + schema.yml column descriptions + unique/not_null tests + marts/ + customers.sql one row per customer, joins in order counts + orders.sql one row per order, joins in customer names + schema.yml column descriptions + tests + relationships +seeds/ + raw_customers.csv 3 rows of test data + raw_orders.csv 4 rows of test data +target/ + manifest.json PRE-COMPILED dbt manifest — ships with the sample so + altimate-code's static workflows (/discover, /review) + work without dbt-core / dbt-duckdb installed +``` + +## What to try (works with zero external tools) + +- `/discover stg_customers` — walk the DAG and see what depends on this model +- `/review models/marts/customers.sql` — run the reviewer against a mart model +- Open any `.sql` file and ask altimate-code to explain the transformation +- Ask altimate-code "what tests would you recommend for `orders`?" + +## What to try (needs `dbt-core` + `dbt-duckdb` installed) + +```bash +pip install dbt-duckdb +cd ~/altimate-sample-dbt # or wherever you materialized the sample +dbt seed # load the CSVs into DuckDB +dbt build # run models + tests +duckdb target/jaffle.duckdb -c 'select * from customers' +``` + +Once `dbt-duckdb` is on your `$PATH`, altimate-code detects it automatically +and the "run" workflows appear in `/help`. + +## Bringing your own project + +When you're ready to switch to your real dbt project, `cd` into it and run +altimate-code again. The scan will pick up your `dbt_project.yml` and offer +to connect its warehouse. diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/dbt_project.yml b/packages/opencode/sample-projects/jaffle-shop-duckdb/dbt_project.yml new file mode 100644 index 0000000000..3fe3033348 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/dbt_project.yml @@ -0,0 +1,16 @@ +name: "jaffle_shop" +version: "1.0.0" + +profile: "jaffle_shop" + +model-paths: ["models"] +seed-paths: ["seeds"] +target-path: "target" +clean-targets: ["target", "dbt_packages"] + +models: + jaffle_shop: + staging: + +materialized: view + marts: + +materialized: table diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/customers.sql b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/customers.sql new file mode 100644 index 0000000000..68721d0adf --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/customers.sql @@ -0,0 +1,9 @@ +select + c.customer_id, + c.first_name, + c.last_name, + count(o.order_id) as order_count, + coalesce(sum(o.amount), 0) as total_amount +from {{ ref('stg_customers') }} c +left join {{ ref('stg_orders') }} o on c.customer_id = o.customer_id +group by c.customer_id, c.first_name, c.last_name diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/orders.sql b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/orders.sql new file mode 100644 index 0000000000..8f226c50fb --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/orders.sql @@ -0,0 +1,8 @@ +select + o.order_id, + o.customer_id, + c.first_name || ' ' || c.last_name as customer_name, + o.order_date, + o.amount +from {{ ref('stg_orders') }} o +join {{ ref('stg_customers') }} c on o.customer_id = c.customer_id diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/schema.yml b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/schema.yml new file mode 100644 index 0000000000..e7317f22ec --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/marts/schema.yml @@ -0,0 +1,32 @@ +version: 2 + +models: + - name: customers + description: One row per customer with total order count and revenue. + columns: + - name: customer_id + description: Primary key. + data_tests: + - unique + - not_null + - name: order_count + description: Number of orders placed by the customer (0 when none). + data_tests: + - not_null + - name: total_amount + description: Sum of all order amounts (0 when none). + data_tests: + - not_null + + - name: orders + description: One row per order with a joined customer name. + columns: + - name: order_id + description: Primary key. + data_tests: + - unique + - not_null + - name: customer_id + description: Foreign key to `stg_customers`. + data_tests: + - not_null diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/schema.yml b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/schema.yml new file mode 100644 index 0000000000..64af1c0537 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/schema.yml @@ -0,0 +1,27 @@ +version: 2 + +models: + - name: stg_customers + description: Renamed customer columns from the raw seed. + columns: + - name: customer_id + description: Primary key of the customer. + data_tests: + - unique + - not_null + + - name: stg_orders + description: Renamed order columns from the raw seed. + columns: + - name: order_id + description: Primary key of the order. + data_tests: + - unique + - not_null + - name: customer_id + description: Foreign key to `stg_customers`. + data_tests: + - not_null + - relationships: + to: ref('stg_customers') + field: customer_id diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/stg_customers.sql b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/stg_customers.sql new file mode 100644 index 0000000000..7b25b8d738 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/stg_customers.sql @@ -0,0 +1,5 @@ +select + id as customer_id, + first_name, + last_name +from {{ ref('raw_customers') }} diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/stg_orders.sql b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/stg_orders.sql new file mode 100644 index 0000000000..ab4239c419 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/models/staging/stg_orders.sql @@ -0,0 +1,6 @@ +select + id as order_id, + customer_id, + order_date, + amount +from {{ ref('raw_orders') }} diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/profiles.yml b/packages/opencode/sample-projects/jaffle-shop-duckdb/profiles.yml new file mode 100644 index 0000000000..429824f440 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/profiles.yml @@ -0,0 +1,14 @@ +# DuckDB profile — everything runs locally against a single file at +# `target/jaffle.duckdb` (created on first `dbt build`). No cloud credentials. +# `path:` is unqualified, so dbt-duckdb resolves it against the PROCESS +# working directory at build time — NOT the project directory. Run +# `dbt build` from the materialized sample dir (`cd `) and +# the database lands at `/target/jaffle.duckdb`. +# Run it from anywhere else and dbt writes to `$PWD/target/jaffle.duckdb`. +jaffle_shop: + target: dev + outputs: + dev: + type: duckdb + path: "target/jaffle.duckdb" + threads: 1 diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/sample-manifest.json b/packages/opencode/sample-projects/jaffle-shop-duckdb/sample-manifest.json new file mode 100644 index 0000000000..267cb36ce7 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/sample-manifest.json @@ -0,0 +1,16 @@ +{ + "$comment": "Metadata about this sample project. Distinct from dbt's target/manifest.json — this is our own version stamp for conflict detection on the materialized copy at ~/altimate-sample-dbt/. If you edit any source file below, run ./regenerate.sh and commit the refreshed target/manifest.json alongside your change.", + "name": "jaffle-shop-duckdb", + "version": "1.0.0", + "kind": "altimate-starter-sample", + "source": "packages/opencode/sample-projects/jaffle-shop-duckdb", + "requires": { + "dbt-core": ">=1.7 <2.0", + "dbt-duckdb": ">=1.7 <2.0" + }, + "notes": [ + "Renamed profile from the original test fixture: `test_jaffle_shop` → `jaffle_shop`.", + "DuckDB target file resolves project-relative at `target/jaffle.duckdb`; no host paths bake into the profile.", + "target/manifest.json ships pre-compiled so static workflows (/discover, /review) work without dbt installed." + ] +} diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/seeds/raw_customers.csv b/packages/opencode/sample-projects/jaffle-shop-duckdb/seeds/raw_customers.csv new file mode 100644 index 0000000000..477f489aa4 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/seeds/raw_customers.csv @@ -0,0 +1,4 @@ +id,first_name,last_name +1,Alice,Smith +2,Bob,Jones +3,Carol,White diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/seeds/raw_orders.csv b/packages/opencode/sample-projects/jaffle-shop-duckdb/seeds/raw_orders.csv new file mode 100644 index 0000000000..df39cf577c --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/seeds/raw_orders.csv @@ -0,0 +1,5 @@ +id,customer_id,order_date,amount +1,1,2024-01-15,100 +2,1,2024-02-20,200 +3,2,2024-01-10,150 +4,3,2024-03-05,300 diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/target/manifest.json b/packages/opencode/sample-projects/jaffle-shop-duckdb/target/manifest.json new file mode 100644 index 0000000000..7f05bb9776 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/target/manifest.json @@ -0,0 +1,16839 @@ +{ + "child_map": { + "model.jaffle_shop.customers": [ + "test.jaffle_shop.not_null_customers_customer_id.5c9bf9911d", + "test.jaffle_shop.not_null_customers_order_count.f60dfe3b39", + "test.jaffle_shop.not_null_customers_total_amount.83faa92c8a", + "test.jaffle_shop.unique_customers_customer_id.c5af1ff4b1" + ], + "model.jaffle_shop.orders": [ + "test.jaffle_shop.not_null_orders_customer_id.c5f02694af", + "test.jaffle_shop.not_null_orders_order_id.cf6c17daed", + "test.jaffle_shop.unique_orders_order_id.fed79b3a6e" + ], + "model.jaffle_shop.stg_customers": [ + "model.jaffle_shop.customers", + "model.jaffle_shop.orders", + "test.jaffle_shop.not_null_stg_customers_customer_id.e2cfb1f9aa", + "test.jaffle_shop.relationships_stg_orders_customer_id__customer_id__ref_stg_customers_.430bf21500", + "test.jaffle_shop.unique_stg_customers_customer_id.c7614daada" + ], + "model.jaffle_shop.stg_orders": [ + "model.jaffle_shop.customers", + "model.jaffle_shop.orders", + "test.jaffle_shop.not_null_stg_orders_customer_id.af79d5e4b5", + "test.jaffle_shop.not_null_stg_orders_order_id.81cfe2fe64", + "test.jaffle_shop.relationships_stg_orders_customer_id__customer_id__ref_stg_customers_.430bf21500", + "test.jaffle_shop.unique_stg_orders_order_id.e3b841c71a" + ], + "seed.jaffle_shop.raw_customers": [ + "model.jaffle_shop.stg_customers" + ], + "seed.jaffle_shop.raw_orders": [ + "model.jaffle_shop.stg_orders" + ], + "test.jaffle_shop.not_null_customers_customer_id.5c9bf9911d": [], + "test.jaffle_shop.not_null_customers_order_count.f60dfe3b39": [], + "test.jaffle_shop.not_null_customers_total_amount.83faa92c8a": [], + "test.jaffle_shop.not_null_orders_customer_id.c5f02694af": [], + "test.jaffle_shop.not_null_orders_order_id.cf6c17daed": [], + "test.jaffle_shop.not_null_stg_customers_customer_id.e2cfb1f9aa": [], + "test.jaffle_shop.not_null_stg_orders_customer_id.af79d5e4b5": [], + "test.jaffle_shop.not_null_stg_orders_order_id.81cfe2fe64": [], + "test.jaffle_shop.relationships_stg_orders_customer_id__customer_id__ref_stg_customers_.430bf21500": [], + "test.jaffle_shop.unique_customers_customer_id.c5af1ff4b1": [], + "test.jaffle_shop.unique_orders_order_id.fed79b3a6e": [], + "test.jaffle_shop.unique_stg_customers_customer_id.c7614daada": [], + "test.jaffle_shop.unique_stg_orders_order_id.e3b841c71a": [] + }, + "disabled": {}, + "docs": { + "doc.dbt.__overview__": { + "block_contents": "### Welcome!\n\nWelcome to the auto-generated documentation for your dbt project!\n\n### Navigation\n\nYou can use the `Project` and `Database` navigation tabs on the left side of the window to explore the models\nin your project.\n\n#### Project Tab\nThe `Project` tab mirrors the directory structure of your dbt project. In this tab, you can see all of the\nmodels defined in your dbt project, as well as models imported from dbt packages.\n\n#### Database Tab\nThe `Database` tab also exposes your models, but in a format that looks more like a database explorer. This view\nshows relations (tables and views) grouped into database schemas. Note that ephemeral models are _not_ shown\nin this interface, as they do not exist in the database.\n\n### Graph Exploration\nYou can click the blue icon on the bottom-right corner of the page to view the lineage graph of your models.\n\nOn model pages, you'll see the immediate parents and children of the model you're exploring. By clicking the `Expand`\nbutton at the top-right of this lineage pane, you'll be able to see all of the models that are used to build,\nor are built from, the model you're exploring.\n\nOnce expanded, you'll be able to use the `--select` and `--exclude` model selection syntax to filter the\nmodels in the graph. For more information on model selection, check out the [dbt docs](https://docs.getdbt.com/docs/model-selection-syntax).\n\nNote that you can also right-click on models to interactively filter and explore the graph.\n\n---\n\n### More information\n\n- [What is dbt](https://docs.getdbt.com/docs/introduction)?\n- Read the [dbt viewpoint](https://docs.getdbt.com/docs/viewpoint)\n- [Installation](https://docs.getdbt.com/docs/installation)\n- Join the [dbt Community](https://www.getdbt.com/community/) for questions and discussion", + "name": "__overview__", + "original_file_path": "docs/overview.md", + "package_name": "dbt", + "path": "overview.md", + "resource_type": "doc", + "unique_id": "doc.dbt.__overview__" + } + }, + "exposures": {}, + "functions": {}, + "group_map": {}, + "groups": {}, + "macros": { + "macro.dbt._split_part_negative": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro _split_part_negative(string_text, delimiter_text, part_number) %}\n\n split_part(\n {{ string_text }},\n {{ delimiter_text }},\n length({{ string_text }})\n - length(\n replace({{ string_text }}, {{ delimiter_text }}, '')\n ) + 2 + {{ part_number }}\n )\n\n{% endmacro %}", + "meta": {}, + "name": "_split_part_negative", + "original_file_path": "macros/utils/split_part.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/split_part.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt._split_part_negative" + }, + "macro.dbt.after_commit": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_hook_config" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro after_commit(sql) %}\n {{ make_hook_config(sql, inside_transaction=False) }}\n{% endmacro %}", + "meta": {}, + "name": "after_commit", + "original_file_path": "macros/materializations/hooks.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/hooks.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.after_commit" + }, + "macro.dbt.alter_column_comment": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__alter_column_comment" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro alter_column_comment(relation, column_dict) -%}\n {{ return(adapter.dispatch('alter_column_comment', 'dbt')(relation, column_dict)) }}\n{% endmacro %}", + "meta": {}, + "name": "alter_column_comment", + "original_file_path": "macros/adapters/persist_docs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.alter_column_comment" + }, + "macro.dbt.alter_column_type": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__alter_column_type" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro alter_column_type(relation, column_name, new_column_type) -%}\n {{ return(adapter.dispatch('alter_column_type', 'dbt')(relation, column_name, new_column_type)) }}\n{% endmacro %}", + "meta": {}, + "name": "alter_column_type", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.alter_column_type" + }, + "macro.dbt.alter_relation_add_remove_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__alter_relation_add_remove_columns" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro alter_relation_add_remove_columns(relation, add_columns = none, remove_columns = none) -%}\n {{ return(adapter.dispatch('alter_relation_add_remove_columns', 'dbt')(relation, add_columns, remove_columns)) }}\n{% endmacro %}", + "meta": {}, + "name": "alter_relation_add_remove_columns", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.alter_relation_add_remove_columns" + }, + "macro.dbt.alter_relation_comment": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__alter_relation_comment" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro alter_relation_comment(relation, relation_comment) -%}\n {{ return(adapter.dispatch('alter_relation_comment', 'dbt')(relation, relation_comment)) }}\n{% endmacro %}", + "meta": {}, + "name": "alter_relation_comment", + "original_file_path": "macros/adapters/persist_docs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.alter_relation_comment" + }, + "macro.dbt.any_value": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__any_value" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro any_value(expression) -%}\n {{ return(adapter.dispatch('any_value', 'dbt') (expression)) }}\n{% endmacro %}", + "meta": {}, + "name": "any_value", + "original_file_path": "macros/utils/any_value.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/any_value.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.any_value" + }, + "macro.dbt.apply_grants": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__apply_grants" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro apply_grants(relation, grant_config, should_revoke) %}\n {{ return(adapter.dispatch(\"apply_grants\", \"dbt\")(relation, grant_config, should_revoke)) }}\n{% endmacro %}", + "meta": {}, + "name": "apply_grants", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.apply_grants" + }, + "macro.dbt.array_append": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__array_append" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro array_append(array, new_element) -%}\n {{ return(adapter.dispatch('array_append', 'dbt')(array, new_element)) }}\n{%- endmacro %}", + "meta": {}, + "name": "array_append", + "original_file_path": "macros/utils/array_append.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/array_append.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.array_append" + }, + "macro.dbt.array_concat": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__array_concat" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro array_concat(array_1, array_2) -%}\n {{ return(adapter.dispatch('array_concat', 'dbt')(array_1, array_2)) }}\n{%- endmacro %}", + "meta": {}, + "name": "array_concat", + "original_file_path": "macros/utils/array_concat.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/array_concat.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.array_concat" + }, + "macro.dbt.array_construct": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__array_construct" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro array_construct(inputs=[], data_type=api.Column.translate_type('integer')) -%}\n {{ return(adapter.dispatch('array_construct', 'dbt')(inputs, data_type)) }}\n{%- endmacro %}", + "meta": {}, + "name": "array_construct", + "original_file_path": "macros/utils/array_construct.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/array_construct.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.array_construct" + }, + "macro.dbt.assert_columns_equivalent": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_column_schema_from_query", + "macro.dbt.get_empty_schema_sql", + "macro.dbt.format_columns" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro assert_columns_equivalent(sql) %}\n\n {#-- First ensure the user has defined 'columns' in yaml specification --#}\n {%- set user_defined_columns = model['columns'] -%}\n {%- if not user_defined_columns -%}\n {{ exceptions.raise_contract_error([], []) }}\n {%- endif -%}\n\n {#-- Obtain the column schema provided by sql file. #}\n {%- set sql_file_provided_columns = get_column_schema_from_query(sql, config.get('sql_header', none)) -%}\n {#--Obtain the column schema provided by the schema file by generating an 'empty schema' query from the model's columns. #}\n {%- set schema_file_provided_columns = get_column_schema_from_query(get_empty_schema_sql(user_defined_columns)) -%}\n\n {#-- create dictionaries with name and formatted data type and strings for exception #}\n {%- set sql_columns = format_columns(sql_file_provided_columns) -%}\n {%- set yaml_columns = format_columns(schema_file_provided_columns) -%}\n\n {%- if sql_columns|length != yaml_columns|length -%}\n {%- do exceptions.raise_contract_error(yaml_columns, sql_columns) -%}\n {%- endif -%}\n\n {%- for sql_col in sql_columns -%}\n {%- set yaml_col = [] -%}\n {%- for this_col in yaml_columns -%}\n {%- if this_col['name'] == sql_col['name'] -%}\n {%- do yaml_col.append(this_col) -%}\n {%- break -%}\n {%- endif -%}\n {%- endfor -%}\n {%- if not yaml_col -%}\n {#-- Column with name not found in yaml #}\n {%- do exceptions.raise_contract_error(yaml_columns, sql_columns) -%}\n {%- endif -%}\n {%- if sql_col['formatted'] != yaml_col[0]['formatted'] -%}\n {#-- Column data types don't match #}\n {%- do exceptions.raise_contract_error(yaml_columns, sql_columns) -%}\n {%- endif -%}\n {%- endfor -%}\n\n{% endmacro %}", + "meta": {}, + "name": "assert_columns_equivalent", + "original_file_path": "macros/relations/column/columns_spec_ddl.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/column/columns_spec_ddl.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.assert_columns_equivalent" + }, + "macro.dbt.before_begin": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_hook_config" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro before_begin(sql) %}\n {{ make_hook_config(sql, inside_transaction=False) }}\n{% endmacro %}", + "meta": {}, + "name": "before_begin", + "original_file_path": "macros/materializations/hooks.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/hooks.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.before_begin" + }, + "macro.dbt.bool_or": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__bool_or" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro bool_or(expression) -%}\n {{ return(adapter.dispatch('bool_or', 'dbt') (expression)) }}\n{% endmacro %}", + "meta": {}, + "name": "bool_or", + "original_file_path": "macros/utils/bool_or.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/bool_or.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.bool_or" + }, + "macro.dbt.build_config_dict": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro build_config_dict(model) %}\n {%- set config_dict = {} -%}\n {% set config_dbt_used = zip(model.config.config_keys_used, model.config.config_keys_defaults) | list %}\n {%- for key, default in config_dbt_used -%}\n {# weird type testing with enum, would be much easier to write this logic in Python! #}\n {%- if key == \"language\" -%}\n {%- set value = \"python\" -%}\n {%- endif -%}\n {%- set value = model.config.get(key, default) -%}\n {%- do config_dict.update({key: value}) -%}\n {%- endfor -%}\n {# Handle dbt.config.meta_get() calls - use separate dict to avoid overwriting native configs #}\n {%- set meta_dict = {} -%}\n {%- if model.config.meta_keys_used -%}\n {% set meta_dbt_used = zip(model.config.meta_keys_used, model.config.meta_keys_defaults) | list %}\n {%- for key, default in meta_dbt_used -%}\n {%- if model.config.meta and key in model.config.meta -%}\n {%- set value = model.config.meta[key] -%}\n {%- else -%}\n {%- set value = default -%}\n {%- endif -%}\n {%- do meta_dict.update({key: value}) -%}\n {%- endfor -%}\n {%- endif -%}\nconfig_dict = {{ config_dict }}\nmeta_dict = {{ meta_dict }}\n{% endmacro %}", + "meta": {}, + "name": "build_config_dict", + "original_file_path": "macros/python_model/python.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/python_model/python.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.build_config_dict" + }, + "macro.dbt.build_ref_function": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.resolve_model_name" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro build_ref_function(model) %}\n\n {%- set ref_dict = {} -%}\n {%- for _ref in model.refs -%}\n {% set _ref_args = [_ref.get('package'), _ref['name']] if _ref.get('package') else [_ref['name'],] %}\n {%- set resolved = ref(*_ref_args, v=_ref.get('version')) -%}\n\n {#\n We want to get the string of the returned relation by calling .render() in order to skip sample/empty\n mode rendering logic. However, people override the default ref macro, and often return a string instead\n of a relation (like the ref macro does by default). Thus, to make sure we dont blow things up, we have\n to ensure the resolved relation has a .render() method.\n #}\n {%- if resolved.render is defined and resolved.render is callable -%}\n {%- set resolved = resolved.render() -%}\n {%- endif -%}\n\n {%- if _ref.get('version') -%}\n {% do _ref_args.extend([\"v\" ~ _ref['version']]) %}\n {%- endif -%}\n {%- do ref_dict.update({_ref_args | join('.'): resolve_model_name(resolved)}) -%}\n {%- endfor -%}\n\ndef ref(*args, **kwargs):\n refs = {{ ref_dict | tojson }}\n key = '.'.join(args)\n version = kwargs.get(\"v\") or kwargs.get(\"version\")\n if version:\n key += f\".v{version}\"\n dbt_load_df_function = kwargs.get(\"dbt_load_df_function\")\n return dbt_load_df_function(refs[key])\n\n{% endmacro %}", + "meta": {}, + "name": "build_ref_function", + "original_file_path": "macros/python_model/python.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/python_model/python.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.build_ref_function" + }, + "macro.dbt.build_snapshot_staging_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_temp_relation", + "macro.dbt.snapshot_staging_table", + "macro.dbt.statement", + "macro.dbt.create_table_as" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro build_snapshot_staging_table(strategy, sql, target_relation) %}\n {% set temp_relation = make_temp_relation(target_relation) %}\n\n {% set select = snapshot_staging_table(strategy, sql, target_relation) %}\n\n {% call statement('build_snapshot_staging_relation') %}\n {{ create_table_as(True, temp_relation, select) }}\n {% endcall %}\n\n {% do return(temp_relation) %}\n{% endmacro %}", + "meta": {}, + "name": "build_snapshot_staging_table", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.build_snapshot_staging_table" + }, + "macro.dbt.build_snapshot_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__build_snapshot_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro build_snapshot_table(strategy, sql) -%}\n {{ adapter.dispatch('build_snapshot_table', 'dbt')(strategy, sql) }}\n{% endmacro %}", + "meta": {}, + "name": "build_snapshot_table", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.build_snapshot_table" + }, + "macro.dbt.build_source_function": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.resolve_model_name" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro build_source_function(model) %}\n\n {%- set source_dict = {} -%}\n {%- for _source in model.sources -%}\n {%- set resolved = source(*_source) -%}\n {%- do source_dict.update({_source | join('.'): resolve_model_name(resolved)}) -%}\n {%- endfor -%}\n\ndef source(*args, dbt_load_df_function):\n sources = {{ source_dict | tojson }}\n key = '.'.join(args)\n return dbt_load_df_function(sources[key])\n\n{% endmacro %}", + "meta": {}, + "name": "build_source_function", + "original_file_path": "macros/python_model/python.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/python_model/python.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.build_source_function" + }, + "macro.dbt.call_dcl_statements": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__call_dcl_statements" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro call_dcl_statements(dcl_statement_list) %}\n {{ return(adapter.dispatch(\"call_dcl_statements\", \"dbt\")(dcl_statement_list)) }}\n{% endmacro %}", + "meta": {}, + "name": "call_dcl_statements", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.call_dcl_statements" + }, + "macro.dbt.can_clone_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__can_clone_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro can_clone_table() %}\n {{ return(adapter.dispatch('can_clone_table', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "can_clone_table", + "original_file_path": "macros/materializations/models/clone/can_clone_table.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/clone/can_clone_table.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.can_clone_table" + }, + "macro.dbt.cast": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__cast" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro cast(field, type) %}\n {{ return(adapter.dispatch('cast', 'dbt') (field, type)) }}\n{% endmacro %}", + "meta": {}, + "name": "cast", + "original_file_path": "macros/utils/cast.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/cast.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.cast" + }, + "macro.dbt.cast_bool_to_text": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__cast_bool_to_text" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro cast_bool_to_text(field) %}\n {{ adapter.dispatch('cast_bool_to_text', 'dbt') (field) }}\n{% endmacro %}", + "meta": {}, + "name": "cast_bool_to_text", + "original_file_path": "macros/utils/cast_bool_to_text.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/cast_bool_to_text.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.cast_bool_to_text" + }, + "macro.dbt.check_for_schema_changes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__check_for_schema_changes" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro check_for_schema_changes(source_relation, target_relation) %}\n {{ return(adapter.dispatch('check_for_schema_changes', 'dbt')(source_relation, target_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "check_for_schema_changes", + "original_file_path": "macros/materializations/models/incremental/on_schema_change.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/on_schema_change.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.check_for_schema_changes" + }, + "macro.dbt.check_schema_exists": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__check_schema_exists" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro check_schema_exists(information_schema, schema) -%}\n {{ return(adapter.dispatch('check_schema_exists', 'dbt')(information_schema, schema)) }}\n{% endmacro %}", + "meta": {}, + "name": "check_schema_exists", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.check_schema_exists" + }, + "macro.dbt.check_time_data_types": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_updated_at_column_data_type", + "macro.dbt.get_snapshot_get_time_data_type" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro check_time_data_types(sql) %}\n {% set dbt_updated_at_data_type = get_updated_at_column_data_type(sql) %}\n {% set snapshot_get_time_data_type = get_snapshot_get_time_data_type() %}\n {% if snapshot_get_time_data_type is not none and dbt_updated_at_data_type is not none and snapshot_get_time_data_type != dbt_updated_at_data_type %}\n {% if exceptions.warn_snapshot_timestamp_data_types %}\n {{ exceptions.warn_snapshot_timestamp_data_types(snapshot_get_time_data_type, dbt_updated_at_data_type) }}\n {% endif %}\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "check_time_data_types", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.check_time_data_types" + }, + "macro.dbt.collect_freshness": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__collect_freshness" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro collect_freshness(source, loaded_at_field, filter) %}\n {{ return(adapter.dispatch('collect_freshness', 'dbt')(source, loaded_at_field, filter))}}\n{% endmacro %}", + "meta": {}, + "name": "collect_freshness", + "original_file_path": "macros/adapters/freshness.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/freshness.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.collect_freshness" + }, + "macro.dbt.collect_freshness_custom_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__collect_freshness_custom_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro collect_freshness_custom_sql(source, loaded_at_query) %}\n {{ return(adapter.dispatch('collect_freshness_custom_sql', 'dbt')(source, loaded_at_query))}}\n{% endmacro %}", + "meta": {}, + "name": "collect_freshness_custom_sql", + "original_file_path": "macros/adapters/freshness.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/freshness.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.collect_freshness_custom_sql" + }, + "macro.dbt.concat": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__concat" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro concat(fields) -%}\n {{ return(adapter.dispatch('concat', 'dbt')(fields)) }}\n{%- endmacro %}", + "meta": {}, + "name": "concat", + "original_file_path": "macros/utils/concat.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/concat.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.concat" + }, + "macro.dbt.convert_datetime": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro convert_datetime(date_str, date_fmt) %}\n\n {% set error_msg -%}\n The provided partition date '{{ date_str }}' does not match the expected format '{{ date_fmt }}'\n {%- endset %}\n\n {% set res = try_or_compiler_error(error_msg, modules.datetime.datetime.strptime, date_str.strip(), date_fmt) %}\n {{ return(res) }}\n\n{% endmacro %}", + "meta": {}, + "name": "convert_datetime", + "original_file_path": "macros/etc/datetime.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/etc/datetime.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.convert_datetime" + }, + "macro.dbt.copy_grants": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__copy_grants" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro copy_grants() %}\n {{ return(adapter.dispatch('copy_grants', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "copy_grants", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.copy_grants" + }, + "macro.dbt.create_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__create_columns" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro create_columns(relation, columns) %}\n {{ adapter.dispatch('create_columns', 'dbt')(relation, columns) }}\n{% endmacro %}", + "meta": {}, + "name": "create_columns", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.create_columns" + }, + "macro.dbt.create_csv_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__create_csv_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro create_csv_table(model, agate_table) -%}\n {{ adapter.dispatch('create_csv_table', 'dbt')(model, agate_table) }}\n{%- endmacro %}", + "meta": {}, + "name": "create_csv_table", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.create_csv_table" + }, + "macro.dbt.create_indexes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__create_indexes" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro create_indexes(relation) -%}\n {{ adapter.dispatch('create_indexes', 'dbt')(relation) }}\n{%- endmacro %}", + "meta": {}, + "name": "create_indexes", + "original_file_path": "macros/adapters/indexes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/indexes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.create_indexes" + }, + "macro.dbt.create_or_replace_clone": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__create_or_replace_clone" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro create_or_replace_clone(this_relation, defer_relation) %}\n {{ return(adapter.dispatch('create_or_replace_clone', 'dbt')(this_relation, defer_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "create_or_replace_clone", + "original_file_path": "macros/materializations/models/clone/create_or_replace_clone.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/clone/create_or_replace_clone.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.create_or_replace_clone" + }, + "macro.dbt.create_or_replace_view": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.run_hooks", + "macro.dbt.handle_existing_table", + "macro.dbt.should_full_refresh", + "macro.dbt.statement", + "macro.dbt.get_create_view_as_sql", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro create_or_replace_view() %}\n {%- set identifier = model['alias'] -%}\n\n {%- set old_relation = adapter.get_relation(database=database, schema=schema, identifier=identifier) -%}\n {%- set exists_as_view = (old_relation is not none and old_relation.is_view) -%}\n\n {%- set target_relation = api.Relation.create(\n identifier=identifier, schema=schema, database=database,\n type='view') -%}\n {% set grant_config = config.get('grants') %}\n\n {{ run_hooks(pre_hooks) }}\n\n -- If there's a table with the same name and we weren't told to full refresh,\n -- that's an error. If we were told to full refresh, drop it. This behavior differs\n -- for Snowflake and BigQuery, so multiple dispatch is used.\n {%- if old_relation is not none and old_relation.is_table -%}\n {{ handle_existing_table(should_full_refresh(), old_relation) }}\n {%- endif -%}\n\n -- build model\n {% call statement('main') -%}\n {{ get_create_view_as_sql(target_relation, sql) }}\n {%- endcall %}\n\n {% set should_revoke = should_revoke(exists_as_view, full_refresh_mode=True) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {{ run_hooks(post_hooks) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{% endmacro %}", + "meta": {}, + "name": "create_or_replace_view", + "original_file_path": "macros/relations/view/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.create_or_replace_view" + }, + "macro.dbt.create_schema": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__create_schema" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro create_schema(relation) -%}\n {{ adapter.dispatch('create_schema', 'dbt')(relation) }}\n{% endmacro %}", + "meta": {}, + "name": "create_schema", + "original_file_path": "macros/adapters/schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.create_schema" + }, + "macro.dbt.create_table_as": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__create_table_as" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro create_table_as(temporary, relation, compiled_code, language='sql') -%}\n {# backward compatibility for create_table_as that does not support language #}\n {% if language == \"sql\" %}\n {{ adapter.dispatch('create_table_as', 'dbt')(temporary, relation, compiled_code)}}\n {% else %}\n {{ adapter.dispatch('create_table_as', 'dbt')(temporary, relation, compiled_code, language) }}\n {% endif %}\n\n{%- endmacro %}", + "meta": {}, + "name": "create_table_as", + "original_file_path": "macros/relations/table/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.create_table_as" + }, + "macro.dbt.create_view_as": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__create_view_as" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro create_view_as(relation, sql) -%}\n {{ adapter.dispatch('create_view_as', 'dbt')(relation, sql) }}\n{%- endmacro %}", + "meta": {}, + "name": "create_view_as", + "original_file_path": "macros/relations/view/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.create_view_as" + }, + "macro.dbt.current_timestamp": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__current_timestamp" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro current_timestamp() -%}\n {{ adapter.dispatch('current_timestamp', 'dbt')() }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "current_timestamp", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.current_timestamp" + }, + "macro.dbt.current_timestamp_backcompat": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__current_timestamp_backcompat" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro current_timestamp_backcompat() %}\n {{ return(adapter.dispatch('current_timestamp_backcompat', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "current_timestamp_backcompat", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.current_timestamp_backcompat" + }, + "macro.dbt.current_timestamp_in_utc_backcompat": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__current_timestamp_in_utc_backcompat" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro current_timestamp_in_utc_backcompat() %}\n {{ return(adapter.dispatch('current_timestamp_in_utc_backcompat', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "current_timestamp_in_utc_backcompat", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.current_timestamp_in_utc_backcompat" + }, + "macro.dbt.date": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__date" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro date(year, month, day) %}\n {{ return(adapter.dispatch('date', 'dbt') (year, month, day)) }}\n{% endmacro %}", + "meta": {}, + "name": "date", + "original_file_path": "macros/utils/date.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/date.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.date" + }, + "macro.dbt.date_spine": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__date_spine" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro date_spine(datepart, start_date, end_date) %}\n {{ return(adapter.dispatch('date_spine', 'dbt')(datepart, start_date, end_date)) }}\n{%- endmacro %}", + "meta": {}, + "name": "date_spine", + "original_file_path": "macros/utils/date_spine.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/date_spine.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.date_spine" + }, + "macro.dbt.date_trunc": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__date_trunc" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro date_trunc(datepart, date) -%}\n {{ return(adapter.dispatch('date_trunc', 'dbt') (datepart, date)) }}\n{%- endmacro %}", + "meta": {}, + "name": "date_trunc", + "original_file_path": "macros/utils/date_trunc.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/date_trunc.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.date_trunc" + }, + "macro.dbt.dateadd": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__dateadd" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro dateadd(datepart, interval, from_date_or_timestamp) %}\n {{ return(adapter.dispatch('dateadd', 'dbt')(datepart, interval, from_date_or_timestamp)) }}\n{% endmacro %}", + "meta": {}, + "name": "dateadd", + "original_file_path": "macros/utils/dateadd.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/dateadd.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.dateadd" + }, + "macro.dbt.datediff": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__datediff" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro datediff(first_date, second_date, datepart) %}\n {{ return(adapter.dispatch('datediff', 'dbt')(first_date, second_date, datepart)) }}\n{% endmacro %}", + "meta": {}, + "name": "datediff", + "original_file_path": "macros/utils/datediff.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/datediff.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.datediff" + }, + "macro.dbt.dates_in_range": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.convert_datetime" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro dates_in_range(start_date_str, end_date_str=none, in_fmt=\"%Y%m%d\", out_fmt=\"%Y%m%d\") %}\n {% set end_date_str = start_date_str if end_date_str is none else end_date_str %}\n\n {% set start_date = convert_datetime(start_date_str, in_fmt) %}\n {% set end_date = convert_datetime(end_date_str, in_fmt) %}\n\n {% set day_count = (end_date - start_date).days %}\n {% if day_count < 0 %}\n {% set msg -%}\n Partition start date is after the end date ({{ start_date }}, {{ end_date }})\n {%- endset %}\n\n {{ exceptions.raise_compiler_error(msg, model) }}\n {% endif %}\n\n {% set date_list = [] %}\n {% for i in range(0, day_count + 1) %}\n {% set the_date = (modules.datetime.timedelta(days=i) + start_date) %}\n {% if not out_fmt %}\n {% set _ = date_list.append(the_date) %}\n {% else %}\n {% set _ = date_list.append(the_date.strftime(out_fmt)) %}\n {% endif %}\n {% endfor %}\n\n {{ return(date_list) }}\n{% endmacro %}", + "meta": {}, + "name": "dates_in_range", + "original_file_path": "macros/etc/datetime.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/etc/datetime.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.dates_in_range" + }, + "macro.dbt.default__alter_column_comment": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__alter_column_comment(relation, column_dict) -%}\n {{ exceptions.raise_not_implemented(\n 'alter_column_comment macro not implemented for adapter '+adapter.type()) }}\n{% endmacro %}", + "meta": {}, + "name": "default__alter_column_comment", + "original_file_path": "macros/adapters/persist_docs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__alter_column_comment" + }, + "macro.dbt.default__alter_column_type": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__alter_column_type(relation, column_name, new_column_type) -%}\n {#\n 1. Create a new column (w/ temp name and correct type)\n 2. Copy data over to it\n 3. Drop the existing column (cascade!)\n 4. Rename the new column to existing column\n #}\n {%- set tmp_column = column_name + \"__dbt_alter\" -%}\n\n {% call statement('alter_column_type') %}\n alter table {{ relation.render() }} add column {{ adapter.quote(tmp_column) }} {{ new_column_type }};\n update {{ relation.render() }} set {{ adapter.quote(tmp_column) }} = {{ adapter.quote(column_name) }};\n alter table {{ relation.render() }} drop column {{ adapter.quote(column_name) }} cascade;\n alter table {{ relation.render() }} rename column {{ adapter.quote(tmp_column) }} to {{ adapter.quote(column_name) }}\n {% endcall %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__alter_column_type", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__alter_column_type" + }, + "macro.dbt.default__alter_relation_add_remove_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.run_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__alter_relation_add_remove_columns(relation, add_columns, remove_columns) %}\n\n {% if add_columns is none %}\n {% set add_columns = [] %}\n {% endif %}\n {% if remove_columns is none %}\n {% set remove_columns = [] %}\n {% endif %}\n\n {% set sql -%}\n\n alter {{ relation.type }} {{ relation.render() }}\n\n {% for column in add_columns %}\n add column {{ column.quoted }} {{ column.expanded_data_type }}{{ ',' if not loop.last }}\n {% endfor %}{{ ',' if add_columns and remove_columns }}\n\n {% for column in remove_columns %}\n drop column {{ column.quoted }}{{ ',' if not loop.last }}\n {% endfor %}\n\n {%- endset -%}\n\n {% do run_query(sql) %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__alter_relation_add_remove_columns", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__alter_relation_add_remove_columns" + }, + "macro.dbt.default__alter_relation_comment": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__alter_relation_comment(relation, relation_comment) -%}\n {{ exceptions.raise_not_implemented(\n 'alter_relation_comment macro not implemented for adapter '+adapter.type()) }}\n{% endmacro %}", + "meta": {}, + "name": "default__alter_relation_comment", + "original_file_path": "macros/adapters/persist_docs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__alter_relation_comment" + }, + "macro.dbt.default__any_value": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__any_value(expression) -%}\n\n any_value({{ expression }})\n\n{%- endmacro %}", + "meta": {}, + "name": "default__any_value", + "original_file_path": "macros/utils/any_value.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/any_value.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__any_value" + }, + "macro.dbt.default__apply_grants": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.run_query", + "macro.dbt.get_show_grant_sql", + "macro.dbt.get_dcl_statement_list", + "macro.dbt.call_dcl_statements" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__apply_grants(relation, grant_config, should_revoke=True) %}\n {#-- If grant_config is {} or None, this is a no-op --#}\n {% if grant_config %}\n {% if should_revoke %}\n {#-- We think previous grants may have carried over --#}\n {#-- Show current grants and calculate diffs --#}\n {% set current_grants_table = run_query(get_show_grant_sql(relation)) %}\n {% set current_grants_dict = adapter.standardize_grants_dict(current_grants_table) %}\n {% set needs_granting = diff_of_two_dicts(grant_config, current_grants_dict) %}\n {% set needs_revoking = diff_of_two_dicts(current_grants_dict, grant_config) %}\n {% if not (needs_granting or needs_revoking) %}\n {{ log('On ' ~ relation.render() ~': All grants are in place, no revocation or granting needed.')}}\n {% endif %}\n {% else %}\n {#-- We don't think there's any chance of previous grants having carried over. --#}\n {#-- Jump straight to granting what the user has configured. --#}\n {% set needs_revoking = {} %}\n {% set needs_granting = grant_config %}\n {% endif %}\n {% if needs_granting or needs_revoking %}\n {% set revoke_statement_list = get_dcl_statement_list(relation, needs_revoking, get_revoke_sql) %}\n {% set grant_statement_list = get_dcl_statement_list(relation, needs_granting, get_grant_sql) %}\n {% set dcl_statement_list = revoke_statement_list + grant_statement_list %}\n {% if dcl_statement_list %}\n {{ call_dcl_statements(dcl_statement_list) }}\n {% endif %}\n {% endif %}\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "default__apply_grants", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__apply_grants" + }, + "macro.dbt.default__array_append": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__array_append(array, new_element) -%}\n array_append({{ array }}, {{ new_element }})\n{%- endmacro %}", + "meta": {}, + "name": "default__array_append", + "original_file_path": "macros/utils/array_append.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/array_append.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__array_append" + }, + "macro.dbt.default__array_concat": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__array_concat(array_1, array_2) -%}\n array_cat({{ array_1 }}, {{ array_2 }})\n{%- endmacro %}", + "meta": {}, + "name": "default__array_concat", + "original_file_path": "macros/utils/array_concat.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/array_concat.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__array_concat" + }, + "macro.dbt.default__array_construct": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__array_construct(inputs, data_type) -%}\n {% if inputs|length > 0 %}\n array[ {{ inputs|join(' , ') }} ]\n {% else %}\n array[]::{{data_type}}[]\n {% endif %}\n{%- endmacro %}", + "meta": {}, + "name": "default__array_construct", + "original_file_path": "macros/utils/array_construct.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/array_construct.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__array_construct" + }, + "macro.dbt.default__bool_or": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__bool_or(expression) -%}\n\n bool_or({{ expression }})\n\n{%- endmacro %}", + "meta": {}, + "name": "default__bool_or", + "original_file_path": "macros/utils/bool_or.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/bool_or.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__bool_or" + }, + "macro.dbt.default__build_snapshot_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_snapshot_table_column_names", + "macro.dbt.get_dbt_valid_to_current" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__build_snapshot_table(strategy, sql) %}\n {% set columns = config.get('snapshot_table_column_names') or get_snapshot_table_column_names() %}\n\n select *,\n {{ strategy.scd_id }} as {{ columns.dbt_scd_id }},\n {{ strategy.updated_at }} as {{ columns.dbt_updated_at }},\n {{ strategy.updated_at }} as {{ columns.dbt_valid_from }},\n {{ get_dbt_valid_to_current(strategy, columns) }}\n {%- if strategy.hard_deletes == 'new_record' -%}\n , 'False' as {{ columns.dbt_is_deleted }}\n {% endif -%}\n from (\n {{ sql }}\n ) sbq\n\n{% endmacro %}", + "meta": {}, + "name": "default__build_snapshot_table", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__build_snapshot_table" + }, + "macro.dbt.default__call_dcl_statements": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__call_dcl_statements(dcl_statement_list) %}\n {#\n -- By default, supply all grant + revoke statements in a single semicolon-separated block,\n -- so that they're all processed together.\n\n -- Some databases do not support this. Those adapters will need to override this macro\n -- to run each statement individually.\n #}\n {% call statement('grants') %}\n {% for dcl_statement in dcl_statement_list %}\n {{ dcl_statement }};\n {% endfor %}\n {% endcall %}\n{% endmacro %}", + "meta": {}, + "name": "default__call_dcl_statements", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__call_dcl_statements" + }, + "macro.dbt.default__can_clone_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__can_clone_table() %}\n {{ return(False) }}\n{% endmacro %}", + "meta": {}, + "name": "default__can_clone_table", + "original_file_path": "macros/materializations/models/clone/can_clone_table.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/clone/can_clone_table.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__can_clone_table" + }, + "macro.dbt.default__cast": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__cast(field, type) %}\n cast({{field}} as {{type}})\n{% endmacro %}", + "meta": {}, + "name": "default__cast", + "original_file_path": "macros/utils/cast.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/cast.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__cast" + }, + "macro.dbt.default__cast_bool_to_text": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__cast_bool_to_text(field) %}\n cast({{ field }} as {{ api.Column.translate_type('string') }})\n{% endmacro %}", + "meta": {}, + "name": "default__cast_bool_to_text", + "original_file_path": "macros/utils/cast_bool_to_text.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/cast_bool_to_text.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__cast_bool_to_text" + }, + "macro.dbt.default__check_for_schema_changes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.diff_columns", + "macro.dbt.diff_column_data_types" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__check_for_schema_changes(source_relation, target_relation) %}\n\n {% set schema_changed = False %}\n\n {%- set source_columns = adapter.get_columns_in_relation(source_relation) -%}\n {%- set target_columns = adapter.get_columns_in_relation(target_relation) -%}\n {%- set source_not_in_target = diff_columns(source_columns, target_columns) -%}\n {%- set target_not_in_source = diff_columns(target_columns, source_columns) -%}\n\n {% set new_target_types = diff_column_data_types(source_columns, target_columns) %}\n\n {% if source_not_in_target != [] %}\n {% set schema_changed = True %}\n {% elif target_not_in_source != [] or new_target_types != [] %}\n {% set schema_changed = True %}\n {% elif new_target_types != [] %}\n {% set schema_changed = True %}\n {% endif %}\n\n {% set changes_dict = {\n 'schema_changed': schema_changed,\n 'source_not_in_target': source_not_in_target,\n 'target_not_in_source': target_not_in_source,\n 'source_columns': source_columns,\n 'target_columns': target_columns,\n 'new_target_types': new_target_types\n } %}\n\n {% set msg %}\n In {{ target_relation }}:\n Schema changed: {{ schema_changed }}\n Source columns not in target: {{ source_not_in_target }}\n Target columns not in source: {{ target_not_in_source }}\n New column types: {{ new_target_types }}\n {% endset %}\n\n {% do log(msg) %}\n\n {{ return(changes_dict) }}\n\n{% endmacro %}", + "meta": {}, + "name": "default__check_for_schema_changes", + "original_file_path": "macros/materializations/models/incremental/on_schema_change.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/on_schema_change.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__check_for_schema_changes" + }, + "macro.dbt.default__check_schema_exists": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.replace", + "macro.dbt.run_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__check_schema_exists(information_schema, schema) -%}\n {% set sql -%}\n select count(*)\n from {{ information_schema.replace(information_schema_view='SCHEMATA') }}\n where catalog_name='{{ information_schema.database }}'\n and schema_name='{{ schema }}'\n {%- endset %}\n {{ return(run_query(sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "default__check_schema_exists", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__check_schema_exists" + }, + "macro.dbt.default__collect_freshness": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement", + "macro.dbt.current_timestamp" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__collect_freshness(source, loaded_at_field, filter) %}\n {% call statement('collect_freshness', fetch_result=True, auto_begin=False) -%}\n select\n max({{ loaded_at_field }}) as max_loaded_at,\n {{ current_timestamp() }} as snapshotted_at\n from {{ source }}\n {% if filter %}\n where {{ filter }}\n {% endif %}\n {% endcall %}\n {{ return(load_result('collect_freshness')) }}\n{% endmacro %}", + "meta": {}, + "name": "default__collect_freshness", + "original_file_path": "macros/adapters/freshness.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/freshness.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__collect_freshness" + }, + "macro.dbt.default__collect_freshness_custom_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement", + "macro.dbt.current_timestamp" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__collect_freshness_custom_sql(source, loaded_at_query) %}\n {% call statement('collect_freshness_custom_sql', fetch_result=True, auto_begin=False) -%}\n with source_query as (\n {{ loaded_at_query }}\n )\n select\n (select * from source_query) as max_loaded_at,\n {{ current_timestamp() }} as snapshotted_at\n {% endcall %}\n {{ return(load_result('collect_freshness_custom_sql')) }}\n{% endmacro %}", + "meta": {}, + "name": "default__collect_freshness_custom_sql", + "original_file_path": "macros/adapters/freshness.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/freshness.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__collect_freshness_custom_sql" + }, + "macro.dbt.default__concat": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__concat(fields) -%}\n {{ fields|join(' || ') }}\n{%- endmacro %}", + "meta": {}, + "name": "default__concat", + "original_file_path": "macros/utils/concat.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/concat.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__concat" + }, + "macro.dbt.default__copy_grants": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__copy_grants() %}\n {{ return(True) }}\n{% endmacro %}", + "meta": {}, + "name": "default__copy_grants", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__copy_grants" + }, + "macro.dbt.default__create_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__create_columns(relation, columns) %}\n {% for column in columns %}\n {% call statement() %}\n alter table {{ relation.render() }} add column {{ adapter.quote(column.name) }} {{ column.expanded_data_type }};\n {% endcall %}\n {% endfor %}\n{% endmacro %}", + "meta": {}, + "name": "default__create_columns", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__create_columns" + }, + "macro.dbt.default__create_csv_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__create_csv_table(model, agate_table) %}\n {%- set column_override = model['config'].get('column_types', {}) -%}\n {%- set quote_seed_column = model['config'].get('quote_columns', None) -%}\n\n {% set sql %}\n create table {{ this.render() }} (\n {%- for col_name in agate_table.column_names -%}\n {%- set inferred_type = adapter.convert_type(agate_table, loop.index0) -%}\n {%- set type = column_override.get(col_name, inferred_type) -%}\n {%- set column_name = (col_name | string) -%}\n {{ adapter.quote_seed_column(column_name, quote_seed_column) }} {{ type }} {%- if not loop.last -%}, {%- endif -%}\n {%- endfor -%}\n )\n {% endset %}\n\n {% call statement('_') -%}\n {{ sql }}\n {%- endcall %}\n\n {{ return(sql) }}\n{% endmacro %}", + "meta": {}, + "name": "default__create_csv_table", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__create_csv_table" + }, + "macro.dbt.default__create_indexes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_create_index_sql", + "macro.dbt.run_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__create_indexes(relation) -%}\n {%- set _indexes = config.get('indexes', default=[]) -%}\n\n {% for _index_dict in _indexes %}\n {% set create_index_sql = get_create_index_sql(relation, _index_dict) %}\n {% if create_index_sql %}\n {% do run_query(create_index_sql) %}\n {% endif %}\n {% endfor %}\n{% endmacro %}", + "meta": {}, + "name": "default__create_indexes", + "original_file_path": "macros/adapters/indexes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/indexes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__create_indexes" + }, + "macro.dbt.default__create_or_replace_clone": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__create_or_replace_clone(this_relation, defer_relation) %}\n create or replace table {{ this_relation.render() }} clone {{ defer_relation.render() }}\n{% endmacro %}", + "meta": {}, + "name": "default__create_or_replace_clone", + "original_file_path": "macros/materializations/models/clone/create_or_replace_clone.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/clone/create_or_replace_clone.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__create_or_replace_clone" + }, + "macro.dbt.default__create_schema": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__create_schema(relation) -%}\n {%- call statement('create_schema') -%}\n create schema if not exists {{ relation.without_identifier() }}\n {% endcall %}\n{% endmacro %}", + "meta": {}, + "name": "default__create_schema", + "original_file_path": "macros/adapters/schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__create_schema" + }, + "macro.dbt.default__create_table_as": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_assert_columns_equivalent", + "macro.dbt.get_table_columns_and_constraints", + "macro.dbt.get_select_subquery" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__create_table_as(temporary, relation, sql) -%}\n {%- set sql_header = config.get('sql_header', none) -%}\n\n {{ sql_header if sql_header is not none }}\n\n create {% if temporary: -%}temporary{%- endif %} table\n {{ relation.include(database=(not temporary), schema=(not temporary)) }}\n {% set contract_config = config.get('contract') %}\n {% if contract_config.enforced and (not temporary) %}\n {{ get_assert_columns_equivalent(sql) }}\n {{ get_table_columns_and_constraints() }}\n {%- set sql = get_select_subquery(sql) %}\n {% endif %}\n as (\n {{ sql }}\n );\n{%- endmacro %}", + "meta": {}, + "name": "default__create_table_as", + "original_file_path": "macros/relations/table/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__create_table_as" + }, + "macro.dbt.default__create_view_as": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_assert_columns_equivalent" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__create_view_as(relation, sql) -%}\n {%- set sql_header = config.get('sql_header', none) -%}\n\n {{ sql_header if sql_header is not none }}\n create view {{ relation.render() }}\n {% set contract_config = config.get('contract') %}\n {% if contract_config.enforced %}\n {{ get_assert_columns_equivalent(sql) }}\n {%- endif %}\n as (\n {{ sql }}\n );\n{%- endmacro %}", + "meta": {}, + "name": "default__create_view_as", + "original_file_path": "macros/relations/view/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__create_view_as" + }, + "macro.dbt.default__current_timestamp": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__current_timestamp() -%}\n {{ exceptions.raise_not_implemented(\n 'current_timestamp macro not implemented for adapter ' + adapter.type()) }}\n{%- endmacro %}", + "meta": {}, + "name": "default__current_timestamp", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__current_timestamp" + }, + "macro.dbt.default__current_timestamp_backcompat": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__current_timestamp_backcompat() %}\n current_timestamp::timestamp\n{% endmacro %}", + "meta": {}, + "name": "default__current_timestamp_backcompat", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__current_timestamp_backcompat" + }, + "macro.dbt.default__current_timestamp_in_utc_backcompat": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.current_timestamp_backcompat", + "macro.dbt.default__current_timestamp_backcompat" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__current_timestamp_in_utc_backcompat() %}\n {{ return(adapter.dispatch('current_timestamp_backcompat', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "default__current_timestamp_in_utc_backcompat", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__current_timestamp_in_utc_backcompat" + }, + "macro.dbt.default__date": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__date(year, month, day) -%}\n {%- set dt = modules.datetime.date(year, month, day) -%}\n {%- set iso_8601_formatted_date = dt.strftime('%Y-%m-%d') -%}\n to_date('{{ iso_8601_formatted_date }}', 'YYYY-MM-DD')\n{%- endmacro %}", + "meta": {}, + "name": "default__date", + "original_file_path": "macros/utils/date.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/date.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__date" + }, + "macro.dbt.default__date_spine": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.generate_series", + "macro.dbt.get_intervals_between", + "macro.dbt.dateadd" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__date_spine(datepart, start_date, end_date) %}\n\n\n {# call as follows:\n\n date_spine(\n \"day\",\n \"to_date('01/01/2016', 'mm/dd/yyyy')\",\n \"dbt.dateadd(week, 1, current_date)\"\n ) #}\n\n\n with rawdata as (\n\n {{dbt.generate_series(\n dbt.get_intervals_between(start_date, end_date, datepart)\n )}}\n\n ),\n\n all_periods as (\n\n select (\n {{\n dbt.dateadd(\n datepart,\n \"row_number() over (order by 1) - 1\",\n start_date\n )\n }}\n ) as date_{{datepart}}\n from rawdata\n\n ),\n\n filtered as (\n\n select *\n from all_periods\n where date_{{datepart}} <= {{ end_date }}\n\n )\n\n select * from filtered\n\n{% endmacro %}", + "meta": {}, + "name": "default__date_spine", + "original_file_path": "macros/utils/date_spine.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/date_spine.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__date_spine" + }, + "macro.dbt.default__date_trunc": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__date_trunc(datepart, date) -%}\n date_trunc('{{datepart}}', {{date}})\n{%- endmacro %}", + "meta": {}, + "name": "default__date_trunc", + "original_file_path": "macros/utils/date_trunc.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/date_trunc.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__date_trunc" + }, + "macro.dbt.default__dateadd": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__dateadd(datepart, interval, from_date_or_timestamp) %}\n\n dateadd(\n {{ datepart }},\n {{ interval }},\n {{ from_date_or_timestamp }}\n )\n\n{% endmacro %}", + "meta": {}, + "name": "default__dateadd", + "original_file_path": "macros/utils/dateadd.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/dateadd.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__dateadd" + }, + "macro.dbt.default__datediff": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__datediff(first_date, second_date, datepart) -%}\n\n datediff(\n {{ datepart }},\n {{ first_date }},\n {{ second_date }}\n )\n\n{%- endmacro %}", + "meta": {}, + "name": "default__datediff", + "original_file_path": "macros/utils/datediff.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/datediff.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__datediff" + }, + "macro.dbt.default__diff_column_data_types": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__diff_column_data_types(source_columns, target_columns) %}\n {% set result = [] %}\n {% for sc in source_columns %}\n {% set tc = target_columns | selectattr(\"name\", \"equalto\", sc.name) | list | first %}\n {% if tc %}\n {% if sc.expanded_data_type != tc.expanded_data_type and not sc.can_expand_to(other_column=tc) %}\n {{ result.append( { 'column_name': tc.name, 'new_type': sc.expanded_data_type } ) }}\n {% endif %}\n {% endif %}\n {% endfor %}\n\n {{ return(result) }}\n\n{% endmacro %}", + "meta": {}, + "name": "default__diff_column_data_types", + "original_file_path": "macros/materializations/models/incremental/column_helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/column_helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__diff_column_data_types" + }, + "macro.dbt.default__drop_materialized_view": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__drop_materialized_view(relation) -%}\n drop materialized view if exists {{ relation.render() }} cascade\n{%- endmacro %}", + "meta": {}, + "name": "default__drop_materialized_view", + "original_file_path": "macros/relations/materialized_view/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__drop_materialized_view" + }, + "macro.dbt.default__drop_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement", + "macro.dbt.get_drop_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__drop_relation(relation) -%}\n {% call statement('drop_relation', auto_begin=False) -%}\n {{ get_drop_sql(relation) }}\n {%- endcall %}\n{% endmacro %}", + "meta": {}, + "name": "default__drop_relation", + "original_file_path": "macros/relations/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__drop_relation" + }, + "macro.dbt.default__drop_schema": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__drop_schema(relation) -%}\n {%- call statement('drop_schema') -%}\n drop schema if exists {{ relation.without_identifier() }} cascade\n {% endcall %}\n{% endmacro %}", + "meta": {}, + "name": "default__drop_schema", + "original_file_path": "macros/adapters/schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__drop_schema" + }, + "macro.dbt.default__drop_schema_named": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__drop_schema_named(schema_name) %}\n {% set schema_relation = api.Relation.create(schema=schema_name) %}\n {{ adapter.drop_schema(schema_relation) }}\n{% endmacro %}", + "meta": {}, + "name": "default__drop_schema_named", + "original_file_path": "macros/relations/schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__drop_schema_named" + }, + "macro.dbt.default__drop_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__drop_table(relation) -%}\n drop table if exists {{ relation.render() }} cascade\n{%- endmacro %}", + "meta": {}, + "name": "default__drop_table", + "original_file_path": "macros/relations/table/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__drop_table" + }, + "macro.dbt.default__drop_view": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__drop_view(relation) -%}\n drop view if exists {{ relation.render() }} cascade\n{%- endmacro %}", + "meta": {}, + "name": "default__drop_view", + "original_file_path": "macros/relations/view/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__drop_view" + }, + "macro.dbt.default__equals": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__equals(expr1, expr2) -%}\n{%- if adapter.behavior.enable_truthy_nulls_equals_macro.no_warn %}\n case when (({{ expr1 }} = {{ expr2 }}) or ({{ expr1 }} is null and {{ expr2 }} is null))\n then 0\n else 1\n end = 0\n{%- else -%}\n ({{ expr1 }} = {{ expr2 }})\n{%- endif %}\n{% endmacro %}", + "meta": {}, + "name": "default__equals", + "original_file_path": "macros/utils/equals.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/equals.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__equals" + }, + "macro.dbt.default__escape_single_quotes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__escape_single_quotes(expression) -%}\n{{ expression | replace(\"'\",\"''\") }}\n{%- endmacro %}", + "meta": {}, + "name": "default__escape_single_quotes", + "original_file_path": "macros/utils/escape_single_quotes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/escape_single_quotes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__escape_single_quotes" + }, + "macro.dbt.default__except": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__except() %}\n\n except\n\n{% endmacro %}", + "meta": {}, + "name": "default__except", + "original_file_path": "macros/utils/except.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/except.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__except" + }, + "macro.dbt.default__format_column": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__format_column(column) -%}\n {% set data_type = column.dtype %}\n {% set formatted = column.column.lower() ~ \" \" ~ data_type %}\n {{ return({'name': column.name, 'data_type': data_type, 'formatted': formatted}) }}\n{%- endmacro -%}", + "meta": {}, + "name": "default__format_column", + "original_file_path": "macros/relations/column/columns_spec_ddl.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/column/columns_spec_ddl.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__format_column" + }, + "macro.dbt.default__formatted_scalar_function_args_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__formatted_scalar_function_args_sql() %}\n {% set args = [] %}\n {% for arg in model.arguments -%}\n {%- do args.append(arg.name ~ ' ' ~ arg.data_type) -%}\n {%- endfor %}\n {{ args | join(', ') }}\n{% endmacro %}", + "meta": {}, + "name": "default__formatted_scalar_function_args_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__formatted_scalar_function_args_sql" + }, + "macro.dbt.default__function_execute_build_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__function_execute_build_sql(build_sql, existing_relation, target_relation) %}\n\n {% set grant_config = config.get('grants') %}\n\n {% call statement(name=\"main\") %}\n {{ build_sql }}\n {% endcall %}\n\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n {{ adapter.commit() }}\n\n{% endmacro %}", + "meta": {}, + "name": "default__function_execute_build_sql", + "original_file_path": "macros/materializations/functions/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__function_execute_build_sql" + }, + "macro.dbt.default__generate_alias_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__generate_alias_name(custom_alias_name=none, node=none) -%}\n\n {%- if custom_alias_name -%}\n\n {{ custom_alias_name | trim }}\n\n {%- elif node.version -%}\n\n {{ return(node.name ~ \"_v\" ~ (node.version | replace(\".\", \"_\"))) }}\n\n {%- else -%}\n\n {{ node.name }}\n\n {%- endif -%}\n\n{%- endmacro %}", + "meta": {}, + "name": "default__generate_alias_name", + "original_file_path": "macros/get_custom_name/get_custom_alias.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/get_custom_name/get_custom_alias.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__generate_alias_name" + }, + "macro.dbt.default__generate_database_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__generate_database_name(custom_database_name=none, node=none) -%}\n {%- set default_database = target.database -%}\n {%- if custom_database_name is none -%}\n\n {{ default_database }}\n\n {%- else -%}\n\n {{ custom_database_name }}\n\n {%- endif -%}\n\n{%- endmacro %}", + "meta": {}, + "name": "default__generate_database_name", + "original_file_path": "macros/get_custom_name/get_custom_database.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/get_custom_name/get_custom_database.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__generate_database_name" + }, + "macro.dbt.default__generate_schema_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__generate_schema_name(custom_schema_name, node) -%}\n\n {%- set default_schema = target.schema -%}\n {%- if custom_schema_name is none -%}\n\n {{ default_schema }}\n\n {%- else -%}\n\n {{ default_schema }}_{{ custom_schema_name | trim }}\n\n {%- endif -%}\n\n{%- endmacro %}", + "meta": {}, + "name": "default__generate_schema_name", + "original_file_path": "macros/get_custom_name/get_custom_schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/get_custom_name/get_custom_schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__generate_schema_name" + }, + "macro.dbt.default__generate_series": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_powers_of_two" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__generate_series(upper_bound) %}\n\n {% set n = dbt.get_powers_of_two(upper_bound) %}\n\n with p as (\n select 0 as generated_number union all select 1\n ), unioned as (\n\n select\n\n {% for i in range(n) %}\n p{{i}}.generated_number * power(2, {{i}})\n {% if not loop.last %} + {% endif %}\n {% endfor %}\n + 1\n as generated_number\n\n from\n\n {% for i in range(n) %}\n p as p{{i}}\n {% if not loop.last %} cross join {% endif %}\n {% endfor %}\n\n )\n\n select *\n from unioned\n where generated_number <= {{upper_bound}}\n order by generated_number\n\n{% endmacro %}", + "meta": {}, + "name": "default__generate_series", + "original_file_path": "macros/utils/generate_series.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/generate_series.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__generate_series" + }, + "macro.dbt.default__get_aggregate_function_create_replace_signature": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_formatted_aggregate_function_args", + "macro.dbt.get_function_language_specifier", + "macro.dbt.get_aggregate_function_volatility_specifier", + "macro.dbt.get_function_python_options" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_aggregate_function_create_replace_signature(target_relation) %}\n CREATE OR REPLACE AGGREGATE FUNCTION {{ target_relation.render() }} ({{ get_formatted_aggregate_function_args()}})\n RETURNS {{ model.returns.data_type }}\n {{ get_function_language_specifier() }}\n {{ get_aggregate_function_volatility_specifier() }}\n {% if model.get('language') == 'python' %}\n {{ get_function_python_options() }}\n {% endif %}\n AS\n{% endmacro %}", + "meta": {}, + "name": "default__get_aggregate_function_create_replace_signature", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_aggregate_function_create_replace_signature" + }, + "macro.dbt.default__get_aggregate_function_volatility_specifier": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.scalar_function_volatility_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_aggregate_function_volatility_specifier() %}\n {{ scalar_function_volatility_sql() }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_aggregate_function_volatility_specifier", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_aggregate_function_volatility_specifier" + }, + "macro.dbt.default__get_alter_materialized_view_as_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_alter_materialized_view_as_sql(\n relation,\n configuration_changes,\n sql,\n existing_relation,\n backup_relation,\n intermediate_relation\n) %}\n {{ exceptions.raise_compiler_error(\"Materialized views have not been implemented for this adapter.\") }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_alter_materialized_view_as_sql", + "original_file_path": "macros/relations/materialized_view/alter.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/alter.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_alter_materialized_view_as_sql" + }, + "macro.dbt.default__get_assert_columns_equivalent": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.assert_columns_equivalent" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_assert_columns_equivalent(sql) -%}\n {{ return(assert_columns_equivalent(sql)) }}\n{%- endmacro %}", + "meta": {}, + "name": "default__get_assert_columns_equivalent", + "original_file_path": "macros/relations/column/columns_spec_ddl.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/column/columns_spec_ddl.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_assert_columns_equivalent" + }, + "macro.dbt.default__get_batch_size": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_batch_size() %}\n {{ return(10000) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_batch_size", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_batch_size" + }, + "macro.dbt.default__get_binding_char": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_binding_char() %}\n {{ return('%s') }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_binding_char", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_binding_char" + }, + "macro.dbt.default__get_catalog": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_catalog(information_schema, schemas) -%}\n\n {% set typename = adapter.type() %}\n {% set msg -%}\n get_catalog not implemented for {{ typename }}\n {%- endset %}\n\n {{ exceptions.raise_compiler_error(msg) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_catalog", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_catalog" + }, + "macro.dbt.default__get_catalog_for_single_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_catalog_for_single_relation(relation) %}\n {{ exceptions.raise_not_implemented(\n 'get_catalog_for_single_relation macro not implemented for adapter '+adapter.type()) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_catalog_for_single_relation", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_catalog_for_single_relation" + }, + "macro.dbt.default__get_catalog_relations": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_catalog_relations(information_schema, relations) -%}\n {% set typename = adapter.type() %}\n {% set msg -%}\n get_catalog_relations not implemented for {{ typename }}\n {%- endset %}\n\n {{ exceptions.raise_compiler_error(msg) }}\n{%- endmacro %}", + "meta": {}, + "name": "default__get_catalog_relations", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_catalog_relations" + }, + "macro.dbt.default__get_column_names": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_column_names() %}\n {#- loop through user_provided_columns to get column names -#}\n {%- set user_provided_columns = model['columns'] -%}\n {%- for i in user_provided_columns %}\n {%- set col = user_provided_columns[i] -%}\n {%- set col_name = adapter.quote(col['name']) if col.get('quote') else col['name'] -%}\n {{ col_name }}{{ \", \" if not loop.last }}\n {%- endfor -%}\n{% endmacro %}", + "meta": {}, + "name": "default__get_column_names", + "original_file_path": "macros/relations/table/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_column_names" + }, + "macro.dbt.default__get_columns_in_query": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement", + "macro.dbt.get_empty_subquery_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_columns_in_query(select_sql) %}\n {% call statement('get_columns_in_query', fetch_result=True, auto_begin=False) -%}\n {{ get_empty_subquery_sql(select_sql) }}\n {% endcall %}\n {{ return(load_result('get_columns_in_query').table.columns | map(attribute='name') | list) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_columns_in_query", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_columns_in_query" + }, + "macro.dbt.default__get_columns_in_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_columns_in_relation(relation) -%}\n {{ exceptions.raise_not_implemented(\n 'get_columns_in_relation macro not implemented for adapter '+adapter.type()) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_columns_in_relation", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_columns_in_relation" + }, + "macro.dbt.default__get_create_backup_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_backup_relation", + "macro.dbt.get_drop_sql", + "macro.dbt.get_rename_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__get_create_backup_sql(relation) -%}\n\n -- get the standard backup name\n {% set backup_relation = make_backup_relation(relation, relation.type) %}\n\n -- drop any pre-existing backup\n {{ get_drop_sql(backup_relation) }};\n\n {{ get_rename_sql(relation, backup_relation.identifier) }}\n\n{%- endmacro -%}", + "meta": {}, + "name": "default__get_create_backup_sql", + "original_file_path": "macros/relations/create_backup.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/create_backup.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_create_backup_sql" + }, + "macro.dbt.default__get_create_index_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_create_index_sql(relation, index_dict) -%}\n {% do return(None) %}\n{% endmacro %}", + "meta": {}, + "name": "default__get_create_index_sql", + "original_file_path": "macros/adapters/indexes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/indexes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_create_index_sql" + }, + "macro.dbt.default__get_create_intermediate_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_intermediate_relation", + "macro.dbt.get_drop_sql", + "macro.dbt.get_create_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__get_create_intermediate_sql(relation, sql) -%}\n\n -- get the standard intermediate name\n {% set intermediate_relation = make_intermediate_relation(relation) %}\n\n -- drop any pre-existing intermediate\n {{ get_drop_sql(intermediate_relation) }};\n\n {{ get_create_sql(intermediate_relation, sql) }}\n\n{%- endmacro -%}", + "meta": {}, + "name": "default__get_create_intermediate_sql", + "original_file_path": "macros/relations/create_intermediate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/create_intermediate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_create_intermediate_sql" + }, + "macro.dbt.default__get_create_materialized_view_as_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_create_materialized_view_as_sql(relation, sql) -%}\n {{ exceptions.raise_compiler_error(\n \"`get_create_materialized_view_as_sql` has not been implemented for this adapter.\"\n ) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_create_materialized_view_as_sql", + "original_file_path": "macros/relations/materialized_view/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_create_materialized_view_as_sql" + }, + "macro.dbt.default__get_create_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_create_view_as_sql", + "macro.dbt.get_create_table_as_sql", + "macro.dbt.get_create_materialized_view_as_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__get_create_sql(relation, sql) -%}\n\n {%- if relation.is_view -%}\n {{ get_create_view_as_sql(relation, sql) }}\n\n {%- elif relation.is_table -%}\n {{ get_create_table_as_sql(False, relation, sql) }}\n\n {%- elif relation.is_materialized_view -%}\n {{ get_create_materialized_view_as_sql(relation, sql) }}\n\n {%- else -%}\n {{- exceptions.raise_compiler_error(\"`get_create_sql` has not been implemented for: \" ~ relation.type ) -}}\n\n {%- endif -%}\n\n{%- endmacro -%}", + "meta": {}, + "name": "default__get_create_sql", + "original_file_path": "macros/relations/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_create_sql" + }, + "macro.dbt.default__get_create_table_as_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.create_table_as" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_create_table_as_sql(temporary, relation, sql) -%}\n {{ return(create_table_as(temporary, relation, sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_create_table_as_sql", + "original_file_path": "macros/relations/table/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_create_table_as_sql" + }, + "macro.dbt.default__get_create_view_as_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.create_view_as" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_create_view_as_sql(relation, sql) -%}\n {{ return(create_view_as(relation, sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_create_view_as_sql", + "original_file_path": "macros/relations/view/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_create_view_as_sql" + }, + "macro.dbt.default__get_csv_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_csv_sql(create_or_truncate_sql, insert_sql) %}\n {{ create_or_truncate_sql }};\n -- dbt seed --\n {{ insert_sql }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_csv_sql", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_csv_sql" + }, + "macro.dbt.default__get_dcl_statement_list": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.support_multiple_grantees_per_dcl_statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro default__get_dcl_statement_list(relation, grant_config, get_dcl_macro) -%}\n {#\n -- Unpack grant_config into specific privileges and the set of users who need them granted/revoked.\n -- Depending on whether this database supports multiple grantees per statement, pass in the list of\n -- all grantees per privilege, or (if not) template one statement per privilege-grantee pair.\n -- `get_dcl_macro` will be either `get_grant_sql` or `get_revoke_sql`\n #}\n {%- set dcl_statements = [] -%}\n {%- for privilege, grantees in grant_config.items() %}\n {%- if support_multiple_grantees_per_dcl_statement() and grantees -%}\n {%- set dcl = get_dcl_macro(relation, privilege, grantees) -%}\n {%- do dcl_statements.append(dcl) -%}\n {%- else -%}\n {%- for grantee in grantees -%}\n {% set dcl = get_dcl_macro(relation, privilege, [grantee]) %}\n {%- do dcl_statements.append(dcl) -%}\n {% endfor -%}\n {%- endif -%}\n {%- endfor -%}\n {{ return(dcl_statements) }}\n{%- endmacro %}", + "meta": {}, + "name": "default__get_dcl_statement_list", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_dcl_statement_list" + }, + "macro.dbt.default__get_delete_insert_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_quoted_csv" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_delete_insert_merge_sql(target, source, unique_key, dest_columns, incremental_predicates) -%}\n\n {%- set dest_cols_csv = get_quoted_csv(dest_columns | map(attribute=\"name\")) -%}\n\n {% if unique_key %}\n {% if unique_key is string %}\n {% set unique_key = [unique_key] %}\n {% endif %}\n\n {%- set unique_key_str = unique_key|join(', ') -%}\n\n delete from {{ target }} as DBT_INTERNAL_DEST\n where ({{ unique_key_str }}) in (\n select distinct {{ unique_key_str }}\n from {{ source }} as DBT_INTERNAL_SOURCE\n )\n {%- if incremental_predicates %}\n {% for predicate in incremental_predicates %}\n and {{ predicate }}\n {% endfor %}\n {%- endif -%};\n\n {% endif %}\n\n insert into {{ target }} ({{ dest_cols_csv }})\n (\n select {{ dest_cols_csv }}\n from {{ source }}\n )\n\n{%- endmacro %}", + "meta": {}, + "name": "default__get_delete_insert_merge_sql", + "original_file_path": "macros/materializations/models/incremental/merge.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_delete_insert_merge_sql" + }, + "macro.dbt.default__get_drop_backup_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_backup_relation", + "macro.dbt.get_drop_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__get_drop_backup_sql(relation) -%}\n\n -- get the standard backup name\n {% set backup_relation = make_backup_relation(relation, relation.type) %}\n\n {{ get_drop_sql(backup_relation) }}\n\n{%- endmacro -%}", + "meta": {}, + "name": "default__get_drop_backup_sql", + "original_file_path": "macros/relations/drop_backup.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/drop_backup.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_drop_backup_sql" + }, + "macro.dbt.default__get_drop_index_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_drop_index_sql(relation, index_name) -%}\n {{ exceptions.raise_compiler_error(\"`get_drop_index_sql has not been implemented for this adapter.\") }}\n{%- endmacro %}", + "meta": {}, + "name": "default__get_drop_index_sql", + "original_file_path": "macros/adapters/indexes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/indexes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_drop_index_sql" + }, + "macro.dbt.default__get_drop_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.drop_view", + "macro.dbt.drop_table", + "macro.dbt.drop_materialized_view" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__get_drop_sql(relation) -%}\n\n {%- if relation.is_view -%}\n {{ drop_view(relation) }}\n\n {%- elif relation.is_table -%}\n {{ drop_table(relation) }}\n\n {%- elif relation.is_materialized_view -%}\n {{ drop_materialized_view(relation) }}\n\n {%- else -%}\n drop {{ relation.type }} if exists {{ relation.render() }} cascade\n\n {%- endif -%}\n\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "default__get_drop_sql", + "original_file_path": "macros/relations/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_drop_sql" + }, + "macro.dbt.default__get_empty_schema_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.cast" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_empty_schema_sql(columns) %}\n {%- set col_err = [] -%}\n {%- set col_naked_numeric = [] -%}\n select\n {% for i in columns %}\n {%- set col = columns[i] -%}\n {%- if col['data_type'] is not defined -%}\n {%- do col_err.append(col['name']) -%}\n {#-- If this column's type is just 'numeric' then it is missing precision/scale, raise a warning --#}\n {%- elif col['data_type'].strip().lower() in ('numeric', 'decimal', 'number') -%}\n {%- do col_naked_numeric.append(col['name']) -%}\n {%- endif -%}\n {% set col_name = adapter.quote(col['name']) if col.get('quote') else col['name'] %}\n {{ cast('null', col['data_type']) }} as {{ col_name }}{{ \", \" if not loop.last }}\n {%- endfor -%}\n {%- if (col_err | length) > 0 -%}\n {{ exceptions.column_type_missing(column_names=col_err) }}\n {%- elif (col_naked_numeric | length) > 0 -%}\n {{ exceptions.warn(\"Detected columns with numeric type and unspecified precision/scale, this can lead to unintended rounding: \" ~ col_naked_numeric ~ \"`\") }}\n {%- endif -%}\n{% endmacro %}", + "meta": {}, + "name": "default__get_empty_schema_sql", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_empty_schema_sql" + }, + "macro.dbt.default__get_empty_subquery_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_empty_subquery_sql(select_sql, select_sql_header=none) %}\n {%- if select_sql_header is not none -%}\n {{ select_sql_header }}\n {%- endif -%}\n select * from (\n {{ select_sql }}\n ) as __dbt_sbq\n where false\n limit 0\n{% endmacro %}", + "meta": {}, + "name": "default__get_empty_subquery_sql", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_empty_subquery_sql" + }, + "macro.dbt.default__get_formatted_aggregate_function_args": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.formatted_scalar_function_args_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_formatted_aggregate_function_args() %}\n {# conveniently we can reuse the sql scalar function args #}\n {{ formatted_scalar_function_args_sql() }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_formatted_aggregate_function_args", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_formatted_aggregate_function_args" + }, + "macro.dbt.default__get_function_language_specifier": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_function_language_specifier() %}\n {% set language = model.get('language') %}\n {% if language == 'sql' %}\n {# generally you dont need to specify the language for sql functions #}\n {% elif language == 'python' %}\n LANGUAGE PYTHON\n {% else %}\n {{ 'LANGUAGE ' ~ language.upper() }}\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "default__get_function_language_specifier", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_function_language_specifier" + }, + "macro.dbt.default__get_function_python_options": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_function_python_options() %}\n RUNTIME_VERSION = '{{ model.config.get('runtime_version') }}'\n HANDLER = '{{ model.config.get('entry_point') }}'\n {% set packages = model.config.get('packages', []) %}\n {% if packages %}\n PACKAGES = ('{{ packages | join(\"','\") }}')\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "default__get_function_python_options", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_function_python_options" + }, + "macro.dbt.default__get_grant_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro default__get_grant_sql(relation, privilege, grantees) -%}\n grant {{ privilege }} on {{ relation.render() }} to {{ grantees | join(', ') }}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "default__get_grant_sql", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_grant_sql" + }, + "macro.dbt.default__get_incremental_append_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_insert_into_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_incremental_append_sql(arg_dict) %}\n\n {% do return(get_insert_into_sql(arg_dict[\"target_relation\"], arg_dict[\"temp_relation\"], arg_dict[\"dest_columns\"])) %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_incremental_append_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_incremental_append_sql" + }, + "macro.dbt.default__get_incremental_default_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_incremental_append_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_incremental_default_sql(arg_dict) %}\n\n {% do return(get_incremental_append_sql(arg_dict)) %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_incremental_default_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_incremental_default_sql" + }, + "macro.dbt.default__get_incremental_delete_insert_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_delete_insert_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_incremental_delete_insert_sql(arg_dict) %}\n\n {% do return(get_delete_insert_merge_sql(arg_dict[\"target_relation\"], arg_dict[\"temp_relation\"], arg_dict[\"unique_key\"], arg_dict[\"dest_columns\"], arg_dict[\"incremental_predicates\"])) %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_incremental_delete_insert_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_incremental_delete_insert_sql" + }, + "macro.dbt.default__get_incremental_insert_overwrite_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_insert_overwrite_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_incremental_insert_overwrite_sql(arg_dict) %}\n\n {% do return(get_insert_overwrite_merge_sql(arg_dict[\"target_relation\"], arg_dict[\"temp_relation\"], arg_dict[\"dest_columns\"], arg_dict[\"incremental_predicates\"])) %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_incremental_insert_overwrite_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_incremental_insert_overwrite_sql" + }, + "macro.dbt.default__get_incremental_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_incremental_merge_sql(arg_dict) %}\n\n {% do return(get_merge_sql(arg_dict[\"target_relation\"], arg_dict[\"temp_relation\"], arg_dict[\"unique_key\"], arg_dict[\"dest_columns\"], arg_dict[\"incremental_predicates\"])) %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_incremental_merge_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_incremental_merge_sql" + }, + "macro.dbt.default__get_incremental_microbatch_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_incremental_microbatch_sql(arg_dict) %}\n\n {{ exceptions.raise_not_implemented('microbatch materialization strategy not implemented for adapter ' + adapter.type()) }}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_incremental_microbatch_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_incremental_microbatch_sql" + }, + "macro.dbt.default__get_insert_overwrite_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_quoted_csv" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_insert_overwrite_merge_sql(target, source, dest_columns, predicates, include_sql_header) -%}\n {#-- The only time include_sql_header is True: --#}\n {#-- BigQuery + insert_overwrite strategy + \"static\" partitions config --#}\n {#-- We should consider including the sql header at the materialization level instead --#}\n\n {%- set predicates = [] if predicates is none else [] + predicates -%}\n {%- set dest_cols_csv = get_quoted_csv(dest_columns | map(attribute=\"name\")) -%}\n {%- set sql_header = config.get('sql_header', none) -%}\n\n {{ sql_header if sql_header is not none and include_sql_header }}\n\n merge into {{ target }} as DBT_INTERNAL_DEST\n using {{ source }} as DBT_INTERNAL_SOURCE\n on FALSE\n\n when not matched by source\n {% if predicates %} and {{ predicates | join(' and ') }} {% endif %}\n then delete\n\n when not matched then insert\n ({{ dest_cols_csv }})\n values\n ({{ dest_cols_csv }})\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_insert_overwrite_merge_sql", + "original_file_path": "macros/materializations/models/incremental/merge.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_insert_overwrite_merge_sql" + }, + "macro.dbt.default__get_intervals_between": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement", + "macro.dbt.datediff" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_intervals_between(start_date, end_date, datepart) -%}\n {%- call statement('get_intervals_between', fetch_result=True) %}\n\n select {{ dbt.datediff(start_date, end_date, datepart) }}\n\n {%- endcall -%}\n\n {%- set value_list = load_result('get_intervals_between') -%}\n\n {%- if value_list and value_list['data'] -%}\n {%- set values = value_list['data'] | map(attribute=0) | list %}\n {{ return(values[0]) }}\n {%- else -%}\n {{ return(1) }}\n {%- endif -%}\n\n{%- endmacro %}", + "meta": {}, + "name": "default__get_intervals_between", + "original_file_path": "macros/utils/date_spine.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/date_spine.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_intervals_between" + }, + "macro.dbt.default__get_limit_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_limit_sql(sql, limit) %}\n {{ sql }}\n {% if limit is not none %}\n limit {{ limit }}\n {%- endif -%}\n{% endmacro %}", + "meta": {}, + "name": "default__get_limit_sql", + "original_file_path": "macros/adapters/show.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/show.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_limit_sql" + }, + "macro.dbt.default__get_materialized_view_configuration_changes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_materialized_view_configuration_changes(existing_relation, new_config) %}\n {{ exceptions.raise_compiler_error(\"Materialized views have not been implemented for this adapter.\") }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_materialized_view_configuration_changes", + "original_file_path": "macros/relations/materialized_view/alter.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/alter.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_materialized_view_configuration_changes" + }, + "macro.dbt.default__get_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_quoted_csv", + "macro.dbt.get_merge_update_columns", + "macro.dbt.equals" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_merge_sql(target, source, unique_key, dest_columns, incremental_predicates=none) -%}\n {%- set predicates = [] if incremental_predicates is none else [] + incremental_predicates -%}\n {%- set dest_cols_csv = get_quoted_csv(dest_columns | map(attribute=\"name\")) -%}\n {%- set merge_update_columns = config.get('merge_update_columns') -%}\n {%- set merge_exclude_columns = config.get('merge_exclude_columns') -%}\n {%- set update_columns = get_merge_update_columns(merge_update_columns, merge_exclude_columns, dest_columns) -%}\n {%- set sql_header = config.get('sql_header', none) -%}\n\n {% if unique_key %}\n {% if unique_key is sequence and unique_key is not mapping and unique_key is not string %}\n {% for key in unique_key %}\n {% set this_key_match %}\n DBT_INTERNAL_SOURCE.{{ key }} = DBT_INTERNAL_DEST.{{ key }}\n {% endset %}\n {% do predicates.append(this_key_match) %}\n {% endfor %}\n {% else %}\n {% set source_unique_key = (\"DBT_INTERNAL_SOURCE.\" ~ unique_key) | trim %}\n\t {% set target_unique_key = (\"DBT_INTERNAL_DEST.\" ~ unique_key) | trim %}\n\t {% set unique_key_match = equals(source_unique_key, target_unique_key) | trim %}\n {% do predicates.append(unique_key_match) %}\n {% endif %}\n {% else %}\n {% do predicates.append('FALSE') %}\n {% endif %}\n\n {{ sql_header if sql_header is not none }}\n\n merge into {{ target }} as DBT_INTERNAL_DEST\n using {{ source }} as DBT_INTERNAL_SOURCE\n on {{\"(\" ~ predicates | join(\") and (\") ~ \")\"}}\n\n {% if unique_key %}\n when matched then update set\n {% for column_name in update_columns -%}\n {{ column_name }} = DBT_INTERNAL_SOURCE.{{ column_name }}\n {%- if not loop.last %}, {%- endif %}\n {%- endfor %}\n {% endif %}\n\n when not matched then insert\n ({{ dest_cols_csv }})\n values\n ({{ dest_cols_csv }})\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_merge_sql", + "original_file_path": "macros/materializations/models/incremental/merge.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_merge_sql" + }, + "macro.dbt.default__get_merge_update_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_merge_update_columns(merge_update_columns, merge_exclude_columns, dest_columns) %}\n {%- set default_cols = dest_columns | map(attribute=\"quoted\") | list -%}\n\n {%- if merge_update_columns and merge_exclude_columns -%}\n {{ exceptions.raise_compiler_error(\n 'Model cannot specify merge_update_columns and merge_exclude_columns. Please update model to use only one config'\n )}}\n {%- elif merge_update_columns -%}\n {%- set update_columns = merge_update_columns -%}\n {%- elif merge_exclude_columns -%}\n {%- set update_columns = [] -%}\n {%- for column in dest_columns -%}\n {% if column.column | lower not in merge_exclude_columns | map(\"lower\") | list %}\n {%- do update_columns.append(column.quoted) -%}\n {% endif %}\n {%- endfor -%}\n {%- else -%}\n {%- set update_columns = default_cols -%}\n {%- endif -%}\n\n {{ return(update_columns) }}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_merge_update_columns", + "original_file_path": "macros/materializations/models/incremental/column_helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/column_helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_merge_update_columns" + }, + "macro.dbt.default__get_or_create_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_or_create_relation(database, schema, identifier, type) %}\n {%- set target_relation = adapter.get_relation(database=database, schema=schema, identifier=identifier) %}\n\n {% if target_relation %}\n {% do return([true, target_relation]) %}\n {% endif %}\n\n {%- set new_relation = api.Relation.create(\n database=database,\n schema=schema,\n identifier=identifier,\n type=type\n ) -%}\n {% do return([false, new_relation]) %}\n{% endmacro %}", + "meta": {}, + "name": "default__get_or_create_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_or_create_relation" + }, + "macro.dbt.default__get_powers_of_two": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_powers_of_two(upper_bound) %}\n\n {% if upper_bound <= 0 %}\n {{ exceptions.raise_compiler_error(\"upper bound must be positive\") }}\n {% endif %}\n\n {% for _ in range(1, 100) %}\n {% if upper_bound <= 2 ** loop.index %}{{ return(loop.index) }}{% endif %}\n {% endfor %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_powers_of_two", + "original_file_path": "macros/utils/generate_series.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/generate_series.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_powers_of_two" + }, + "macro.dbt.default__get_relation_last_modified": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_relation_last_modified(information_schema, relations) %}\n {{ exceptions.raise_not_implemented(\n 'get_relation_last_modified macro not implemented for adapter ' + adapter.type()) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_relation_last_modified", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_relation_last_modified" + }, + "macro.dbt.default__get_relations": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_relations() %}\n {{ exceptions.raise_not_implemented(\n 'get_relations macro not implemented for adapter '+adapter.type()) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_relations", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_relations" + }, + "macro.dbt.default__get_rename_intermediate_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_intermediate_relation", + "macro.dbt.get_rename_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__get_rename_intermediate_sql(relation) -%}\n\n -- get the standard intermediate name\n {% set intermediate_relation = make_intermediate_relation(relation) %}\n\n {{ get_rename_sql(intermediate_relation, relation.identifier) }}\n\n{%- endmacro -%}", + "meta": {}, + "name": "default__get_rename_intermediate_sql", + "original_file_path": "macros/relations/rename_intermediate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/rename_intermediate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_rename_intermediate_sql" + }, + "macro.dbt.default__get_rename_materialized_view_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_rename_materialized_view_sql(relation, new_name) %}\n {{ exceptions.raise_compiler_error(\n \"`get_rename_materialized_view_sql` has not been implemented for this adapter.\"\n ) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_rename_materialized_view_sql", + "original_file_path": "macros/relations/materialized_view/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_rename_materialized_view_sql" + }, + "macro.dbt.default__get_rename_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_rename_view_sql", + "macro.dbt.get_rename_table_sql", + "macro.dbt.get_rename_materialized_view_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__get_rename_sql(relation, new_name) -%}\n\n {%- if relation.is_view -%}\n {{ get_rename_view_sql(relation, new_name) }}\n\n {%- elif relation.is_table -%}\n {{ get_rename_table_sql(relation, new_name) }}\n\n {%- elif relation.is_materialized_view -%}\n {{ get_rename_materialized_view_sql(relation, new_name) }}\n\n {%- else -%}\n {{- exceptions.raise_compiler_error(\"`get_rename_sql` has not been implemented for: \" ~ relation.type ) -}}\n\n {%- endif -%}\n\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "default__get_rename_sql", + "original_file_path": "macros/relations/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_rename_sql" + }, + "macro.dbt.default__get_rename_table_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_rename_table_sql(relation, new_name) %}\n {{ exceptions.raise_compiler_error(\n \"`get_rename_table_sql` has not been implemented for this adapter.\"\n ) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_rename_table_sql", + "original_file_path": "macros/relations/table/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_rename_table_sql" + }, + "macro.dbt.default__get_rename_view_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_rename_view_sql(relation, new_name) %}\n {{ exceptions.raise_compiler_error(\n \"`get_rename_view_sql` has not been implemented for this adapter.\"\n ) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_rename_view_sql", + "original_file_path": "macros/relations/view/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_rename_view_sql" + }, + "macro.dbt.default__get_replace_materialized_view_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_replace_materialized_view_sql(relation, sql) %}\n {{ exceptions.raise_compiler_error(\n \"`get_replace_materialized_view_sql` has not been implemented for this adapter.\"\n ) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_replace_materialized_view_sql", + "original_file_path": "macros/relations/materialized_view/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_replace_materialized_view_sql" + }, + "macro.dbt.default__get_replace_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_replace_view_sql", + "macro.dbt.get_replace_table_sql", + "macro.dbt.get_replace_materialized_view_sql", + "macro.dbt.get_create_intermediate_sql", + "macro.dbt.get_create_backup_sql", + "macro.dbt.get_rename_intermediate_sql", + "macro.dbt.get_drop_backup_sql", + "macro.dbt.get_drop_sql", + "macro.dbt.get_create_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_replace_sql(existing_relation, target_relation, sql) %}\n\n {# /* use a create or replace statement if possible */ #}\n\n {% set is_replaceable = existing_relation.type == target_relation.type and existing_relation.can_be_replaced %}\n\n {% if is_replaceable and existing_relation.is_view %}\n {{ get_replace_view_sql(target_relation, sql) }}\n\n {% elif is_replaceable and existing_relation.is_table %}\n {{ get_replace_table_sql(target_relation, sql) }}\n\n {% elif is_replaceable and existing_relation.is_materialized_view %}\n {{ get_replace_materialized_view_sql(target_relation, sql) }}\n\n {# /* a create or replace statement is not possible, so try to stage and/or backup to be safe */ #}\n\n {# /* create target_relation as an intermediate relation, then swap it out with the existing one using a backup */ #}\n {%- elif target_relation.can_be_renamed and existing_relation.can_be_renamed -%}\n {{ get_create_intermediate_sql(target_relation, sql) }};\n {{ get_create_backup_sql(existing_relation) }};\n {{ get_rename_intermediate_sql(target_relation) }};\n {{ get_drop_backup_sql(existing_relation) }}\n\n {# /* create target_relation as an intermediate relation, then swap it out with the existing one without using a backup */ #}\n {%- elif target_relation.can_be_renamed -%}\n {{ get_create_intermediate_sql(target_relation, sql) }};\n {{ get_drop_sql(existing_relation) }};\n {{ get_rename_intermediate_sql(target_relation) }}\n\n {# /* create target_relation in place by first backing up the existing relation */ #}\n {%- elif existing_relation.can_be_renamed -%}\n {{ get_create_backup_sql(existing_relation) }};\n {{ get_create_sql(target_relation, sql) }};\n {{ get_drop_backup_sql(existing_relation) }}\n\n {# /* no renaming is allowed, so just drop and create */ #}\n {%- else -%}\n {{ get_drop_sql(existing_relation) }};\n {{ get_create_sql(target_relation, sql) }}\n\n {%- endif -%}\n\n{% endmacro %}", + "meta": {}, + "name": "default__get_replace_sql", + "original_file_path": "macros/relations/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_replace_sql" + }, + "macro.dbt.default__get_replace_table_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_replace_table_sql(relation, sql) %}\n {{ exceptions.raise_compiler_error(\n \"`get_replace_table_sql` has not been implemented for this adapter.\"\n ) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_replace_table_sql", + "original_file_path": "macros/relations/table/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_replace_table_sql" + }, + "macro.dbt.default__get_replace_view_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_replace_view_sql(relation, sql) %}\n {{ exceptions.raise_compiler_error(\n \"`get_replace_view_sql` has not been implemented for this adapter.\"\n ) }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_replace_view_sql", + "original_file_path": "macros/relations/view/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_replace_view_sql" + }, + "macro.dbt.default__get_revoke_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro default__get_revoke_sql(relation, privilege, grantees) -%}\n revoke {{ privilege }} on {{ relation.render() }} from {{ grantees | join(', ') }}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "default__get_revoke_sql", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_revoke_sql" + }, + "macro.dbt.default__get_select_subquery": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.get_column_names", + "macro.dbt.default__get_column_names" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_select_subquery(sql) %}\n select {{ adapter.dispatch('get_column_names', 'dbt')() }}\n from (\n {{ sql }}\n ) as model_subq\n{%- endmacro %}", + "meta": {}, + "name": "default__get_select_subquery", + "original_file_path": "macros/relations/table/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_select_subquery" + }, + "macro.dbt.default__get_show_grant_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_show_grant_sql(relation) %}\n show grants on {{ relation.render() }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_show_grant_sql", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_show_grant_sql" + }, + "macro.dbt.default__get_show_indexes_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_show_indexes_sql(relation) -%}\n {{ exceptions.raise_compiler_error(\"`get_show_indexes_sql has not been implemented for this adapter.\") }}\n{%- endmacro %}", + "meta": {}, + "name": "default__get_show_indexes_sql", + "original_file_path": "macros/adapters/indexes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/indexes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_show_indexes_sql" + }, + "macro.dbt.default__get_table_columns_and_constraints": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.table_columns_and_constraints" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_table_columns_and_constraints() -%}\n {{ return(table_columns_and_constraints()) }}\n{%- endmacro %}", + "meta": {}, + "name": "default__get_table_columns_and_constraints", + "original_file_path": "macros/relations/column/columns_spec_ddl.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/column/columns_spec_ddl.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_table_columns_and_constraints" + }, + "macro.dbt.default__get_test_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_test_sql(main_sql, fail_calc, warn_if, error_if, limit) -%}\n select\n {{ fail_calc }} as failures,\n {{ fail_calc }} {{ warn_if }} as should_warn,\n {{ fail_calc }} {{ error_if }} as should_error\n from (\n {{ main_sql }}\n {{ \"limit \" ~ limit if limit != none }}\n ) dbt_internal_test\n{%- endmacro %}", + "meta": {}, + "name": "default__get_test_sql", + "original_file_path": "macros/materializations/tests/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/tests/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_test_sql" + }, + "macro.dbt.default__get_true_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_true_sql() %}\n {{ return('TRUE') }}\n{% endmacro %}", + "meta": {}, + "name": "default__get_true_sql", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_true_sql" + }, + "macro.dbt.default__get_unit_test_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.string_literal" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_unit_test_sql(main_sql, expected_fixture_sql, expected_column_names) -%}\n-- Build actual result given inputs\nwith dbt_internal_unit_test_actual as (\n select\n {% for expected_column_name in expected_column_names %}{{expected_column_name}}{% if not loop.last -%},{% endif %}{%- endfor -%}, {{ dbt.string_literal(\"actual\") }} as {{ adapter.quote(\"actual_or_expected\") }}\n from (\n {{ main_sql }}\n ) _dbt_internal_unit_test_actual\n),\n-- Build expected result\ndbt_internal_unit_test_expected as (\n select\n {% for expected_column_name in expected_column_names %}{{expected_column_name}}{% if not loop.last -%}, {% endif %}{%- endfor -%}, {{ dbt.string_literal(\"expected\") }} as {{ adapter.quote(\"actual_or_expected\") }}\n from (\n {{ expected_fixture_sql }}\n ) _dbt_internal_unit_test_expected\n)\n-- Union actual and expected results\nselect * from dbt_internal_unit_test_actual\nunion all\nselect * from dbt_internal_unit_test_expected\n{%- endmacro %}", + "meta": {}, + "name": "default__get_unit_test_sql", + "original_file_path": "macros/materializations/tests/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/tests/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_unit_test_sql" + }, + "macro.dbt.default__get_where_subquery": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__get_where_subquery(relation) -%}\n {% set where = config.get('where', '') %}\n {% if where %}\n {%- set filtered -%}\n (select * from {{ relation }} where {{ where }}) dbt_subquery\n {%- endset -%}\n {% do return(filtered) %}\n {%- else -%}\n {% do return(relation) %}\n {%- endif -%}\n{%- endmacro %}", + "meta": {}, + "name": "default__get_where_subquery", + "original_file_path": "macros/materializations/tests/where_subquery.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/tests/where_subquery.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__get_where_subquery" + }, + "macro.dbt.default__handle_existing_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__handle_existing_table(full_refresh, old_relation) %}\n {{ log(\"Dropping relation \" ~ old_relation.render() ~ \" because it is of type \" ~ old_relation.type) }}\n {{ adapter.drop_relation(old_relation) }}\n{% endmacro %}", + "meta": {}, + "name": "default__handle_existing_table", + "original_file_path": "macros/relations/view/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__handle_existing_table" + }, + "macro.dbt.default__hash": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__hash(field) -%}\n md5(cast({{ field }} as {{ api.Column.translate_type('string') }}))\n{%- endmacro %}", + "meta": {}, + "name": "default__hash", + "original_file_path": "macros/utils/hash.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/hash.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__hash" + }, + "macro.dbt.default__information_schema_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__information_schema_name(database) -%}\n {%- if database -%}\n {{ database }}.INFORMATION_SCHEMA\n {%- else -%}\n INFORMATION_SCHEMA\n {%- endif -%}\n{%- endmacro %}", + "meta": {}, + "name": "default__information_schema_name", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__information_schema_name" + }, + "macro.dbt.default__intersect": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__intersect() %}\n\n intersect\n\n{% endmacro %}", + "meta": {}, + "name": "default__intersect", + "original_file_path": "macros/utils/intersect.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/intersect.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__intersect" + }, + "macro.dbt.default__last_day": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default_last_day" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__last_day(date, datepart) -%}\n {{dbt.default_last_day(date, datepart)}}\n{%- endmacro %}", + "meta": {}, + "name": "default__last_day", + "original_file_path": "macros/utils/last_day.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/last_day.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__last_day" + }, + "macro.dbt.default__length": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__length(expression) %}\n\n length(\n {{ expression }}\n )\n\n{%- endmacro -%}", + "meta": {}, + "name": "default__length", + "original_file_path": "macros/utils/length.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/length.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__length" + }, + "macro.dbt.default__list_relations_without_caching": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__list_relations_without_caching(schema_relation) %}\n {{ exceptions.raise_not_implemented(\n 'list_relations_without_caching macro not implemented for adapter '+adapter.type()) }}\n{% endmacro %}", + "meta": {}, + "name": "default__list_relations_without_caching", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__list_relations_without_caching" + }, + "macro.dbt.default__list_schemas": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.information_schema_name", + "macro.dbt.run_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__list_schemas(database) -%}\n {% set sql %}\n select distinct schema_name\n from {{ information_schema_name(database) }}.SCHEMATA\n where catalog_name ilike '{{ database }}'\n {% endset %}\n {{ return(run_query(sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "default__list_schemas", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__list_schemas" + }, + "macro.dbt.default__listagg": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__listagg(measure, delimiter_text, order_by_clause, limit_num) -%}\n\n {% if limit_num -%}\n array_to_string(\n array_slice(\n array_agg(\n {{ measure }}\n ){% if order_by_clause -%}\n within group ({{ order_by_clause }})\n {%- endif %}\n ,0\n ,{{ limit_num }}\n ),\n {{ delimiter_text }}\n )\n {%- else %}\n listagg(\n {{ measure }},\n {{ delimiter_text }}\n )\n {% if order_by_clause -%}\n within group ({{ order_by_clause }})\n {%- endif %}\n {%- endif %}\n\n{%- endmacro %}", + "meta": {}, + "name": "default__listagg", + "original_file_path": "macros/utils/listagg.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/listagg.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__listagg" + }, + "macro.dbt.default__load_csv_rows": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_batch_size", + "macro.dbt.get_seed_column_quoted_csv", + "macro.dbt.get_binding_char" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__load_csv_rows(model, agate_table) %}\n\n {% set batch_size = get_batch_size() %}\n\n {% set cols_sql = get_seed_column_quoted_csv(model, agate_table.column_names) %}\n {% set bindings = [] %}\n\n {% set statements = [] %}\n\n {% for chunk in agate_table.rows | batch(batch_size) %}\n {% set bindings = [] %}\n\n {% for row in chunk %}\n {% do bindings.extend(row) %}\n {% endfor %}\n\n {% set sql %}\n insert into {{ this.render() }} ({{ cols_sql }}) values\n {% for row in chunk -%}\n ({%- for column in agate_table.column_names -%}\n {{ get_binding_char() }}\n {%- if not loop.last%},{%- endif %}\n {%- endfor -%})\n {%- if not loop.last%},{%- endif %}\n {%- endfor %}\n {% endset %}\n\n {% do adapter.add_query(sql, bindings=bindings, abridge_sql_log=True) %}\n\n {% if loop.index0 == 0 %}\n {% do statements.append(sql) %}\n {% endif %}\n {% endfor %}\n\n {# Return SQL so we can render it out into the compiled files #}\n {{ return(statements[0]) }}\n{% endmacro %}", + "meta": {}, + "name": "default__load_csv_rows", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__load_csv_rows" + }, + "macro.dbt.default__make_backup_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__make_backup_relation(base_relation, backup_relation_type, suffix) %}\n {%- set backup_identifier = base_relation.identifier ~ suffix -%}\n {%- set backup_relation = base_relation.incorporate(\n path={\"identifier\": backup_identifier},\n type=backup_relation_type\n ) -%}\n {{ return(backup_relation) }}\n{% endmacro %}", + "meta": {}, + "name": "default__make_backup_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__make_backup_relation" + }, + "macro.dbt.default__make_intermediate_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__make_temp_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__make_intermediate_relation(base_relation, suffix) %}\n {{ return(default__make_temp_relation(base_relation, suffix)) }}\n{% endmacro %}", + "meta": {}, + "name": "default__make_intermediate_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__make_intermediate_relation" + }, + "macro.dbt.default__make_temp_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__make_temp_relation(base_relation, suffix) %}\n {%- set temp_identifier = base_relation.identifier ~ suffix -%}\n {%- set temp_relation = base_relation.incorporate(\n path={\"identifier\": temp_identifier}) -%}\n\n {{ return(temp_relation) }}\n{% endmacro %}", + "meta": {}, + "name": "default__make_temp_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__make_temp_relation" + }, + "macro.dbt.default__persist_docs": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.run_query", + "macro.dbt.alter_relation_comment", + "macro.dbt.validate_doc_columns", + "macro.dbt.alter_column_comment" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__persist_docs(relation, model, for_relation, for_columns) -%}\n {% if for_relation and config.persist_relation_docs() and model.description %}\n {% do run_query(alter_relation_comment(relation, model.description)) %}\n {% endif %}\n\n {% if for_columns and config.persist_column_docs() and model.columns %}\n {% set existing_columns = adapter.get_columns_in_relation(relation) | map(attribute=\"name\") | list %}\n {% set filtered_columns = validate_doc_columns(relation, model.columns, existing_columns) %}\n {% set alter_comment_sql = alter_column_comment(relation, filtered_columns) %}\n {% if alter_comment_sql and alter_comment_sql | trim | length > 0 %}\n {% do run_query(alter_comment_sql) %}\n {% endif %}\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "default__persist_docs", + "original_file_path": "macros/adapters/persist_docs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__persist_docs" + }, + "macro.dbt.default__position": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__position(substring_text, string_text) %}\n\n position(\n {{ substring_text }} in {{ string_text }}\n )\n\n{%- endmacro -%}", + "meta": {}, + "name": "default__position", + "original_file_path": "macros/utils/position.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/position.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__position" + }, + "macro.dbt.default__post_snapshot": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__post_snapshot(staging_relation) %}\n {# no-op #}\n{% endmacro %}", + "meta": {}, + "name": "default__post_snapshot", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__post_snapshot" + }, + "macro.dbt.default__process_schema_changes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.check_for_schema_changes", + "macro.dbt.sync_column_schemas" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__process_schema_changes(on_schema_change, source_relation, target_relation) %}\n\n {% if on_schema_change == 'ignore' %}\n\n {{ return({}) }}\n\n {% else %}\n\n {% set schema_changes_dict = check_for_schema_changes(source_relation, target_relation) %}\n\n {% if schema_changes_dict['schema_changed'] %}\n\n {% if on_schema_change == 'fail' %}\n\n {% set fail_msg %}\n The source and target schemas on this incremental model are out of sync!\n They can be reconciled in several ways:\n - set the `on_schema_change` config to either append_new_columns or sync_all_columns, depending on your situation.\n - Re-run the incremental model with `full_refresh: True` to update the target schema.\n - update the schema manually and re-run the process.\n\n Additional troubleshooting context:\n Source columns not in target: {{ schema_changes_dict['source_not_in_target'] }}\n Target columns not in source: {{ schema_changes_dict['target_not_in_source'] }}\n New column types: {{ schema_changes_dict['new_target_types'] }}\n {% endset %}\n\n {% do exceptions.raise_compiler_error(fail_msg) %}\n\n {# -- unless we ignore, run the sync operation per the config #}\n {% else %}\n\n {% do sync_column_schemas(on_schema_change, target_relation, schema_changes_dict) %}\n\n {% endif %}\n\n {% endif %}\n\n {{ return(schema_changes_dict['source_columns']) }}\n\n {% endif %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__process_schema_changes", + "original_file_path": "macros/materializations/models/incremental/on_schema_change.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/on_schema_change.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__process_schema_changes" + }, + "macro.dbt.default__refresh_materialized_view": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__refresh_materialized_view(relation) %}\n {{ exceptions.raise_compiler_error(\"`refresh_materialized_view` has not been implemented for this adapter.\") }}\n{% endmacro %}", + "meta": {}, + "name": "default__refresh_materialized_view", + "original_file_path": "macros/relations/materialized_view/refresh.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/refresh.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__refresh_materialized_view" + }, + "macro.dbt.default__rename_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__rename_relation(from_relation, to_relation) -%}\n {% set target_name = adapter.quote_as_configured(to_relation.identifier, 'identifier') %}\n {% call statement('rename_relation') -%}\n alter table {{ from_relation.render() }} rename to {{ target_name }}\n {%- endcall %}\n{% endmacro %}", + "meta": {}, + "name": "default__rename_relation", + "original_file_path": "macros/relations/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__rename_relation" + }, + "macro.dbt.default__replace": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__replace(field, old_chars, new_chars) %}\n\n replace(\n {{ field }},\n {{ old_chars }},\n {{ new_chars }}\n )\n\n\n{% endmacro %}", + "meta": {}, + "name": "default__replace", + "original_file_path": "macros/utils/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__replace" + }, + "macro.dbt.default__reset_csv_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.create_csv_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__reset_csv_table(model, full_refresh, old_relation, agate_table) %}\n {% set sql = \"\" %}\n {% if full_refresh %}\n {{ adapter.drop_relation(old_relation) }}\n {% set sql = create_csv_table(model, agate_table) %}\n {% else %}\n {{ adapter.truncate_relation(old_relation) }}\n {% set sql = \"truncate table \" ~ old_relation.render() %}\n {% endif %}\n\n {{ return(sql) }}\n{% endmacro %}", + "meta": {}, + "name": "default__reset_csv_table", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__reset_csv_table" + }, + "macro.dbt.default__resolve_model_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro default__resolve_model_name(input_model_name) -%}\n {{ input_model_name | string | replace('\"', '\\\"') }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "default__resolve_model_name", + "original_file_path": "macros/python_model/python.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/python_model/python.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__resolve_model_name" + }, + "macro.dbt.default__right": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__right(string_text, length_expression) %}\n\n right(\n {{ string_text }},\n {{ length_expression }}\n )\n\n{%- endmacro -%}", + "meta": {}, + "name": "default__right", + "original_file_path": "macros/utils/right.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/right.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__right" + }, + "macro.dbt.default__safe_cast": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__safe_cast(field, type) %}\n {# most databases don't support this function yet\n so we just need to use cast #}\n cast({{field}} as {{type}})\n{% endmacro %}", + "meta": {}, + "name": "default__safe_cast", + "original_file_path": "macros/utils/safe_cast.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/safe_cast.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__safe_cast" + }, + "macro.dbt.default__scalar_function_body_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__scalar_function_body_sql() %}\n $$\n {{ model.compiled_code }}\n $$ LANGUAGE SQL\n{% endmacro %}", + "meta": {}, + "name": "default__scalar_function_body_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__scalar_function_body_sql" + }, + "macro.dbt.default__scalar_function_create_replace_signature_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.formatted_scalar_function_args_sql", + "macro.dbt.scalar_function_volatility_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__scalar_function_create_replace_signature_sql(target_relation) %}\n CREATE OR REPLACE FUNCTION {{ target_relation.render() }} ({{ formatted_scalar_function_args_sql()}})\n RETURNS {{ model.returns.data_type }}\n {{ scalar_function_volatility_sql() }}\n AS\n{% endmacro %}", + "meta": {}, + "name": "default__scalar_function_create_replace_signature_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__scalar_function_create_replace_signature_sql" + }, + "macro.dbt.default__scalar_function_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.scalar_function_create_replace_signature_sql", + "macro.dbt.scalar_function_body_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__scalar_function_sql(target_relation) %}\n {{ scalar_function_create_replace_signature_sql(target_relation) }}\n {{ scalar_function_body_sql() }};\n{% endmacro %}", + "meta": {}, + "name": "default__scalar_function_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__scalar_function_sql" + }, + "macro.dbt.default__scalar_function_volatility_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.unsupported_volatility_warning" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__scalar_function_volatility_sql() %}\n {% set volatility = model.config.get('volatility') %}\n {% if volatility == 'deterministic' %}\n IMMUTABLE\n {% elif volatility == 'stable' %}\n STABLE\n {% elif volatility == 'non-deterministic' %}\n VOLATILE\n {% elif volatility != none %}\n {# This shouldn't happen unless a new volatility is invented #}\n {% do unsupported_volatility_warning(volatility) %}\n {% endif %}\n {# If no volatility is set, don't add anything and let the data warehouse default it #}\n{% endmacro %}", + "meta": {}, + "name": "default__scalar_function_volatility_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__scalar_function_volatility_sql" + }, + "macro.dbt.default__snapshot_get_time": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.current_timestamp" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__snapshot_get_time() %}\n {{ current_timestamp() }}\n{% endmacro %}", + "meta": {}, + "name": "default__snapshot_get_time", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__snapshot_get_time" + }, + "macro.dbt.default__snapshot_hash_arguments": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__snapshot_hash_arguments(args) -%}\n md5({%- for arg in args -%}\n coalesce(cast({{ arg }} as varchar ), '')\n {% if not loop.last %} || '|' || {% endif %}\n {%- endfor -%})\n{%- endmacro %}", + "meta": {}, + "name": "default__snapshot_hash_arguments", + "original_file_path": "macros/materializations/snapshots/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__snapshot_hash_arguments" + }, + "macro.dbt.default__snapshot_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_snapshot_table_column_names", + "macro.dbt.equals" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__snapshot_merge_sql(target, source, insert_cols) -%}\n {%- set insert_cols_csv = insert_cols | join(', ') -%}\n\n {%- set columns = config.get(\"snapshot_table_column_names\") or get_snapshot_table_column_names() -%}\n\n merge into {{ target.render() }} as DBT_INTERNAL_DEST\n using {{ source }} as DBT_INTERNAL_SOURCE\n on DBT_INTERNAL_SOURCE.{{ columns.dbt_scd_id }} = DBT_INTERNAL_DEST.{{ columns.dbt_scd_id }}\n\n when matched\n {% if config.get(\"dbt_valid_to_current\") %}\n\t{% set source_unique_key = (\"DBT_INTERNAL_DEST.\" ~ columns.dbt_valid_to) | trim %}\n\t{% set target_unique_key = config.get('dbt_valid_to_current') | trim %}\n\tand ({{ equals(source_unique_key, target_unique_key) }} or {{ source_unique_key }} is null)\n\n {% else %}\n and DBT_INTERNAL_DEST.{{ columns.dbt_valid_to }} is null\n {% endif %}\n and DBT_INTERNAL_SOURCE.dbt_change_type in ('update', 'delete')\n then update\n set {{ columns.dbt_valid_to }} = DBT_INTERNAL_SOURCE.{{ columns.dbt_valid_to }}\n\n when not matched\n and DBT_INTERNAL_SOURCE.dbt_change_type = 'insert'\n then insert ({{ insert_cols_csv }})\n values ({{ insert_cols_csv }})\n\n{% endmacro %}", + "meta": {}, + "name": "default__snapshot_merge_sql", + "original_file_path": "macros/materializations/snapshots/snapshot_merge.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/snapshot_merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__snapshot_merge_sql" + }, + "macro.dbt.default__snapshot_staging_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_snapshot_table_column_names", + "macro.dbt.snapshot_hash_arguments", + "macro.dbt.snapshot_get_time", + "macro.dbt.unique_key_fields", + "macro.dbt.equals", + "macro.dbt.get_dbt_valid_to_current", + "macro.dbt.unique_key_join_on", + "macro.dbt.unique_key_is_null", + "macro.dbt.unique_key_is_not_null", + "macro.dbt.get_list_of_column_names", + "macro.dbt.get_columns_in_relation", + "macro.dbt.get_column_schema_from_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__snapshot_staging_table(strategy, source_sql, target_relation) -%}\n {% set columns = config.get('snapshot_table_column_names') or get_snapshot_table_column_names() %}\n {% if strategy.hard_deletes == 'new_record' %}\n {% set new_scd_id = snapshot_hash_arguments([columns.dbt_scd_id, snapshot_get_time()]) %}\n {% endif %}\n with snapshot_query as (\n\n {{ source_sql }}\n\n ),\n\n snapshotted_data as (\n\n select *, {{ unique_key_fields(strategy.unique_key) }}\n from {{ target_relation }}\n where\n {% if config.get('dbt_valid_to_current') %}\n\t\t{% set source_unique_key = columns.dbt_valid_to | trim %}\n\t\t{% set target_unique_key = config.get('dbt_valid_to_current') | trim %}\n\n\t\t{# The exact equals semantics between NULL values depends on the current behavior flag set. Also, update records if the source field is null #}\n ( {{ equals(source_unique_key, target_unique_key) }} or {{ source_unique_key }} is null )\n {% else %}\n {{ columns.dbt_valid_to }} is null\n {% endif %}\n\n ),\n\n insertions_source_data as (\n\n select *, {{ unique_key_fields(strategy.unique_key) }},\n {{ strategy.updated_at }} as {{ columns.dbt_updated_at }},\n {{ strategy.updated_at }} as {{ columns.dbt_valid_from }},\n {{ get_dbt_valid_to_current(strategy, columns) }},\n {{ strategy.scd_id }} as {{ columns.dbt_scd_id }}\n\n from snapshot_query\n ),\n\n updates_source_data as (\n\n select *, {{ unique_key_fields(strategy.unique_key) }},\n {{ strategy.updated_at }} as {{ columns.dbt_updated_at }},\n {{ strategy.updated_at }} as {{ columns.dbt_valid_from }},\n {{ strategy.updated_at }} as {{ columns.dbt_valid_to }}\n\n from snapshot_query\n ),\n\n {%- if strategy.hard_deletes == 'invalidate' or strategy.hard_deletes == 'new_record' %}\n\n deletes_source_data as (\n\n select *, {{ unique_key_fields(strategy.unique_key) }}\n from snapshot_query\n ),\n {% endif %}\n\n insertions as (\n\n select\n 'insert' as dbt_change_type,\n source_data.*\n {%- if strategy.hard_deletes == 'new_record' -%}\n ,'False' as {{ columns.dbt_is_deleted }}\n {%- endif %}\n\n from insertions_source_data as source_data\n left outer join snapshotted_data\n on {{ unique_key_join_on(strategy.unique_key, \"snapshotted_data\", \"source_data\") }}\n where {{ unique_key_is_null(strategy.unique_key, \"snapshotted_data\") }}\n or ({{ unique_key_is_not_null(strategy.unique_key, \"snapshotted_data\") }} and (\n {{ strategy.row_changed }} {%- if strategy.hard_deletes == 'new_record' -%} or snapshotted_data.{{ columns.dbt_is_deleted }} = 'True' {% endif %}\n )\n\n )\n\n ),\n\n updates as (\n\n select\n 'update' as dbt_change_type,\n source_data.*,\n snapshotted_data.{{ columns.dbt_scd_id }}\n {%- if strategy.hard_deletes == 'new_record' -%}\n , snapshotted_data.{{ columns.dbt_is_deleted }}\n {%- endif %}\n\n from updates_source_data as source_data\n join snapshotted_data\n on {{ unique_key_join_on(strategy.unique_key, \"snapshotted_data\", \"source_data\") }}\n where (\n {{ strategy.row_changed }} {%- if strategy.hard_deletes == 'new_record' -%} or snapshotted_data.{{ columns.dbt_is_deleted }} = 'True' {% endif %}\n )\n )\n\n {%- if strategy.hard_deletes == 'invalidate' or strategy.hard_deletes == 'new_record' %}\n ,\n deletes as (\n\n select\n 'delete' as dbt_change_type,\n source_data.*,\n {{ snapshot_get_time() }} as {{ columns.dbt_valid_from }},\n {{ snapshot_get_time() }} as {{ columns.dbt_updated_at }},\n {{ snapshot_get_time() }} as {{ columns.dbt_valid_to }},\n snapshotted_data.{{ columns.dbt_scd_id }}\n {%- if strategy.hard_deletes == 'new_record' -%}\n , snapshotted_data.{{ columns.dbt_is_deleted }}\n {%- endif %}\n from snapshotted_data\n left join deletes_source_data as source_data\n on {{ unique_key_join_on(strategy.unique_key, \"snapshotted_data\", \"source_data\") }}\n where {{ unique_key_is_null(strategy.unique_key, \"source_data\") }}\n\n {%- if strategy.hard_deletes == 'new_record' %}\n and not (\n --avoid updating the record's valid_to if the latest entry is marked as deleted\n snapshotted_data.{{ columns.dbt_is_deleted }} = 'True'\n and\n {% if config.get('dbt_valid_to_current') -%}\n snapshotted_data.{{ columns.dbt_valid_to }} = {{ config.get('dbt_valid_to_current') }}\n {%- else -%}\n snapshotted_data.{{ columns.dbt_valid_to }} is null\n {%- endif %}\n )\n {%- endif %}\n )\n {%- endif %}\n\n {%- if strategy.hard_deletes == 'new_record' %}\n {% set snapshotted_cols = get_list_of_column_names(get_columns_in_relation(target_relation)) %}\n {% set source_sql_cols = get_column_schema_from_query(source_sql) %}\n ,\n deletion_records as (\n\n select\n 'insert' as dbt_change_type,\n {#/*\n If a column has been added to the source it won't yet exist in the\n snapshotted table so we insert a null value as a placeholder for the column.\n */#}\n {%- for col in source_sql_cols -%}\n {%- if col.name in snapshotted_cols -%}\n snapshotted_data.{{ adapter.quote(col.column) }},\n {%- else -%}\n NULL as {{ adapter.quote(col.column) }},\n {%- endif -%}\n {% endfor -%}\n {%- if strategy.unique_key | is_list -%}\n {%- for key in strategy.unique_key -%}\n snapshotted_data.{{ key }} as dbt_unique_key_{{ loop.index }},\n {% endfor -%}\n {%- else -%}\n snapshotted_data.dbt_unique_key as dbt_unique_key,\n {% endif -%}\n {{ snapshot_get_time() }} as {{ columns.dbt_valid_from }},\n {{ snapshot_get_time() }} as {{ columns.dbt_updated_at }},\n snapshotted_data.{{ columns.dbt_valid_to }} as {{ columns.dbt_valid_to }},\n {{ new_scd_id }} as {{ columns.dbt_scd_id }},\n 'True' as {{ columns.dbt_is_deleted }}\n from snapshotted_data\n left join deletes_source_data as source_data\n on {{ unique_key_join_on(strategy.unique_key, \"snapshotted_data\", \"source_data\") }}\n where {{ unique_key_is_null(strategy.unique_key, \"source_data\") }}\n and not (\n --avoid inserting a new record if the latest one is marked as deleted\n snapshotted_data.{{ columns.dbt_is_deleted }} = 'True'\n and\n {% if config.get('dbt_valid_to_current') -%}\n snapshotted_data.{{ columns.dbt_valid_to }} = {{ config.get('dbt_valid_to_current') }}\n {%- else -%}\n snapshotted_data.{{ columns.dbt_valid_to }} is null\n {%- endif %}\n )\n\n )\n {%- endif %}\n\n select * from insertions\n union all\n select * from updates\n {%- if strategy.hard_deletes == 'invalidate' or strategy.hard_deletes == 'new_record' %}\n union all\n select * from deletes\n {%- endif %}\n {%- if strategy.hard_deletes == 'new_record' %}\n union all\n select * from deletion_records\n {%- endif %}\n\n\n{%- endmacro %}", + "meta": {}, + "name": "default__snapshot_staging_table", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__snapshot_staging_table" + }, + "macro.dbt.default__snapshot_string_as_time": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__snapshot_string_as_time(timestamp) %}\n {% do exceptions.raise_not_implemented(\n 'snapshot_string_as_time macro not implemented for adapter '+adapter.type()\n ) %}\n{% endmacro %}", + "meta": {}, + "name": "default__snapshot_string_as_time", + "original_file_path": "macros/materializations/snapshots/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__snapshot_string_as_time" + }, + "macro.dbt.default__split_part": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__split_part(string_text, delimiter_text, part_number) %}\n\n split_part(\n {{ string_text }},\n {{ delimiter_text }},\n {{ part_number }}\n )\n\n{% endmacro %}", + "meta": {}, + "name": "default__split_part", + "original_file_path": "macros/utils/split_part.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/split_part.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__split_part" + }, + "macro.dbt.default__string_literal": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__string_literal(value) -%}\n '{{ value }}'\n{%- endmacro %}", + "meta": {}, + "name": "default__string_literal", + "original_file_path": "macros/utils/literal.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/literal.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__string_literal" + }, + "macro.dbt.default__support_multiple_grantees_per_dcl_statement": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro default__support_multiple_grantees_per_dcl_statement() -%}\n {{ return(True) }}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "default__support_multiple_grantees_per_dcl_statement", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__support_multiple_grantees_per_dcl_statement" + }, + "macro.dbt.default__sync_column_schemas": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.alter_relation_add_remove_columns", + "macro.dbt.alter_column_type" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__sync_column_schemas(on_schema_change, target_relation, schema_changes_dict) %}\n\n {%- set add_to_target_arr = schema_changes_dict['source_not_in_target'] -%}\n {%- set remove_from_target_arr = schema_changes_dict['target_not_in_source'] -%}\n {%- set new_target_types = schema_changes_dict['new_target_types'] -%}\n\n {%- if on_schema_change == 'append_new_columns'-%}\n {%- if add_to_target_arr | length > 0 -%}\n {%- do alter_relation_add_remove_columns(target_relation, add_to_target_arr, none) -%}\n {%- endif -%}\n\n {% elif on_schema_change == 'sync_all_columns' %}\n\n {% if add_to_target_arr | length > 0 or remove_from_target_arr | length > 0 %}\n {%- do alter_relation_add_remove_columns(target_relation, add_to_target_arr, remove_from_target_arr) -%}\n {% endif %}\n\n {% if new_target_types != [] %}\n {% for ntt in new_target_types %}\n {% set column_name = ntt['column_name'] %}\n {% set new_type = ntt['new_type'] %}\n {% do alter_column_type(target_relation, column_name, new_type) %}\n {% endfor %}\n {% endif %}\n\n {% endif %}\n\n {% set schema_change_message %}\n In {{ target_relation }}:\n Schema change approach: {{ on_schema_change }}\n Columns added: {{ add_to_target_arr }}\n Columns removed: {{ remove_from_target_arr }}\n Data types changed: {{ new_target_types }}\n {% endset %}\n\n {% do log(schema_change_message) %}\n\n{% endmacro %}", + "meta": {}, + "name": "default__sync_column_schemas", + "original_file_path": "macros/materializations/models/incremental/on_schema_change.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/on_schema_change.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__sync_column_schemas" + }, + "macro.dbt.default__test_accepted_values": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__test_accepted_values(model, column_name, values, quote=True) %}\n\nwith all_values as (\n\n select\n {{ column_name }} as value_field,\n count(*) as n_records\n\n from {{ model }}\n group by {{ column_name }}\n\n)\n\nselect *\nfrom all_values\nwhere value_field not in (\n {% for value in values -%}\n {% if quote -%}\n '{{ value }}'\n {%- else -%}\n {{ value }}\n {%- endif -%}\n {%- if not loop.last -%},{%- endif %}\n {%- endfor %}\n)\n\n{% endmacro %}", + "meta": {}, + "name": "default__test_accepted_values", + "original_file_path": "macros/generic_test_sql/accepted_values.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/generic_test_sql/accepted_values.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__test_accepted_values" + }, + "macro.dbt.default__test_not_null": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.should_store_failures" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__test_not_null(model, column_name) %}\n\n{% set column_list = '*' if should_store_failures() else column_name %}\n\nselect {{ column_list }}\nfrom {{ model }}\nwhere {{ column_name }} is null\n\n{% endmacro %}", + "meta": {}, + "name": "default__test_not_null", + "original_file_path": "macros/generic_test_sql/not_null.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/generic_test_sql/not_null.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__test_not_null" + }, + "macro.dbt.default__test_relationships": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__test_relationships(model, column_name, to, field) %}\n\nwith child as (\n select {{ column_name }} as from_field\n from {{ model }}\n where {{ column_name }} is not null\n),\n\nparent as (\n select {{ field }} as to_field\n from {{ to }}\n)\n\nselect\n from_field\n\nfrom child\nleft join parent\n on child.from_field = parent.to_field\n\nwhere parent.to_field is null\n\n{% endmacro %}", + "meta": {}, + "name": "default__test_relationships", + "original_file_path": "macros/generic_test_sql/relationships.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/generic_test_sql/relationships.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__test_relationships" + }, + "macro.dbt.default__test_unique": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__test_unique(model, column_name) %}\n\nselect\n {{ column_name }} as unique_field,\n count(*) as n_records\n\nfrom {{ model }}\nwhere {{ column_name }} is not null\ngroup by {{ column_name }}\nhaving count(*) > 1\n\n{% endmacro %}", + "meta": {}, + "name": "default__test_unique", + "original_file_path": "macros/generic_test_sql/unique.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/generic_test_sql/unique.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__test_unique" + }, + "macro.dbt.default__truncate_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__truncate_relation(relation) -%}\n {% call statement('truncate_relation') -%}\n truncate table {{ relation.render() }}\n {%- endcall %}\n{% endmacro %}", + "meta": {}, + "name": "default__truncate_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__truncate_relation" + }, + "macro.dbt.default__type_bigint": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__type_bigint() %}\n {{ return(api.Column.translate_type(\"bigint\")) }}\n{% endmacro %}", + "meta": {}, + "name": "default__type_bigint", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__type_bigint" + }, + "macro.dbt.default__type_boolean": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__type_boolean() -%}\n {{ return(api.Column.translate_type(\"boolean\")) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "default__type_boolean", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__type_boolean" + }, + "macro.dbt.default__type_float": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__type_float() %}\n {{ return(api.Column.translate_type(\"float\")) }}\n{% endmacro %}", + "meta": {}, + "name": "default__type_float", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__type_float" + }, + "macro.dbt.default__type_int": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__type_int() -%}\n {{ return(api.Column.translate_type(\"integer\")) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "default__type_int", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__type_int" + }, + "macro.dbt.default__type_numeric": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__type_numeric() %}\n {{ return(api.Column.numeric_type(\"numeric\", 28, 6)) }}\n{% endmacro %}", + "meta": {}, + "name": "default__type_numeric", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__type_numeric" + }, + "macro.dbt.default__type_string": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__type_string() %}\n {{ return(api.Column.translate_type(\"string\")) }}\n{% endmacro %}", + "meta": {}, + "name": "default__type_string", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__type_string" + }, + "macro.dbt.default__type_timestamp": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__type_timestamp() %}\n {{ return(api.Column.translate_type(\"timestamp\")) }}\n{% endmacro %}", + "meta": {}, + "name": "default__type_timestamp", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__type_timestamp" + }, + "macro.dbt.default__unsupported_volatility_warning": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__unsupported_volatility_warning(volatility) %}\n {% set msg = \"Found `\" ~ volatility ~ \"` volatility specified on function `\" ~ model.name ~ \"`. This volatility is not supported by \" ~ adapter.type() ~ \", and will be ignored\" %}\n {% do exceptions.warn(msg) %}\n{% endmacro %}", + "meta": {}, + "name": "default__unsupported_volatility_warning", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__unsupported_volatility_warning" + }, + "macro.dbt.default__validate_fixture_rows": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro default__validate_fixture_rows(rows, row_number) -%}\n {# This is an abstract method for adapter overrides as needed #}\n{%- endmacro -%}", + "meta": {}, + "name": "default__validate_fixture_rows", + "original_file_path": "macros/unit_test_sql/get_fixture_sql.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/unit_test_sql/get_fixture_sql.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__validate_fixture_rows" + }, + "macro.dbt.default__validate_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro default__validate_sql(sql) -%}\n {% call statement('validate_sql') -%}\n explain {{ sql }}\n {% endcall %}\n {{ return(load_result('validate_sql')) }}\n{% endmacro %}", + "meta": {}, + "name": "default__validate_sql", + "original_file_path": "macros/adapters/validate_sql.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/validate_sql.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default__validate_sql" + }, + "macro.dbt.default_last_day": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.dateadd", + "macro.dbt.date_trunc" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro default_last_day(date, datepart) -%}\n cast(\n {{dbt.dateadd('day', '-1',\n dbt.dateadd(datepart, '1', dbt.date_trunc(datepart, date))\n )}}\n as date)\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "default_last_day", + "original_file_path": "macros/utils/last_day.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/last_day.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.default_last_day" + }, + "macro.dbt.diff_column_data_types": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__diff_column_data_types" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro diff_column_data_types(source_columns, target_columns) %}\n {{ return(adapter.dispatch('diff_column_data_types', 'dbt')(source_columns, target_columns)) }}\n{% endmacro %}", + "meta": {}, + "name": "diff_column_data_types", + "original_file_path": "macros/materializations/models/incremental/column_helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/column_helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.diff_column_data_types" + }, + "macro.dbt.diff_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro diff_columns(source_columns, target_columns) %}\n\n {% set result = [] %}\n {% set source_names = source_columns | map(attribute = 'column') | list %}\n {% set target_names = target_columns | map(attribute = 'column') | list %}\n\n {# --check whether the name attribute exists in the target - this does not perform a data type check #}\n {% for sc in source_columns %}\n {% if sc.name not in target_names %}\n {{ result.append(sc) }}\n {% endif %}\n {% endfor %}\n\n {{ return(result) }}\n\n{% endmacro %}", + "meta": {}, + "name": "diff_columns", + "original_file_path": "macros/materializations/models/incremental/column_helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/column_helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.diff_columns" + }, + "macro.dbt.drop_materialized_view": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__drop_materialized_view" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro drop_materialized_view(relation) -%}\n {{- adapter.dispatch('drop_materialized_view', 'dbt')(relation) -}}\n{%- endmacro %}", + "meta": {}, + "name": "drop_materialized_view", + "original_file_path": "macros/relations/materialized_view/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.drop_materialized_view" + }, + "macro.dbt.drop_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__drop_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro drop_relation(relation) -%}\n {{ return(adapter.dispatch('drop_relation', 'dbt')(relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "drop_relation", + "original_file_path": "macros/relations/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.drop_relation" + }, + "macro.dbt.drop_relation_if_exists": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro drop_relation_if_exists(relation) %}\n {% if relation is not none %}\n {{ adapter.drop_relation(relation) }}\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "drop_relation_if_exists", + "original_file_path": "macros/relations/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.drop_relation_if_exists" + }, + "macro.dbt.drop_schema": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__drop_schema" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro drop_schema(relation) -%}\n {{ adapter.dispatch('drop_schema', 'dbt')(relation) }}\n{% endmacro %}", + "meta": {}, + "name": "drop_schema", + "original_file_path": "macros/adapters/schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.drop_schema" + }, + "macro.dbt.drop_schema_named": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__drop_schema_named" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro drop_schema_named(schema_name) %}\n {{ return(adapter.dispatch('drop_schema_named', 'dbt') (schema_name)) }}\n{% endmacro %}", + "meta": {}, + "name": "drop_schema_named", + "original_file_path": "macros/relations/schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.drop_schema_named" + }, + "macro.dbt.drop_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__drop_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro drop_table(relation) -%}\n {{- adapter.dispatch('drop_table', 'dbt')(relation) -}}\n{%- endmacro %}", + "meta": {}, + "name": "drop_table", + "original_file_path": "macros/relations/table/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.drop_table" + }, + "macro.dbt.drop_view": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__drop_view" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro drop_view(relation) -%}\n {{- adapter.dispatch('drop_view', 'dbt')(relation) -}}\n{%- endmacro %}", + "meta": {}, + "name": "drop_view", + "original_file_path": "macros/relations/view/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.drop_view" + }, + "macro.dbt.equals": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__equals" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro equals(expr1, expr2) %}\n {{ return(adapter.dispatch('equals', 'dbt') (expr1, expr2)) }}\n{%- endmacro %}", + "meta": {}, + "name": "equals", + "original_file_path": "macros/utils/equals.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/equals.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.equals" + }, + "macro.dbt.escape_single_quotes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__escape_single_quotes" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro escape_single_quotes(expression) %}\n {{ return(adapter.dispatch('escape_single_quotes', 'dbt') (expression)) }}\n{% endmacro %}", + "meta": {}, + "name": "escape_single_quotes", + "original_file_path": "macros/utils/escape_single_quotes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/escape_single_quotes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.escape_single_quotes" + }, + "macro.dbt.except": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__except" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro except() %}\n {{ return(adapter.dispatch('except', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "except", + "original_file_path": "macros/utils/except.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/except.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.except" + }, + "macro.dbt.format_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__format_column" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro format_columns(columns) %}\n {% set formatted_columns = [] %}\n {% for column in columns %}\n {%- set formatted_column = adapter.dispatch('format_column', 'dbt')(column) -%}\n {%- do formatted_columns.append(formatted_column) -%}\n {% endfor %}\n {{ return(formatted_columns) }}\n{% endmacro %}", + "meta": {}, + "name": "format_columns", + "original_file_path": "macros/relations/column/columns_spec_ddl.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/column/columns_spec_ddl.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.format_columns" + }, + "macro.dbt.format_row": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.string_literal", + "macro.dbt.escape_single_quotes", + "macro.dbt.safe_cast" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro format_row(row, column_name_to_data_types) -%}\n {#-- generate case-insensitive formatted row --#}\n {% set formatted_row = {} %}\n {%- for column_name, column_value in row.items() -%}\n {% set column_name = column_name|lower %}\n\n {%- if column_name not in column_name_to_data_types %}\n {#-- if user-provided row contains column name that relation does not contain, raise an error --#}\n {% set fixture_name = \"expected output\" if model.resource_type == 'unit_test' else (\"'\" ~ model.name ~ \"'\") %}\n {{ exceptions.raise_compiler_error(\n \"Invalid column name: '\" ~ column_name ~ \"' in unit test fixture for \" ~ fixture_name ~ \".\"\n \"\\nAccepted columns for \" ~ fixture_name ~ \" are: \" ~ (column_name_to_data_types.keys()|list)\n ) }}\n {%- endif -%}\n\n {%- set column_type = column_name_to_data_types[column_name] %}\n\n {#-- For string fixture values, strip varchar length to prevent silent truncation (GH-11974) --#}\n {%- if column_value is string and 'varying' in column_type -%}\n {%- set column_type = column_type.split('(')[0] -%}\n {%- endif -%}\n\n {#-- sanitize column_value: wrap yaml strings in quotes, apply cast --#}\n {%- set column_value_clean = column_value -%}\n {%- if column_value is string -%}\n {%- set column_value_clean = dbt.string_literal(dbt.escape_single_quotes(column_value)) -%}\n {%- elif column_value is none -%}\n {%- set column_value_clean = 'null' -%}\n {%- endif -%}\n\n {%- set row_update = {column_name: safe_cast(column_value_clean, column_type) } -%}\n {%- do formatted_row.update(row_update) -%}\n {%- endfor -%}\n {{ return(formatted_row) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "format_row", + "original_file_path": "macros/unit_test_sql/get_fixture_sql.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/unit_test_sql/get_fixture_sql.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.format_row" + }, + "macro.dbt.formatted_scalar_function_args_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__formatted_scalar_function_args_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro formatted_scalar_function_args_sql() %}\n {{ return(adapter.dispatch('formatted_scalar_function_args_sql', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "formatted_scalar_function_args_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.formatted_scalar_function_args_sql" + }, + "macro.dbt.function_execute_build_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__function_execute_build_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro function_execute_build_sql(build_sql, existing_relation, target_relation) %}\n {{ return(adapter.dispatch('function_execute_build_sql', 'dbt')(build_sql, existing_relation, target_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "function_execute_build_sql", + "original_file_path": "macros/materializations/functions/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.function_execute_build_sql" + }, + "macro.dbt.generate_alias_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__generate_alias_name" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro generate_alias_name(custom_alias_name=none, node=none) -%}\n {% do return(adapter.dispatch('generate_alias_name', 'dbt')(custom_alias_name, node)) %}\n{%- endmacro %}", + "meta": {}, + "name": "generate_alias_name", + "original_file_path": "macros/get_custom_name/get_custom_alias.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/get_custom_name/get_custom_alias.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.generate_alias_name" + }, + "macro.dbt.generate_database_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__generate_database_name" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro generate_database_name(custom_database_name=none, node=none) -%}\n {% do return(adapter.dispatch('generate_database_name', 'dbt')(custom_database_name, node)) %}\n{%- endmacro %}", + "meta": {}, + "name": "generate_database_name", + "original_file_path": "macros/get_custom_name/get_custom_database.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/get_custom_name/get_custom_database.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.generate_database_name" + }, + "macro.dbt.generate_schema_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__generate_schema_name" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro generate_schema_name(custom_schema_name=none, node=none) -%}\n {{ return(adapter.dispatch('generate_schema_name', 'dbt')(custom_schema_name, node)) }}\n{% endmacro %}", + "meta": {}, + "name": "generate_schema_name", + "original_file_path": "macros/get_custom_name/get_custom_schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/get_custom_name/get_custom_schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.generate_schema_name" + }, + "macro.dbt.generate_schema_name_for_env": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro generate_schema_name_for_env(custom_schema_name, node) -%}\n\n {%- set default_schema = target.schema -%}\n {%- if target.name == 'prod' and custom_schema_name is not none -%}\n\n {{ custom_schema_name | trim }}\n\n {%- else -%}\n\n {{ default_schema }}\n\n {%- endif -%}\n\n{%- endmacro %}", + "meta": {}, + "name": "generate_schema_name_for_env", + "original_file_path": "macros/get_custom_name/get_custom_schema.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/get_custom_name/get_custom_schema.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.generate_schema_name_for_env" + }, + "macro.dbt.generate_series": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__generate_series" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro generate_series(upper_bound) %}\n {{ return(adapter.dispatch('generate_series', 'dbt')(upper_bound)) }}\n{% endmacro %}", + "meta": {}, + "name": "generate_series", + "original_file_path": "macros/utils/generate_series.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/generate_series.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.generate_series" + }, + "macro.dbt.get_aggregate_function_create_replace_signature": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_aggregate_function_create_replace_signature" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_aggregate_function_create_replace_signature(target_relation) %}\n {{ return(adapter.dispatch('get_aggregate_function_create_replace_signature', 'dbt')(target_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_aggregate_function_create_replace_signature", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_aggregate_function_create_replace_signature" + }, + "macro.dbt.get_aggregate_function_volatility_specifier": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_aggregate_function_volatility_specifier" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_aggregate_function_volatility_specifier() %}\n {{ return(adapter.dispatch('get_aggregate_function_volatility_specifier', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "get_aggregate_function_volatility_specifier", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_aggregate_function_volatility_specifier" + }, + "macro.dbt.get_alter_materialized_view_as_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_alter_materialized_view_as_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_alter_materialized_view_as_sql(\n relation,\n configuration_changes,\n sql,\n existing_relation,\n backup_relation,\n intermediate_relation\n) %}\n {{- log('Applying ALTER to: ' ~ relation) -}}\n {{- adapter.dispatch('get_alter_materialized_view_as_sql', 'dbt')(\n relation,\n configuration_changes,\n sql,\n existing_relation,\n backup_relation,\n intermediate_relation\n ) -}}\n{% endmacro %}", + "meta": {}, + "name": "get_alter_materialized_view_as_sql", + "original_file_path": "macros/relations/materialized_view/alter.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/alter.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_alter_materialized_view_as_sql" + }, + "macro.dbt.get_assert_columns_equivalent": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_assert_columns_equivalent" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro get_assert_columns_equivalent(sql) -%}\n {{ adapter.dispatch('get_assert_columns_equivalent', 'dbt')(sql) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "get_assert_columns_equivalent", + "original_file_path": "macros/relations/column/columns_spec_ddl.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/column/columns_spec_ddl.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_assert_columns_equivalent" + }, + "macro.dbt.get_batch_size": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_batch_size" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_batch_size() -%}\n {{ return(adapter.dispatch('get_batch_size', 'dbt')()) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_batch_size", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_batch_size" + }, + "macro.dbt.get_binding_char": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_binding_char" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_binding_char() -%}\n {{ adapter.dispatch('get_binding_char', 'dbt')() }}\n{%- endmacro %}", + "meta": {}, + "name": "get_binding_char", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_binding_char" + }, + "macro.dbt.get_catalog": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_catalog" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_catalog(information_schema, schemas) -%}\n {{ return(adapter.dispatch('get_catalog', 'dbt')(information_schema, schemas)) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_catalog", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_catalog" + }, + "macro.dbt.get_catalog_for_single_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_catalog_for_single_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_catalog_for_single_relation(relation) %}\n {{ return(adapter.dispatch('get_catalog_for_single_relation', 'dbt')(relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_catalog_for_single_relation", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_catalog_for_single_relation" + }, + "macro.dbt.get_catalog_relations": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_catalog_relations" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_catalog_relations(information_schema, relations) -%}\n {{ return(adapter.dispatch('get_catalog_relations', 'dbt')(information_schema, relations)) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_catalog_relations", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_catalog_relations" + }, + "macro.dbt.get_column_schema_from_query": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_empty_subquery_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_column_schema_from_query(select_sql, select_sql_header=none) -%}\n {% set columns = [] %}\n {# -- Using an 'empty subquery' here to get the same schema as the given select_sql statement, without necessitating a data scan.#}\n {% set sql = get_empty_subquery_sql(select_sql, select_sql_header) %}\n {% set column_schema = adapter.get_column_schema_from_query(sql) %}\n {{ return(column_schema) }}\n{% endmacro %}", + "meta": {}, + "name": "get_column_schema_from_query", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_column_schema_from_query" + }, + "macro.dbt.get_columns_in_query": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_columns_in_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_columns_in_query(select_sql) -%}\n {{ return(adapter.dispatch('get_columns_in_query', 'dbt')(select_sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_columns_in_query", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_columns_in_query" + }, + "macro.dbt.get_columns_in_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_columns_in_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_columns_in_relation(relation) -%}\n {{ return(adapter.dispatch('get_columns_in_relation', 'dbt')(relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_columns_in_relation", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_columns_in_relation" + }, + "macro.dbt.get_create_backup_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_create_backup_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro get_create_backup_sql(relation) -%}\n {{- log('Applying CREATE BACKUP to: ' ~ relation) -}}\n {{- adapter.dispatch('get_create_backup_sql', 'dbt')(relation) -}}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "get_create_backup_sql", + "original_file_path": "macros/relations/create_backup.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/create_backup.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_create_backup_sql" + }, + "macro.dbt.get_create_index_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_create_index_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_create_index_sql(relation, index_dict) -%}\n {{ return(adapter.dispatch('get_create_index_sql', 'dbt')(relation, index_dict)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_create_index_sql", + "original_file_path": "macros/adapters/indexes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/indexes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_create_index_sql" + }, + "macro.dbt.get_create_intermediate_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_create_intermediate_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro get_create_intermediate_sql(relation, sql) -%}\n {{- log('Applying CREATE INTERMEDIATE to: ' ~ relation) -}}\n {{- adapter.dispatch('get_create_intermediate_sql', 'dbt')(relation, sql) -}}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "get_create_intermediate_sql", + "original_file_path": "macros/relations/create_intermediate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/create_intermediate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_create_intermediate_sql" + }, + "macro.dbt.get_create_materialized_view_as_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_create_materialized_view_as_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_create_materialized_view_as_sql(relation, sql) -%}\n {{- adapter.dispatch('get_create_materialized_view_as_sql', 'dbt')(relation, sql) -}}\n{%- endmacro %}", + "meta": {}, + "name": "get_create_materialized_view_as_sql", + "original_file_path": "macros/relations/materialized_view/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_create_materialized_view_as_sql" + }, + "macro.dbt.get_create_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_create_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro get_create_sql(relation, sql) -%}\n {{- log('Applying CREATE to: ' ~ relation) -}}\n {{- adapter.dispatch('get_create_sql', 'dbt')(relation, sql) -}}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "get_create_sql", + "original_file_path": "macros/relations/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_create_sql" + }, + "macro.dbt.get_create_table_as_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_create_table_as_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_create_table_as_sql(temporary, relation, sql) -%}\n {{ adapter.dispatch('get_create_table_as_sql', 'dbt')(temporary, relation, sql) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_create_table_as_sql", + "original_file_path": "macros/relations/table/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_create_table_as_sql" + }, + "macro.dbt.get_create_view_as_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_create_view_as_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_create_view_as_sql(relation, sql) -%}\n {{ adapter.dispatch('get_create_view_as_sql', 'dbt')(relation, sql) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_create_view_as_sql", + "original_file_path": "macros/relations/view/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_create_view_as_sql" + }, + "macro.dbt.get_csv_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_csv_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_csv_sql(create_or_truncate_sql, insert_sql) %}\n {{ adapter.dispatch('get_csv_sql', 'dbt')(create_or_truncate_sql, insert_sql) }}\n{% endmacro %}", + "meta": {}, + "name": "get_csv_sql", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_csv_sql" + }, + "macro.dbt.get_dbt_valid_to_current": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_dbt_valid_to_current(strategy, columns) %}\n {% set dbt_valid_to_current = config.get('dbt_valid_to_current') or \"null\" %}\n coalesce(nullif({{ strategy.updated_at }}, {{ strategy.updated_at }}), {{dbt_valid_to_current}})\n as {{ columns.dbt_valid_to }}\n{% endmacro %}", + "meta": {}, + "name": "get_dbt_valid_to_current", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_dbt_valid_to_current" + }, + "macro.dbt.get_dcl_statement_list": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_dcl_statement_list" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_dcl_statement_list(relation, grant_config, get_dcl_macro) %}\n {{ return(adapter.dispatch('get_dcl_statement_list', 'dbt')(relation, grant_config, get_dcl_macro)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_dcl_statement_list", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_dcl_statement_list" + }, + "macro.dbt.get_delete_insert_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_delete_insert_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_delete_insert_merge_sql(target, source, unique_key, dest_columns, incremental_predicates) -%}\n {{ adapter.dispatch('get_delete_insert_merge_sql', 'dbt')(target, source, unique_key, dest_columns, incremental_predicates) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_delete_insert_merge_sql", + "original_file_path": "macros/materializations/models/incremental/merge.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_delete_insert_merge_sql" + }, + "macro.dbt.get_drop_backup_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_drop_backup_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro get_drop_backup_sql(relation) -%}\n {{- log('Applying DROP BACKUP to: ' ~ relation) -}}\n {{- adapter.dispatch('get_drop_backup_sql', 'dbt')(relation) -}}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "get_drop_backup_sql", + "original_file_path": "macros/relations/drop_backup.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/drop_backup.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_drop_backup_sql" + }, + "macro.dbt.get_drop_index_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_drop_index_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_drop_index_sql(relation, index_name) -%}\n {{ adapter.dispatch('get_drop_index_sql', 'dbt')(relation, index_name) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_drop_index_sql", + "original_file_path": "macros/adapters/indexes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/indexes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_drop_index_sql" + }, + "macro.dbt.get_drop_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_drop_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro get_drop_sql(relation) -%}\n {{- log('Applying DROP to: ' ~ relation) -}}\n {{- adapter.dispatch('get_drop_sql', 'dbt')(relation) -}}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "get_drop_sql", + "original_file_path": "macros/relations/drop.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/drop.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_drop_sql" + }, + "macro.dbt.get_empty_schema_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_empty_schema_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_empty_schema_sql(columns) -%}\n {{ return(adapter.dispatch('get_empty_schema_sql', 'dbt')(columns)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_empty_schema_sql", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_empty_schema_sql" + }, + "macro.dbt.get_empty_subquery_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_empty_subquery_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_empty_subquery_sql(select_sql, select_sql_header=none) -%}\n {{ return(adapter.dispatch('get_empty_subquery_sql', 'dbt')(select_sql, select_sql_header)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_empty_subquery_sql", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_empty_subquery_sql" + }, + "macro.dbt.get_expected_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.format_row" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_expected_sql(rows, column_name_to_data_types, column_name_to_quoted) %}\n\n{%- if (rows | length) == 0 -%}\n select * from dbt_internal_unit_test_actual\n limit 0\n{%- else -%}\n{%- for row in rows -%}\n{%- set formatted_row = format_row(row, column_name_to_data_types) -%}\nselect\n{%- for column_name, column_value in formatted_row.items() %} {{ column_value }} as {{ column_name_to_quoted[column_name] }}{% if not loop.last -%}, {%- endif %}\n{%- endfor %}\n{%- if not loop.last %}\nunion all\n{% endif %}\n{%- endfor -%}\n{%- endif -%}\n\n{% endmacro %}", + "meta": {}, + "name": "get_expected_sql", + "original_file_path": "macros/unit_test_sql/get_fixture_sql.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/unit_test_sql/get_fixture_sql.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_expected_sql" + }, + "macro.dbt.get_fixture_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_relation", + "macro.dbt.safe_cast", + "macro.dbt.validate_fixture_rows", + "macro.dbt.format_row" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_fixture_sql(rows, column_name_to_data_types) %}\n-- Fixture for {{ model.name }}\n{% set default_row = {} %}\n\n{%- if not column_name_to_data_types -%}\n{#-- Use defer_relation IFF it is available in the manifest and 'this' is missing from the database --#}\n{%- set this_or_defer_relation = defer_relation if (defer_relation and not load_relation(this)) else this -%}\n{%- set columns_in_relation = adapter.get_columns_in_relation(this_or_defer_relation) -%}\n\n{%- set column_name_to_data_types = {} -%}\n{%- set column_name_to_quoted = {} -%}\n{%- for column in columns_in_relation -%}\n\n{#-- This needs to be a case-insensitive comparison --#}\n{%- do column_name_to_data_types.update({column.name|lower: column.data_type}) -%}\n{%- do column_name_to_quoted.update({column.name|lower: column.quoted}) -%}\n{%- endfor -%}\n{%- endif -%}\n\n{%- if not column_name_to_data_types -%}\n {{ exceptions.raise_compiler_error(\"Not able to get columns for unit test '\" ~ model.name ~ \"' from relation \" ~ this ~ \" because the relation doesn't exist\") }}\n{%- endif -%}\n\n{%- for column_name, column_type in column_name_to_data_types.items() -%}\n {%- do default_row.update({column_name: (safe_cast(\"null\", column_type) | trim )}) -%}\n{%- endfor -%}\n\n{{ validate_fixture_rows(rows, row_number) }}\n\n{%- for row in rows -%}\n{%- set formatted_row = format_row(row, column_name_to_data_types) -%}\n{%- set default_row_copy = default_row.copy() -%}\n{%- do default_row_copy.update(formatted_row) -%}\nselect\n{%- for column_name, column_value in default_row_copy.items() %} {{ column_value }} as {{ column_name_to_quoted[column_name] }}{% if not loop.last -%}, {%- endif %}\n{%- endfor %}\n{%- if not loop.last %}\nunion all\n{% endif %}\n{%- endfor -%}\n\n{%- if (rows | length) == 0 -%}\n select\n {%- for column_name, column_value in default_row.items() %} {{ column_value }} as {{ column_name_to_quoted[column_name] }}{% if not loop.last -%},{%- endif %}\n {%- endfor %}\n limit 0\n{%- endif -%}\n{% endmacro %}", + "meta": {}, + "name": "get_fixture_sql", + "original_file_path": "macros/unit_test_sql/get_fixture_sql.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/unit_test_sql/get_fixture_sql.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_fixture_sql" + }, + "macro.dbt.get_formatted_aggregate_function_args": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_formatted_aggregate_function_args" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_formatted_aggregate_function_args() %}\n {{ return(adapter.dispatch('get_formatted_aggregate_function_args', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "get_formatted_aggregate_function_args", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_formatted_aggregate_function_args" + }, + "macro.dbt.get_function_language_specifier": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_function_language_specifier" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_function_language_specifier() %}\n {{ return(adapter.dispatch('get_function_language_specifier', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "get_function_language_specifier", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_function_language_specifier" + }, + "macro.dbt.get_function_python_options": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_function_python_options" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_function_python_options() %}\n {{ return(adapter.dispatch('get_function_python_options', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "get_function_python_options", + "original_file_path": "macros/materializations/functions/aggregate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/aggregate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_function_python_options" + }, + "macro.dbt.get_grant_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_grant_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_grant_sql(relation, privilege, grantees) %}\n {{ return(adapter.dispatch('get_grant_sql', 'dbt')(relation, privilege, grantees)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_grant_sql", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_grant_sql" + }, + "macro.dbt.get_incremental_append_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_incremental_append_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_incremental_append_sql(arg_dict) %}\n\n {{ return(adapter.dispatch('get_incremental_append_sql', 'dbt')(arg_dict)) }}\n\n{% endmacro %}", + "meta": {}, + "name": "get_incremental_append_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_incremental_append_sql" + }, + "macro.dbt.get_incremental_default_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_incremental_default_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_incremental_default_sql(arg_dict) %}\n\n {{ return(adapter.dispatch('get_incremental_default_sql', 'dbt')(arg_dict)) }}\n\n{% endmacro %}", + "meta": {}, + "name": "get_incremental_default_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_incremental_default_sql" + }, + "macro.dbt.get_incremental_delete_insert_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_incremental_delete_insert_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_incremental_delete_insert_sql(arg_dict) %}\n\n {{ return(adapter.dispatch('get_incremental_delete_insert_sql', 'dbt')(arg_dict)) }}\n\n{% endmacro %}", + "meta": {}, + "name": "get_incremental_delete_insert_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_incremental_delete_insert_sql" + }, + "macro.dbt.get_incremental_insert_overwrite_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_incremental_insert_overwrite_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_incremental_insert_overwrite_sql(arg_dict) %}\n\n {{ return(adapter.dispatch('get_incremental_insert_overwrite_sql', 'dbt')(arg_dict)) }}\n\n{% endmacro %}", + "meta": {}, + "name": "get_incremental_insert_overwrite_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_incremental_insert_overwrite_sql" + }, + "macro.dbt.get_incremental_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_incremental_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_incremental_merge_sql(arg_dict) %}\n\n {{ return(adapter.dispatch('get_incremental_merge_sql', 'dbt')(arg_dict)) }}\n\n{% endmacro %}", + "meta": {}, + "name": "get_incremental_merge_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_incremental_merge_sql" + }, + "macro.dbt.get_incremental_microbatch_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_incremental_microbatch_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_incremental_microbatch_sql(arg_dict) %}\n\n {{ return(adapter.dispatch('get_incremental_microbatch_sql', 'dbt')(arg_dict)) }}\n\n{% endmacro %}", + "meta": {}, + "name": "get_incremental_microbatch_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_incremental_microbatch_sql" + }, + "macro.dbt.get_insert_into_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_quoted_csv" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_insert_into_sql(target_relation, temp_relation, dest_columns) %}\n\n {%- set dest_cols_csv = get_quoted_csv(dest_columns | map(attribute=\"name\")) -%}\n\n insert into {{ target_relation }} ({{ dest_cols_csv }})\n (\n select {{ dest_cols_csv }}\n from {{ temp_relation }}\n )\n\n{% endmacro %}", + "meta": {}, + "name": "get_insert_into_sql", + "original_file_path": "macros/materializations/models/incremental/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_insert_into_sql" + }, + "macro.dbt.get_insert_overwrite_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_insert_overwrite_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_insert_overwrite_merge_sql(target, source, dest_columns, predicates, include_sql_header=false) -%}\n {{ adapter.dispatch('get_insert_overwrite_merge_sql', 'dbt')(target, source, dest_columns, predicates, include_sql_header) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_insert_overwrite_merge_sql", + "original_file_path": "macros/materializations/models/incremental/merge.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_insert_overwrite_merge_sql" + }, + "macro.dbt.get_intervals_between": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_intervals_between" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_intervals_between(start_date, end_date, datepart) -%}\n {{ return(adapter.dispatch('get_intervals_between', 'dbt')(start_date, end_date, datepart)) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_intervals_between", + "original_file_path": "macros/utils/date_spine.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/date_spine.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_intervals_between" + }, + "macro.dbt.get_limit_subquery_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_limit_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n{%- macro get_limit_subquery_sql(sql, limit) -%}\n {{ adapter.dispatch('get_limit_sql', 'dbt')(sql, limit) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "get_limit_subquery_sql", + "original_file_path": "macros/adapters/show.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/show.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_limit_subquery_sql" + }, + "macro.dbt.get_list_of_column_names": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro get_list_of_column_names(columns) -%}\n {% set col_names = [] %}\n {% for col in columns %}\n {% do col_names.append(col.name) %}\n {% endfor %}\n {{ return(col_names) }}\n{% endmacro %}", + "meta": {}, + "name": "get_list_of_column_names", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_list_of_column_names" + }, + "macro.dbt.get_materialized_view_configuration_changes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_materialized_view_configuration_changes" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_materialized_view_configuration_changes(existing_relation, new_config) %}\n /* {#\n It's recommended that configuration changes be formatted as follows:\n {\"\": [{\"action\": \"\", \"context\": ...}]}\n\n For example:\n {\n \"indexes\": [\n {\"action\": \"drop\", \"context\": \"index_abc\"},\n {\"action\": \"create\", \"context\": {\"columns\": [\"column_1\", \"column_2\"], \"type\": \"hash\", \"unique\": True}},\n ],\n }\n\n Either way, `get_materialized_view_configuration_changes` needs to align with `get_alter_materialized_view_as_sql`.\n #} */\n {{- log('Determining configuration changes on: ' ~ existing_relation) -}}\n {%- do return(adapter.dispatch('get_materialized_view_configuration_changes', 'dbt')(existing_relation, new_config)) -%}\n{% endmacro %}", + "meta": {}, + "name": "get_materialized_view_configuration_changes", + "original_file_path": "macros/relations/materialized_view/alter.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/alter.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_materialized_view_configuration_changes" + }, + "macro.dbt.get_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__get_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_merge_sql(target, source, unique_key, dest_columns, incremental_predicates=none) -%}\n -- back compat for old kwarg name\n {% set incremental_predicates = kwargs.get('predicates', incremental_predicates) %}\n {{ adapter.dispatch('get_merge_sql', 'dbt')(target, source, unique_key, dest_columns, incremental_predicates) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_merge_sql", + "original_file_path": "macros/materializations/models/incremental/merge.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_merge_sql" + }, + "macro.dbt.get_merge_update_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_merge_update_columns" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_merge_update_columns(merge_update_columns, merge_exclude_columns, dest_columns) %}\n {{ return(adapter.dispatch('get_merge_update_columns', 'dbt')(merge_update_columns, merge_exclude_columns, dest_columns)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_merge_update_columns", + "original_file_path": "macros/materializations/models/incremental/column_helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/column_helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_merge_update_columns" + }, + "macro.dbt.get_or_create_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_or_create_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_or_create_relation(database, schema, identifier, type) -%}\n {{ return(adapter.dispatch('get_or_create_relation', 'dbt')(database, schema, identifier, type)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_or_create_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_or_create_relation" + }, + "macro.dbt.get_powers_of_two": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_powers_of_two" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_powers_of_two(upper_bound) %}\n {{ return(adapter.dispatch('get_powers_of_two', 'dbt')(upper_bound)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_powers_of_two", + "original_file_path": "macros/utils/generate_series.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/generate_series.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_powers_of_two" + }, + "macro.dbt.get_quoted_csv": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_quoted_csv(column_names) %}\n\n {% set quoted = [] %}\n {% for col in column_names -%}\n {%- do quoted.append(adapter.quote(col)) -%}\n {%- endfor %}\n\n {%- set dest_cols_csv = quoted | join(', ') -%}\n {{ return(dest_cols_csv) }}\n\n{% endmacro %}", + "meta": {}, + "name": "get_quoted_csv", + "original_file_path": "macros/materializations/models/incremental/column_helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/column_helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_quoted_csv" + }, + "macro.dbt.get_relation_last_modified": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_relation_last_modified" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_relation_last_modified(information_schema, relations) %}\n {{ return(adapter.dispatch('get_relation_last_modified', 'dbt')(information_schema, relations)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_relation_last_modified", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_relation_last_modified" + }, + "macro.dbt.get_relations": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_relations" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_relations() %}\n {{ return(adapter.dispatch('get_relations', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "get_relations", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_relations" + }, + "macro.dbt.get_rename_intermediate_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_rename_intermediate_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro get_rename_intermediate_sql(relation) -%}\n {{- log('Applying RENAME INTERMEDIATE to: ' ~ relation) -}}\n {{- adapter.dispatch('get_rename_intermediate_sql', 'dbt')(relation) -}}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "get_rename_intermediate_sql", + "original_file_path": "macros/relations/rename_intermediate.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/rename_intermediate.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_rename_intermediate_sql" + }, + "macro.dbt.get_rename_materialized_view_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_rename_materialized_view_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_rename_materialized_view_sql(relation, new_name) %}\n {{- adapter.dispatch('get_rename_materialized_view_sql', 'dbt')(relation, new_name) -}}\n{% endmacro %}", + "meta": {}, + "name": "get_rename_materialized_view_sql", + "original_file_path": "macros/relations/materialized_view/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_rename_materialized_view_sql" + }, + "macro.dbt.get_rename_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_rename_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro get_rename_sql(relation, new_name) -%}\n {{- log('Applying RENAME to: ' ~ relation) -}}\n {{- adapter.dispatch('get_rename_sql', 'dbt')(relation, new_name) -}}\n{%- endmacro -%}\n\n\n", + "meta": {}, + "name": "get_rename_sql", + "original_file_path": "macros/relations/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_rename_sql" + }, + "macro.dbt.get_rename_table_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_rename_table_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_rename_table_sql(relation, new_name) %}\n {{- adapter.dispatch('get_rename_table_sql', 'dbt')(relation, new_name) -}}\n{% endmacro %}", + "meta": {}, + "name": "get_rename_table_sql", + "original_file_path": "macros/relations/table/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_rename_table_sql" + }, + "macro.dbt.get_rename_view_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_rename_view_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_rename_view_sql(relation, new_name) %}\n {{- adapter.dispatch('get_rename_view_sql', 'dbt')(relation, new_name) -}}\n{% endmacro %}", + "meta": {}, + "name": "get_rename_view_sql", + "original_file_path": "macros/relations/view/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_rename_view_sql" + }, + "macro.dbt.get_replace_materialized_view_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_replace_materialized_view_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_replace_materialized_view_sql(relation, sql) %}\n {{- adapter.dispatch('get_replace_materialized_view_sql', 'dbt')(relation, sql) -}}\n{% endmacro %}", + "meta": {}, + "name": "get_replace_materialized_view_sql", + "original_file_path": "macros/relations/materialized_view/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_replace_materialized_view_sql" + }, + "macro.dbt.get_replace_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_replace_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_replace_sql(existing_relation, target_relation, sql) %}\n {{- log('Applying REPLACE to: ' ~ existing_relation) -}}\n {{- adapter.dispatch('get_replace_sql', 'dbt')(existing_relation, target_relation, sql) -}}\n{% endmacro %}", + "meta": {}, + "name": "get_replace_sql", + "original_file_path": "macros/relations/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_replace_sql" + }, + "macro.dbt.get_replace_table_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_replace_table_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_replace_table_sql(relation, sql) %}\n {{- adapter.dispatch('get_replace_table_sql', 'dbt')(relation, sql) -}}\n{% endmacro %}", + "meta": {}, + "name": "get_replace_table_sql", + "original_file_path": "macros/relations/table/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_replace_table_sql" + }, + "macro.dbt.get_replace_view_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_replace_view_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_replace_view_sql(relation, sql) %}\n {{- adapter.dispatch('get_replace_view_sql', 'dbt')(relation, sql) -}}\n{% endmacro %}", + "meta": {}, + "name": "get_replace_view_sql", + "original_file_path": "macros/relations/view/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_replace_view_sql" + }, + "macro.dbt.get_revoke_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_revoke_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_revoke_sql(relation, privilege, grantees) %}\n {{ return(adapter.dispatch('get_revoke_sql', 'dbt')(relation, privilege, grantees)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_revoke_sql", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_revoke_sql" + }, + "macro.dbt.get_seed_column_quoted_csv": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_seed_column_quoted_csv(model, column_names) %}\n {%- set quote_seed_column = model['config'].get('quote_columns', None) -%}\n {% set quoted = [] %}\n {% for col in column_names -%}\n {%- do quoted.append(adapter.quote_seed_column(col, quote_seed_column)) -%}\n {%- endfor %}\n\n {%- set dest_cols_csv = quoted | join(', ') -%}\n {{ return(dest_cols_csv) }}\n{% endmacro %}", + "meta": {}, + "name": "get_seed_column_quoted_csv", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_seed_column_quoted_csv" + }, + "macro.dbt.get_select_subquery": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_select_subquery" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_select_subquery(sql) %}\n {{ return(adapter.dispatch('get_select_subquery', 'dbt')(sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_select_subquery", + "original_file_path": "macros/relations/table/create.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/table/create.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_select_subquery" + }, + "macro.dbt.get_show_grant_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_show_grant_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_show_grant_sql(relation) %}\n {{ return(adapter.dispatch(\"get_show_grant_sql\", \"dbt\")(relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "get_show_grant_sql", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_show_grant_sql" + }, + "macro.dbt.get_show_indexes_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_show_indexes_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_show_indexes_sql(relation) -%}\n {{ adapter.dispatch('get_show_indexes_sql', 'dbt')(relation) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_show_indexes_sql", + "original_file_path": "macros/adapters/indexes.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/indexes.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_show_indexes_sql" + }, + "macro.dbt.get_show_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_limit_subquery_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_show_sql(compiled_code, sql_header, limit) -%}\n {%- if sql_header is not none -%}\n {{ sql_header }}\n {%- endif %}\n {{ get_limit_subquery_sql(compiled_code, limit) }}\n{% endmacro %}", + "meta": {}, + "name": "get_show_sql", + "original_file_path": "macros/adapters/show.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/show.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_show_sql" + }, + "macro.dbt.get_snapshot_get_time_data_type": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.snapshot_get_time", + "macro.dbt_duckdb.duckdb__snapshot_get_time", + "macro.dbt.get_column_schema_from_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_snapshot_get_time_data_type() %}\n {% set snapshot_time = adapter.dispatch('snapshot_get_time', 'dbt')() %}\n {% set time_data_type_sql = 'select ' ~ snapshot_time ~ ' as dbt_snapshot_time' %}\n {% set snapshot_time_column_schema = get_column_schema_from_query(time_data_type_sql) %}\n {% set time_data_type = snapshot_time_column_schema[0].dtype %}\n {{ return(time_data_type or none) }}\n{% endmacro %}", + "meta": {}, + "name": "get_snapshot_get_time_data_type", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_snapshot_get_time_data_type" + }, + "macro.dbt.get_snapshot_table_column_names": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_snapshot_table_column_names() %}\n {{ return({'dbt_valid_to': 'dbt_valid_to', 'dbt_valid_from': 'dbt_valid_from', 'dbt_scd_id': 'dbt_scd_id', 'dbt_updated_at': 'dbt_updated_at', 'dbt_is_deleted': 'dbt_is_deleted'}) }}\n{% endmacro %}", + "meta": {}, + "name": "get_snapshot_table_column_names", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_snapshot_table_column_names" + }, + "macro.dbt.get_table_columns_and_constraints": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_table_columns_and_constraints" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro get_table_columns_and_constraints() -%}\n {{ adapter.dispatch('get_table_columns_and_constraints', 'dbt')() }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "get_table_columns_and_constraints", + "original_file_path": "macros/relations/column/columns_spec_ddl.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/column/columns_spec_ddl.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_table_columns_and_constraints" + }, + "macro.dbt.get_test_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_test_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_test_sql(main_sql, fail_calc, warn_if, error_if, limit) -%}\n {{ adapter.dispatch('get_test_sql', 'dbt')(main_sql, fail_calc, warn_if, error_if, limit) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_test_sql", + "original_file_path": "macros/materializations/tests/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/tests/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_test_sql" + }, + "macro.dbt.get_true_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_true_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_true_sql() %}\n {{ adapter.dispatch('get_true_sql', 'dbt')() }}\n{% endmacro %}", + "meta": {}, + "name": "get_true_sql", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_true_sql" + }, + "macro.dbt.get_unit_test_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_unit_test_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_unit_test_sql(main_sql, expected_fixture_sql, expected_column_names) -%}\n {{ adapter.dispatch('get_unit_test_sql', 'dbt')(main_sql, expected_fixture_sql, expected_column_names) }}\n{%- endmacro %}", + "meta": {}, + "name": "get_unit_test_sql", + "original_file_path": "macros/materializations/tests/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/tests/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_unit_test_sql" + }, + "macro.dbt.get_updated_at_column_data_type": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_column_schema_from_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_updated_at_column_data_type(snapshot_sql) %}\n {% set snapshot_sql_column_schema = get_column_schema_from_query(snapshot_sql) %}\n {% set dbt_updated_at_data_type = null %}\n {% set ns = namespace() -%} {#-- handle for-loop scoping with a namespace --#}\n {% set ns.dbt_updated_at_data_type = null -%}\n {% for column in snapshot_sql_column_schema %}\n {% if ((column.column == 'dbt_updated_at') or (column.column == 'DBT_UPDATED_AT')) %}\n {% set ns.dbt_updated_at_data_type = column.dtype %}\n {% endif %}\n {% endfor %}\n {{ return(ns.dbt_updated_at_data_type or none) }}\n{% endmacro %}", + "meta": {}, + "name": "get_updated_at_column_data_type", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_updated_at_column_data_type" + }, + "macro.dbt.get_where_subquery": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__get_where_subquery" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_where_subquery(relation) -%}\n {% do return(adapter.dispatch('get_where_subquery', 'dbt')(relation)) %}\n{%- endmacro %}", + "meta": {}, + "name": "get_where_subquery", + "original_file_path": "macros/materializations/tests/where_subquery.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/tests/where_subquery.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.get_where_subquery" + }, + "macro.dbt.handle_existing_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__handle_existing_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro handle_existing_table(full_refresh, old_relation) %}\n {{ adapter.dispatch('handle_existing_table', 'dbt')(full_refresh, old_relation) }}\n{% endmacro %}", + "meta": {}, + "name": "handle_existing_table", + "original_file_path": "macros/relations/view/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/view/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.handle_existing_table" + }, + "macro.dbt.hash": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__hash" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro hash(field) -%}\n {{ return(adapter.dispatch('hash', 'dbt') (field)) }}\n{%- endmacro %}", + "meta": {}, + "name": "hash", + "original_file_path": "macros/utils/hash.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/hash.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.hash" + }, + "macro.dbt.in_transaction": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_hook_config" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro in_transaction(sql) %}\n {{ make_hook_config(sql, inside_transaction=True) }}\n{% endmacro %}", + "meta": {}, + "name": "in_transaction", + "original_file_path": "macros/materializations/hooks.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/hooks.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.in_transaction" + }, + "macro.dbt.incremental_validate_on_schema_change": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro incremental_validate_on_schema_change(on_schema_change, default='ignore') %}\n\n {% if on_schema_change not in ['sync_all_columns', 'append_new_columns', 'fail', 'ignore'] %}\n\n {% set log_message = 'Invalid value for on_schema_change (%s) specified. Setting default value of %s.' % (on_schema_change, default) %}\n {% do log(log_message) %}\n\n {{ return(default) }}\n\n {% else %}\n\n {{ return(on_schema_change) }}\n\n {% endif %}\n\n{% endmacro %}", + "meta": {}, + "name": "incremental_validate_on_schema_change", + "original_file_path": "macros/materializations/models/incremental/on_schema_change.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/on_schema_change.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.incremental_validate_on_schema_change" + }, + "macro.dbt.information_schema_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__information_schema_name" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro information_schema_name(database) %}\n {{ return(adapter.dispatch('information_schema_name', 'dbt')(database)) }}\n{% endmacro %}", + "meta": {}, + "name": "information_schema_name", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.information_schema_name" + }, + "macro.dbt.intersect": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__intersect" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro intersect() %}\n {{ return(adapter.dispatch('intersect', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "intersect", + "original_file_path": "macros/utils/intersect.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/intersect.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.intersect" + }, + "macro.dbt.is_incremental": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.should_full_refresh" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro is_incremental() %}\n {#-- do not run introspective queries in parsing #}\n {% if not execute %}\n {{ return(False) }}\n {% else %}\n {% set relation = adapter.get_relation(this.database, this.schema, this.table) %}\n {{ return(relation is not none\n and relation.type == 'table'\n and model.config.materialized == 'incremental'\n and not should_full_refresh()) }}\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "is_incremental", + "original_file_path": "macros/materializations/models/incremental/is_incremental.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/is_incremental.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.is_incremental" + }, + "macro.dbt.last_day": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__last_day" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro last_day(date, datepart) %}\n {{ return(adapter.dispatch('last_day', 'dbt') (date, datepart)) }}\n{% endmacro %}", + "meta": {}, + "name": "last_day", + "original_file_path": "macros/utils/last_day.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/last_day.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.last_day" + }, + "macro.dbt.length": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__length" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro length(expression) -%}\n {{ return(adapter.dispatch('length', 'dbt') (expression)) }}\n{% endmacro %}", + "meta": {}, + "name": "length", + "original_file_path": "macros/utils/length.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/length.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.length" + }, + "macro.dbt.list_relations_without_caching": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__list_relations_without_caching" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro list_relations_without_caching(schema_relation) %}\n {{ return(adapter.dispatch('list_relations_without_caching', 'dbt')(schema_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "list_relations_without_caching", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.list_relations_without_caching" + }, + "macro.dbt.list_schemas": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__list_schemas" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro list_schemas(database) -%}\n {{ return(adapter.dispatch('list_schemas', 'dbt')(database)) }}\n{% endmacro %}", + "meta": {}, + "name": "list_schemas", + "original_file_path": "macros/adapters/metadata.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/metadata.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.list_schemas" + }, + "macro.dbt.listagg": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__listagg" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro listagg(measure, delimiter_text=\"','\", order_by_clause=none, limit_num=none) -%}\n {{ return(adapter.dispatch('listagg', 'dbt') (measure, delimiter_text, order_by_clause, limit_num)) }}\n{%- endmacro %}", + "meta": {}, + "name": "listagg", + "original_file_path": "macros/utils/listagg.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/listagg.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.listagg" + }, + "macro.dbt.load_cached_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro load_cached_relation(relation) %}\n {% do return(adapter.get_relation(\n database=relation.database,\n schema=relation.schema,\n identifier=relation.identifier\n )) -%}\n{% endmacro %}", + "meta": {}, + "name": "load_cached_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.load_cached_relation" + }, + "macro.dbt.load_csv_rows": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__load_csv_rows" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro load_csv_rows(model, agate_table) -%}\n {{ adapter.dispatch('load_csv_rows', 'dbt')(model, agate_table) }}\n{%- endmacro %}", + "meta": {}, + "name": "load_csv_rows", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.load_csv_rows" + }, + "macro.dbt.load_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro load_relation(relation) %}\n {{ return(load_cached_relation(relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "load_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.load_relation" + }, + "macro.dbt.make_backup_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__make_backup_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro make_backup_relation(base_relation, backup_relation_type, suffix='__dbt_backup') %}\n {{ return(adapter.dispatch('make_backup_relation', 'dbt')(base_relation, backup_relation_type, suffix)) }}\n{% endmacro %}", + "meta": {}, + "name": "make_backup_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.make_backup_relation" + }, + "macro.dbt.make_hook_config": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro make_hook_config(sql, inside_transaction) %}\n {{ tojson({\"sql\": sql, \"transaction\": inside_transaction}) }}\n{% endmacro %}", + "meta": {}, + "name": "make_hook_config", + "original_file_path": "macros/materializations/hooks.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/hooks.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.make_hook_config" + }, + "macro.dbt.make_intermediate_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__make_intermediate_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro make_intermediate_relation(base_relation, suffix='__dbt_tmp') %}\n {{ return(adapter.dispatch('make_intermediate_relation', 'dbt')(base_relation, suffix)) }}\n{% endmacro %}", + "meta": {}, + "name": "make_intermediate_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.make_intermediate_relation" + }, + "macro.dbt.make_temp_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__make_temp_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro make_temp_relation(base_relation, suffix='__dbt_tmp') %}\n {#-- This ensures microbatch batches get unique temp relations to avoid clobbering --#}\n {% if suffix == '__dbt_tmp' and model.batch %}\n {% set suffix = suffix ~ '_' ~ model.batch.id %}\n {% endif %}\n\n {{ return(adapter.dispatch('make_temp_relation', 'dbt')(base_relation, suffix)) }}\n{% endmacro %}", + "meta": {}, + "name": "make_temp_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.make_temp_relation" + }, + "macro.dbt.materialization_clone_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.can_clone_table", + "macro.dbt.drop_relation_if_exists", + "macro.dbt.statement", + "macro.dbt.create_or_replace_clone", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- materialization clone, default -%}\n\n {%- set relations = {'relations': []} -%}\n\n {%- if not defer_relation -%}\n -- nothing to do\n {{ log(\"No relation found in state manifest for \" ~ model.unique_id, info=True) }}\n {{ return(relations) }}\n {%- endif -%}\n\n {%- set existing_relation = load_cached_relation(this) -%}\n\n {%- if existing_relation and not flags.FULL_REFRESH -%}\n -- noop!\n {{ log(\"Relation \" ~ existing_relation ~ \" already exists\", info=True) }}\n {{ return(relations) }}\n {%- endif -%}\n\n {%- set other_existing_relation = load_cached_relation(defer_relation) -%}\n\n -- If this is a database that can do zero-copy cloning of tables, and the other relation is a table, then this will be a table\n -- Otherwise, this will be a view\n\n {% set can_clone_table = can_clone_table() %}\n {%- set grant_config = config.get('grants') -%}\n\n {%- if other_existing_relation and other_existing_relation.type == 'table' and can_clone_table -%}\n\n {%- set target_relation = this.incorporate(type='table') -%}\n {% if existing_relation is not none and not existing_relation.is_table %}\n {{ log(\"Dropping relation \" ~ existing_relation.render() ~ \" because it is of type \" ~ existing_relation.type) }}\n {{ drop_relation_if_exists(existing_relation) }}\n {% endif %}\n\n -- as a general rule, data platforms that can clone tables can also do atomic 'create or replace'\n {% if target_relation.database == defer_relation.database and\n target_relation.schema == defer_relation.schema and\n target_relation.identifier == defer_relation.identifier %}\n {{ log(\"Target relation and defer relation are the same, skipping clone for relation: \" ~ target_relation.render()) }}\n {% else %}\n {% call statement('main') %}\n {{ create_or_replace_clone(target_relation, defer_relation) }}\n {% endcall %}\n {% endif %}\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n {% do persist_docs(target_relation, model) %}\n\n {{ return({'relations': [target_relation]}) }}\n\n {%- else -%}\n\n {%- set target_relation = this.incorporate(type='view') -%}\n\n -- reuse the view materialization\n -- TODO: support actual dispatch for materialization macros\n -- Tracking ticket: https://github.com/dbt-labs/dbt-core/issues/7799\n {% set search_name = \"materialization_view_\" ~ adapter.type() %}\n {% if not search_name in context %}\n {% set search_name = \"materialization_view_default\" %}\n {% endif %}\n {% set materialization_macro = context[search_name] %}\n {% set relations = materialization_macro() %}\n {{ return(relations) }}\n\n {%- endif -%}\n\n{%- endmaterialization -%}", + "meta": {}, + "name": "materialization_clone_default", + "original_file_path": "macros/materializations/models/clone/clone.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/clone/clone.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_clone_default" + }, + "macro.dbt.materialization_function_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.run_hooks", + "macro.dbt.function_execute_build_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization function, default, supported_languages=['sql', 'python'] %}\n {% set existing_relation = load_cached_relation(this) %}\n {% set target_relation = this.incorporate(type=this.Function) %}\n\n {{ run_hooks(pre_hooks) }}\n\n {% set function_config = this.get_function_config(model) %}\n {% set macro_name = this.get_function_macro_name(function_config) %}\n\n {# Doing this aliasing of adapter.dispatch is a hacky way to disable the static analysis of actually calling adapter.dispatch #}\n {# This is necessary because the static analysis breaks being able to dynamically pass a macro_name #}\n {% set _dispatch = adapter.dispatch %}\n\n {% set build_sql = _dispatch(macro_name, 'dbt')(target_relation) %}\n {{ function_execute_build_sql(build_sql, existing_relation, target_relation) }}\n {{ run_hooks(post_hooks) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{% endmaterialization %}", + "meta": {}, + "name": "materialization_function_default", + "original_file_path": "macros/materializations/functions/function.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/function.sql", + "resource_type": "macro", + "supported_languages": [ + "sql", + "python" + ], + "unique_id": "macro.dbt.materialization_function_default" + }, + "macro.dbt.materialization_incremental_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.make_temp_relation", + "macro.dbt.make_intermediate_relation", + "macro.dbt.make_backup_relation", + "macro.dbt.should_full_refresh", + "macro.dbt.incremental_validate_on_schema_change", + "macro.dbt.drop_relation_if_exists", + "macro.dbt.run_hooks", + "macro.dbt.get_create_table_as_sql", + "macro.dbt.run_query", + "macro.dbt.process_schema_changes", + "macro.dbt.statement", + "macro.dbt.create_indexes", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization incremental, default -%}\n\n -- relations\n {%- set existing_relation = load_cached_relation(this) -%}\n {%- set target_relation = this.incorporate(type='table') -%}\n {%- set temp_relation = make_temp_relation(target_relation)-%}\n {%- set intermediate_relation = make_intermediate_relation(target_relation)-%}\n {%- set backup_relation_type = 'table' if existing_relation is none else existing_relation.type -%}\n {%- set backup_relation = make_backup_relation(target_relation, backup_relation_type) -%}\n\n -- configs\n {%- set unique_key = config.get('unique_key') -%}\n {%- set full_refresh_mode = (should_full_refresh() or existing_relation.is_view) -%}\n {%- set on_schema_change = incremental_validate_on_schema_change(config.get('on_schema_change'), default='ignore') -%}\n\n -- the temp_ and backup_ relations should not already exist in the database; get_relation\n -- will return None in that case. Otherwise, we get a relation that we can drop\n -- later, before we try to use this name for the current operation. This has to happen before\n -- BEGIN, in a separate transaction\n {%- set preexisting_intermediate_relation = load_cached_relation(intermediate_relation)-%}\n {%- set preexisting_backup_relation = load_cached_relation(backup_relation) -%}\n -- grab current tables grants config for comparision later on\n {% set grant_config = config.get('grants') %}\n {{ drop_relation_if_exists(preexisting_intermediate_relation) }}\n {{ drop_relation_if_exists(preexisting_backup_relation) }}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n {% set to_drop = [] %}\n\n {% set incremental_strategy = config.get('incremental_strategy') or 'default' %}\n {% set strategy_sql_macro_func = adapter.get_incremental_strategy_macro(context, incremental_strategy) %}\n\n {% if existing_relation is none %}\n {% set build_sql = get_create_table_as_sql(False, target_relation, sql) %}\n {% set relation_for_indexes = target_relation %}\n {% elif full_refresh_mode %}\n {% set build_sql = get_create_table_as_sql(False, intermediate_relation, sql) %}\n {% set relation_for_indexes = intermediate_relation %}\n {% set need_swap = true %}\n {% else %}\n {% do run_query(get_create_table_as_sql(True, temp_relation, sql)) %}\n {% set relation_for_indexes = temp_relation %}\n {% set contract_config = config.get('contract') %}\n {% if not contract_config or not contract_config.enforced %}\n {% do adapter.expand_target_column_types(\n from_relation=temp_relation,\n to_relation=target_relation) %}\n {% endif %}\n {#-- Process schema changes. Returns dict of changes if successful. Use source columns for upserting/merging --#}\n {% set dest_columns = process_schema_changes(on_schema_change, temp_relation, existing_relation) %}\n {% if not dest_columns %}\n {% set dest_columns = adapter.get_columns_in_relation(existing_relation) %}\n {% endif %}\n\n {#-- Get the incremental_strategy, the macro to use for the strategy, and build the sql --#}\n {% set incremental_predicates = config.get('predicates', none) or config.get('incremental_predicates', none) %}\n {% set strategy_arg_dict = ({'target_relation': target_relation, 'temp_relation': temp_relation, 'unique_key': unique_key, 'dest_columns': dest_columns, 'incremental_predicates': incremental_predicates }) %}\n {% set build_sql = strategy_sql_macro_func(strategy_arg_dict) %}\n\n {% endif %}\n\n {% call statement(\"main\") %}\n {{ build_sql }}\n {% endcall %}\n\n {% if existing_relation is none or existing_relation.is_view or should_full_refresh() %}\n {% do create_indexes(relation_for_indexes) %}\n {% endif %}\n\n {% if need_swap %}\n {% do adapter.rename_relation(target_relation, backup_relation) %}\n {% do adapter.rename_relation(intermediate_relation, target_relation) %}\n {% do to_drop.append(backup_relation) %}\n {% endif %}\n\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n -- `COMMIT` happens here\n {% do adapter.commit() %}\n\n {% for rel in to_drop %}\n {% do adapter.drop_relation(rel) %}\n {% endfor %}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{%- endmaterialization %}", + "meta": {}, + "name": "materialization_incremental_default", + "original_file_path": "macros/materializations/models/incremental/incremental.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/incremental.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_incremental_default" + }, + "macro.dbt.materialization_materialized_view_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.make_intermediate_relation", + "macro.dbt.make_backup_relation", + "macro.dbt.materialized_view_setup", + "macro.dbt.materialized_view_get_build_sql", + "macro.dbt.materialized_view_execute_no_op", + "macro.dbt.materialized_view_execute_build_sql", + "macro.dbt.materialized_view_teardown" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization materialized_view, default %}\n {% set existing_relation = load_cached_relation(this) %}\n {% set target_relation = this.incorporate(type=this.MaterializedView) %}\n {% set intermediate_relation = make_intermediate_relation(target_relation) %}\n {% set backup_relation_type = target_relation.MaterializedView if existing_relation is none else existing_relation.type %}\n {% set backup_relation = make_backup_relation(target_relation, backup_relation_type) %}\n\n {{ materialized_view_setup(backup_relation, intermediate_relation, pre_hooks) }}\n\n {% set build_sql = materialized_view_get_build_sql(existing_relation, target_relation, backup_relation, intermediate_relation) %}\n\n {% if build_sql == '' %}\n {{ materialized_view_execute_no_op(target_relation) }}\n {% else %}\n {{ materialized_view_execute_build_sql(build_sql, existing_relation, target_relation, post_hooks) }}\n {% endif %}\n\n {{ materialized_view_teardown(backup_relation, intermediate_relation, post_hooks) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{% endmaterialization %}", + "meta": {}, + "name": "materialization_materialized_view_default", + "original_file_path": "macros/materializations/models/materialized_view.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/materialized_view.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_materialized_view_default" + }, + "macro.dbt.materialization_seed_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.should_full_refresh", + "macro.dbt.run_hooks", + "macro.dbt.reset_csv_table", + "macro.dbt.create_csv_table", + "macro.dbt.load_csv_rows", + "macro.dbt.noop_statement", + "macro.dbt.get_csv_sql", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs", + "macro.dbt.create_indexes" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization seed, default %}\n\n {%- set identifier = model['alias'] -%}\n {%- set full_refresh_mode = (should_full_refresh()) -%}\n\n {%- set old_relation = adapter.get_relation(database=database, schema=schema, identifier=identifier) -%}\n\n {%- set exists_as_table = (old_relation is not none and old_relation.is_table) -%}\n {%- set exists_as_view = (old_relation is not none and old_relation.is_view) -%}\n\n {%- set grant_config = config.get('grants') -%}\n {%- set agate_table = load_agate_table() -%}\n -- grab current tables grants config for comparison later on\n\n {%- do store_result('agate_table', response='OK', agate_table=agate_table) -%}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n -- build model\n {% set create_table_sql = \"\" %}\n {% if exists_as_view %}\n {{ exceptions.raise_compiler_error(\"Cannot seed to '{}', it is a view\".format(old_relation.render())) }}\n {% elif exists_as_table %}\n {% set create_table_sql = reset_csv_table(model, full_refresh_mode, old_relation, agate_table) %}\n {% else %}\n {% set create_table_sql = create_csv_table(model, agate_table) %}\n {% endif %}\n\n {% set code = 'CREATE' if full_refresh_mode else 'INSERT' %}\n {% set rows_affected = (agate_table.rows | length) %}\n {% set sql = load_csv_rows(model, agate_table) %}\n\n {% call noop_statement('main', code ~ ' ' ~ rows_affected, code, rows_affected) %}\n {{ get_csv_sql(create_table_sql, sql) }};\n {% endcall %}\n\n {% set target_relation = this.incorporate(type='table') %}\n\n {% set should_revoke = should_revoke(old_relation, full_refresh_mode) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n {% if full_refresh_mode or not exists_as_table %}\n {% do create_indexes(target_relation) %}\n {% endif %}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n -- `COMMIT` happens here\n {{ adapter.commit() }}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{% endmaterialization %}", + "meta": {}, + "name": "materialization_seed_default", + "original_file_path": "macros/materializations/seeds/seed.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/seed.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_seed_default" + }, + "macro.dbt.materialization_snapshot_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_or_create_relation", + "macro.dbt.run_hooks", + "macro.dbt.strategy_dispatch", + "macro.dbt.build_snapshot_table", + "macro.dbt.create_table_as", + "macro.dbt.get_snapshot_table_column_names", + "macro.dbt.snapshot_staging_table", + "macro.dbt.build_snapshot_staging_table", + "macro.dbt.create_columns", + "macro.dbt.snapshot_merge_sql", + "macro.dbt.check_time_data_types", + "macro.dbt.statement", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs", + "macro.dbt.create_indexes", + "macro.dbt.post_snapshot" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization snapshot, default %}\n\n {%- set target_table = model.get('alias', model.get('name')) -%}\n\n {%- set strategy_name = config.get('strategy') -%}\n {%- set unique_key = config.get('unique_key') %}\n -- grab current tables grants config for comparision later on\n {%- set grant_config = config.get('grants') -%}\n\n {% set target_relation_exists, target_relation = get_or_create_relation(\n database=model.database,\n schema=model.schema,\n identifier=target_table,\n type='table') -%}\n\n {%- if not target_relation.is_table -%}\n {% do exceptions.relation_wrong_type(target_relation, 'table') %}\n {%- endif -%}\n\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n {% set strategy_macro = strategy_dispatch(strategy_name) %}\n {# The model['config'] parameter below is no longer used, but passing anyway for compatibility #}\n {# It was a dictionary of config, instead of the config object from the context #}\n {% set strategy = strategy_macro(model, \"snapshotted_data\", \"source_data\", model['config'], target_relation_exists) %}\n\n {% if not target_relation_exists %}\n\n {% set build_sql = build_snapshot_table(strategy, model['compiled_code']) %}\n {% set build_or_select_sql = build_sql %}\n {% set final_sql = create_table_as(False, target_relation, build_sql) %}\n\n {% else %}\n\n {% set columns = config.get(\"snapshot_table_column_names\") or get_snapshot_table_column_names() %}\n\n {{ adapter.assert_valid_snapshot_target_given_strategy(target_relation, columns, strategy) }}\n\n {% set build_or_select_sql = snapshot_staging_table(strategy, sql, target_relation) %}\n {% set staging_table = build_snapshot_staging_table(strategy, sql, target_relation) %}\n\n -- this may no-op if the database does not require column expansion\n {% do adapter.expand_target_column_types(from_relation=staging_table,\n to_relation=target_relation) %}\n\n {% set remove_columns = ['dbt_change_type', 'DBT_CHANGE_TYPE', 'dbt_unique_key', 'DBT_UNIQUE_KEY'] %}\n {% if unique_key | is_list %}\n {% for key in strategy.unique_key %}\n {{ remove_columns.append('dbt_unique_key_' + loop.index|string) }}\n {{ remove_columns.append('DBT_UNIQUE_KEY_' + loop.index|string) }}\n {% endfor %}\n {% endif %}\n\n {% set missing_columns = adapter.get_missing_columns(staging_table, target_relation)\n | rejectattr('name', 'in', remove_columns)\n | list %}\n\n {% do create_columns(target_relation, missing_columns) %}\n\n {% set source_columns = adapter.get_columns_in_relation(staging_table)\n | rejectattr('name', 'in', remove_columns)\n | list %}\n\n {% set quoted_source_columns = [] %}\n {% for column in source_columns %}\n {% do quoted_source_columns.append(adapter.quote(column.name)) %}\n {% endfor %}\n\n {% set final_sql = snapshot_merge_sql(\n target = target_relation,\n source = staging_table,\n insert_cols = quoted_source_columns\n )\n %}\n\n {% endif %}\n\n\n {{ check_time_data_types(build_or_select_sql) }}\n\n {% call statement('main') %}\n {{ final_sql }}\n {% endcall %}\n\n {% set should_revoke = should_revoke(target_relation_exists, full_refresh_mode=False) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n {% if not target_relation_exists %}\n {% do create_indexes(target_relation) %}\n {% endif %}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n {{ adapter.commit() }}\n\n {% if staging_table is defined %}\n {% do post_snapshot(staging_table) %}\n {% endif %}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{% endmaterialization %}", + "meta": {}, + "name": "materialization_snapshot_default", + "original_file_path": "macros/materializations/snapshots/snapshot.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/snapshot.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_snapshot_default" + }, + "macro.dbt.materialization_table_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.make_intermediate_relation", + "macro.dbt.make_backup_relation", + "macro.dbt.drop_relation_if_exists", + "macro.dbt.run_hooks", + "macro.dbt.statement", + "macro.dbt.get_create_table_as_sql", + "macro.dbt.create_indexes", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization table, default %}\n\n {%- set existing_relation = load_cached_relation(this) -%}\n {%- set target_relation = this.incorporate(type='table') %}\n {%- set intermediate_relation = make_intermediate_relation(target_relation) -%}\n -- the intermediate_relation should not already exist in the database; get_relation\n -- will return None in that case. Otherwise, we get a relation that we can drop\n -- later, before we try to use this name for the current operation\n {%- set preexisting_intermediate_relation = load_cached_relation(intermediate_relation) -%}\n /*\n See ../view/view.sql for more information about this relation.\n */\n {%- set backup_relation_type = 'table' if existing_relation is none else existing_relation.type -%}\n {%- set backup_relation = make_backup_relation(target_relation, backup_relation_type) -%}\n -- as above, the backup_relation should not already exist\n {%- set preexisting_backup_relation = load_cached_relation(backup_relation) -%}\n -- grab current tables grants config for comparision later on\n {% set grant_config = config.get('grants') %}\n\n -- drop the temp relations if they exist already in the database\n {{ drop_relation_if_exists(preexisting_intermediate_relation) }}\n {{ drop_relation_if_exists(preexisting_backup_relation) }}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n -- build model\n {% call statement('main') -%}\n {{ get_create_table_as_sql(False, intermediate_relation, sql) }}\n {%- endcall %}\n\n {% do create_indexes(intermediate_relation) %}\n\n -- cleanup\n {% if existing_relation is not none %}\n /* Do the equivalent of rename_if_exists. 'existing_relation' could have been dropped\n since the variable was first set. */\n {% set existing_relation = load_cached_relation(existing_relation) %}\n {% if existing_relation is not none %}\n {{ adapter.rename_relation(existing_relation, backup_relation) }}\n {% endif %}\n {% endif %}\n\n {{ adapter.rename_relation(intermediate_relation, target_relation) }}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n -- `COMMIT` happens here\n {{ adapter.commit() }}\n\n -- finally, drop the existing/backup relation after the commit\n {{ drop_relation_if_exists(backup_relation) }}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n{% endmaterialization %}", + "meta": {}, + "name": "materialization_table_default", + "original_file_path": "macros/materializations/models/table.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/table.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_table_default" + }, + "macro.dbt.materialization_test_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_limit_subquery_sql", + "macro.dbt.should_store_failures", + "macro.dbt.statement", + "macro.dbt.get_create_sql", + "macro.dbt.get_test_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- materialization test, default -%}\n\n {% set relations = [] %}\n {% set limit = config.get('limit') %}\n {% set sql_header = config.get('sql_header') if flags.REQUIRE_SQL_HEADER_IN_TEST_CONFIGS else none %}\n\n {% set sql_with_limit %}\n {{ get_limit_subquery_sql(sql, limit) }}\n {% endset %}\n\n {% if should_store_failures() %}\n\n {% set identifier = model['alias'] %}\n {% set old_relation = adapter.get_relation(database=database, schema=schema, identifier=identifier) %}\n\n {% set store_failures_as = config.get('store_failures_as') %}\n -- if `--store-failures` is invoked via command line and `store_failures_as` is not set,\n -- config.get('store_failures_as', 'table') returns None, not 'table'\n {% if store_failures_as == none %}{% set store_failures_as = 'table' %}{% endif %}\n {% if store_failures_as not in ['table', 'view'] %}\n {{ exceptions.raise_compiler_error(\n \"'\" ~ store_failures_as ~ \"' is not a valid value for `store_failures_as`. \"\n \"Accepted values are: ['ephemeral', 'table', 'view']\"\n ) }}\n {% endif %}\n\n {% set target_relation = api.Relation.create(\n identifier=identifier, schema=schema, database=database, type=store_failures_as) -%} %}\n\n {% if old_relation %}\n {% do adapter.drop_relation(old_relation) %}\n {% endif %}\n\n {% call statement(auto_begin=True) %}\n {% if sql_header %}{{ sql_header }}{% endif %}\n {{ get_create_sql(target_relation, sql_with_limit) }}\n {% endcall %}\n\n {% do relations.append(target_relation) %}\n\n {# Since the test failures have already been saved to the database, reuse that result rather than querying again #}\n {% set main_sql %}\n select *\n from {{ target_relation }}\n {% endset %}\n\n {{ adapter.commit() }}\n\n {% else %}\n\n {% set main_sql = sql_with_limit %}\n\n {% endif %}\n\n {% set fail_calc = config.get('fail_calc') %}\n {% set warn_if = config.get('warn_if') %}\n {% set error_if = config.get('error_if') %}\n\n {% call statement('main', fetch_result=True) -%}\n\n {% if sql_header %}{{ sql_header }}{% endif %}\n {# The limit has already been included above, and we do not want to duplicate it again. We also want to be safe for macro overrides treating `limit` as a required parameter. #}\n {{ get_test_sql(main_sql, fail_calc, warn_if, error_if, limit=none)}}\n\n {%- endcall %}\n\n {{ return({'relations': relations}) }}\n\n{%- endmaterialization -%}", + "meta": {}, + "name": "materialization_test_default", + "original_file_path": "macros/materializations/tests/test.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/tests/test.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_test_default" + }, + "macro.dbt.materialization_unit_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_columns_in_query", + "macro.dbt.make_temp_relation", + "macro.dbt.run_query", + "macro.dbt.get_create_table_as_sql", + "macro.dbt.get_empty_subquery_sql", + "macro.dbt.get_expected_sql", + "macro.dbt.get_unit_test_sql", + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- materialization unit, default -%}\n\n {% set relations = [] %}\n {% set sql_header = config.get('sql_header') if flags.REQUIRE_SQL_HEADER_IN_TEST_CONFIGS else none %}\n\n {% set expected_rows = config.get('expected_rows') %}\n {% set expected_sql = config.get('expected_sql') %}\n {% set tested_expected_column_names = expected_rows[0].keys() if (expected_rows | length ) > 0 else get_columns_in_query(sql) %}\n\n {%- set target_relation = this.incorporate(type='table') -%}\n {%- set temp_relation = make_temp_relation(target_relation)-%}\n {% do run_query(get_create_table_as_sql(True, temp_relation, get_empty_subquery_sql(sql))) %}\n {%- set columns_in_relation = adapter.get_columns_in_relation(temp_relation) -%}\n {%- set column_name_to_data_types = {} -%}\n {%- set column_name_to_quoted = {} -%}\n {%- for column in columns_in_relation -%}\n {%- do column_name_to_data_types.update({column.name|lower: column.data_type}) -%}\n {%- do column_name_to_quoted.update({column.name|lower: column.quoted}) -%}\n {%- endfor -%}\n\n {%- set expected_column_names_quoted = [] -%}\n {%- for column_name in tested_expected_column_names -%}\n {%- do expected_column_names_quoted.append(column_name_to_quoted[column_name|lower]) -%}\n {%- endfor -%}\n\n {% if not expected_sql %}\n {% set expected_sql = get_expected_sql(expected_rows, column_name_to_data_types, column_name_to_quoted) %}\n {% endif %}\n {% set unit_test_sql = get_unit_test_sql(sql, expected_sql, expected_column_names_quoted) %}\n\n {% call statement('main', fetch_result=True) -%}\n\n {% if sql_header %}{{ sql_header }}{% endif %}\n {{ unit_test_sql }}\n\n {%- endcall %}\n\n {% do adapter.drop_relation(temp_relation) %}\n\n {{ return({'relations': relations}) }}\n\n{%- endmaterialization -%}", + "meta": {}, + "name": "materialization_unit_default", + "original_file_path": "macros/materializations/tests/unit.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/tests/unit.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_unit_default" + }, + "macro.dbt.materialization_view_default": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.make_intermediate_relation", + "macro.dbt.make_backup_relation", + "macro.dbt.run_hooks", + "macro.dbt.drop_relation_if_exists", + "macro.dbt.statement", + "macro.dbt.get_create_view_as_sql", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- materialization view, default -%}\n\n {%- set existing_relation = load_cached_relation(this) -%}\n {%- set target_relation = this.incorporate(type='view') -%}\n {%- set intermediate_relation = make_intermediate_relation(target_relation) -%}\n\n -- the intermediate_relation should not already exist in the database; get_relation\n -- will return None in that case. Otherwise, we get a relation that we can drop\n -- later, before we try to use this name for the current operation\n {%- set preexisting_intermediate_relation = load_cached_relation(intermediate_relation) -%}\n /*\n This relation (probably) doesn't exist yet. If it does exist, it's a leftover from\n a previous run, and we're going to try to drop it immediately. At the end of this\n materialization, we're going to rename the \"existing_relation\" to this identifier,\n and then we're going to drop it. In order to make sure we run the correct one of:\n - drop view ...\n - drop table ...\n\n We need to set the type of this relation to be the type of the existing_relation, if it exists,\n or else \"view\" as a sane default if it does not. Note that if the existing_relation does not\n exist, then there is nothing to move out of the way and subsequentally drop. In that case,\n this relation will be effectively unused.\n */\n {%- set backup_relation_type = 'view' if existing_relation is none else existing_relation.type -%}\n {%- set backup_relation = make_backup_relation(target_relation, backup_relation_type) -%}\n -- as above, the backup_relation should not already exist\n {%- set preexisting_backup_relation = load_cached_relation(backup_relation) -%}\n -- grab current tables grants config for comparision later on\n {% set grant_config = config.get('grants') %}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n -- drop the temp relations if they exist already in the database\n {{ drop_relation_if_exists(preexisting_intermediate_relation) }}\n {{ drop_relation_if_exists(preexisting_backup_relation) }}\n\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n -- build model\n {% call statement('main') -%}\n {{ get_create_view_as_sql(intermediate_relation, sql) }}\n {%- endcall %}\n\n -- cleanup\n -- move the existing view out of the way\n {% if existing_relation is not none %}\n /* Do the equivalent of rename_if_exists. 'existing_relation' could have been dropped\n since the variable was first set. */\n {% set existing_relation = load_cached_relation(existing_relation) %}\n {% if existing_relation is not none %}\n {{ adapter.rename_relation(existing_relation, backup_relation) }}\n {% endif %}\n {% endif %}\n {{ adapter.rename_relation(intermediate_relation, target_relation) }}\n\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n {{ adapter.commit() }}\n\n {{ drop_relation_if_exists(backup_relation) }}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{%- endmaterialization -%}", + "meta": {}, + "name": "materialization_view_default", + "original_file_path": "macros/materializations/models/view.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/view.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt.materialization_view_default" + }, + "macro.dbt.materialized_view_execute_build_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.run_hooks", + "macro.dbt.statement", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro materialized_view_execute_build_sql(build_sql, existing_relation, target_relation, post_hooks) %}\n\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n {% set grant_config = config.get('grants') %}\n\n {% call statement(name=\"main\") %}\n {{ build_sql }}\n {% endcall %}\n\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n {{ adapter.commit() }}\n\n{% endmacro %}", + "meta": {}, + "name": "materialized_view_execute_build_sql", + "original_file_path": "macros/materializations/models/materialized_view.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/materialized_view.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.materialized_view_execute_build_sql" + }, + "macro.dbt.materialized_view_execute_no_op": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro materialized_view_execute_no_op(target_relation) %}\n {% do store_raw_result(\n name=\"main\",\n message=\"skip \" ~ target_relation,\n code=\"skip\",\n rows_affected=\"-1\"\n ) %}\n{% endmacro %}", + "meta": {}, + "name": "materialized_view_execute_no_op", + "original_file_path": "macros/materializations/models/materialized_view.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/materialized_view.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.materialized_view_execute_no_op" + }, + "macro.dbt.materialized_view_get_build_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.should_full_refresh", + "macro.dbt.get_create_materialized_view_as_sql", + "macro.dbt.get_replace_sql", + "macro.dbt.get_materialized_view_configuration_changes", + "macro.dbt.refresh_materialized_view", + "macro.dbt.get_alter_materialized_view_as_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro materialized_view_get_build_sql(existing_relation, target_relation, backup_relation, intermediate_relation) %}\n\n {% set full_refresh_mode = should_full_refresh() %}\n\n -- determine the scenario we're in: create, full_refresh, alter, refresh data\n {% if existing_relation is none %}\n {% set build_sql = get_create_materialized_view_as_sql(target_relation, sql) %}\n {% elif full_refresh_mode or not existing_relation.is_materialized_view %}\n {% set build_sql = get_replace_sql(existing_relation, target_relation, sql) %}\n {% else %}\n\n -- get config options\n {% set on_configuration_change = config.get('on_configuration_change') %}\n {% set configuration_changes = get_materialized_view_configuration_changes(existing_relation, config) %}\n\n {% if configuration_changes is none %}\n {% set build_sql = refresh_materialized_view(target_relation) %}\n\n {% elif on_configuration_change == 'apply' %}\n {% set build_sql = get_alter_materialized_view_as_sql(target_relation, configuration_changes, sql, existing_relation, backup_relation, intermediate_relation) %}\n {% elif on_configuration_change == 'continue' %}\n {% set build_sql = '' %}\n {{ exceptions.warn(\"Configuration changes were identified and `on_configuration_change` was set to `continue` for `\" ~ target_relation.render() ~ \"`\") }}\n {% elif on_configuration_change == 'fail' %}\n {{ exceptions.raise_fail_fast_error(\"Configuration changes were identified and `on_configuration_change` was set to `fail` for `\" ~ target_relation.render() ~ \"`\") }}\n\n {% else %}\n -- this only happens if the user provides a value other than `apply`, 'skip', 'fail'\n {{ exceptions.raise_compiler_error(\"Unexpected configuration scenario\") }}\n\n {% endif %}\n\n {% endif %}\n\n {% do return(build_sql) %}\n\n{% endmacro %}", + "meta": {}, + "name": "materialized_view_get_build_sql", + "original_file_path": "macros/materializations/models/materialized_view.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/materialized_view.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.materialized_view_get_build_sql" + }, + "macro.dbt.materialized_view_setup": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.drop_relation_if_exists", + "macro.dbt.run_hooks" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro materialized_view_setup(backup_relation, intermediate_relation, pre_hooks) %}\n\n -- backup_relation and intermediate_relation should not already exist in the database\n -- it's possible these exist because of a previous run that exited unexpectedly\n {% set preexisting_backup_relation = load_cached_relation(backup_relation) %}\n {% set preexisting_intermediate_relation = load_cached_relation(intermediate_relation) %}\n\n -- drop the temp relations if they exist already in the database\n {{ drop_relation_if_exists(preexisting_backup_relation) }}\n {{ drop_relation_if_exists(preexisting_intermediate_relation) }}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n{% endmacro %}", + "meta": {}, + "name": "materialized_view_setup", + "original_file_path": "macros/materializations/models/materialized_view.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/materialized_view.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.materialized_view_setup" + }, + "macro.dbt.materialized_view_teardown": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.drop_relation_if_exists", + "macro.dbt.run_hooks" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro materialized_view_teardown(backup_relation, intermediate_relation, post_hooks) %}\n\n -- drop the temp relations if they exist to leave the database clean for the next run\n {{ drop_relation_if_exists(backup_relation) }}\n {{ drop_relation_if_exists(intermediate_relation) }}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n{% endmacro %}", + "meta": {}, + "name": "materialized_view_teardown", + "original_file_path": "macros/materializations/models/materialized_view.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/materialized_view.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.materialized_view_teardown" + }, + "macro.dbt.noop_statement": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro noop_statement(name=None, message=None, code=None, rows_affected=None, res=None) -%}\n {%- set sql = caller() -%}\n\n {%- if name == 'main' -%}\n {{ log('Writing runtime SQL for node \"{}\"'.format(model['unique_id'])) }}\n {{ write(sql) }}\n {%- endif -%}\n\n {%- if name is not none -%}\n {{ store_raw_result(name, message=message, code=code, rows_affected=rows_affected, agate_table=res) }}\n {%- endif -%}\n\n{%- endmacro %}", + "meta": {}, + "name": "noop_statement", + "original_file_path": "macros/etc/statement.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/etc/statement.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.noop_statement" + }, + "macro.dbt.partition_range": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.dates_in_range" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro partition_range(raw_partition_date, date_fmt='%Y%m%d') %}\n {% set partition_range = (raw_partition_date | string).split(\",\") %}\n\n {% if (partition_range | length) == 1 %}\n {% set start_date = partition_range[0] %}\n {% set end_date = none %}\n {% elif (partition_range | length) == 2 %}\n {% set start_date = partition_range[0] %}\n {% set end_date = partition_range[1] %}\n {% else %}\n {{ exceptions.raise_compiler_error(\"Invalid partition time. Expected format: {Start Date}[,{End Date}]. Got: \" ~ raw_partition_date) }}\n {% endif %}\n\n {{ return(dates_in_range(start_date, end_date, in_fmt=date_fmt)) }}\n{% endmacro %}", + "meta": {}, + "name": "partition_range", + "original_file_path": "macros/etc/datetime.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/etc/datetime.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.partition_range" + }, + "macro.dbt.persist_docs": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro persist_docs(relation, model, for_relation=true, for_columns=true) -%}\n {{ return(adapter.dispatch('persist_docs', 'dbt')(relation, model, for_relation, for_columns)) }}\n{% endmacro %}", + "meta": {}, + "name": "persist_docs", + "original_file_path": "macros/adapters/persist_docs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.persist_docs" + }, + "macro.dbt.position": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__position" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro position(substring_text, string_text) -%}\n {{ return(adapter.dispatch('position', 'dbt') (substring_text, string_text)) }}\n{% endmacro %}", + "meta": {}, + "name": "position", + "original_file_path": "macros/utils/position.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/position.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.position" + }, + "macro.dbt.post_snapshot": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__post_snapshot" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro post_snapshot(staging_relation) %}\n {{ adapter.dispatch('post_snapshot', 'dbt')(staging_relation) }}\n{% endmacro %}", + "meta": {}, + "name": "post_snapshot", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.post_snapshot" + }, + "macro.dbt.process_schema_changes": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__process_schema_changes" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro process_schema_changes(on_schema_change, source_relation, target_relation) %}\n {{ return(adapter.dispatch('process_schema_changes', 'dbt')(on_schema_change, source_relation, target_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "process_schema_changes", + "original_file_path": "macros/materializations/models/incremental/on_schema_change.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/on_schema_change.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.process_schema_changes" + }, + "macro.dbt.py_current_timestring": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro py_current_timestring() %}\n {% set dt = modules.datetime.datetime.now() %}\n {% do return(dt.strftime(\"%Y%m%d%H%M%S%f\")) %}\n{% endmacro %}", + "meta": {}, + "name": "py_current_timestring", + "original_file_path": "macros/etc/datetime.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/etc/datetime.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.py_current_timestring" + }, + "macro.dbt.py_script_comment": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%macro py_script_comment()%}\n{%endmacro%}", + "meta": {}, + "name": "py_script_comment", + "original_file_path": "macros/python_model/python.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/python_model/python.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.py_script_comment" + }, + "macro.dbt.py_script_postfix": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.build_ref_function", + "macro.dbt.build_source_function", + "macro.dbt.build_config_dict", + "macro.dbt.resolve_model_name", + "macro.dbt.is_incremental", + "macro.dbt.py_script_comment" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro py_script_postfix(model) %}\n# This part is user provided model code\n# you will need to copy the next section to run the code\n# COMMAND ----------\n# this part is dbt logic for get ref work, do not modify\n\n{{ build_ref_function(model ) }}\n{{ build_source_function(model ) }}\n{{ build_config_dict(model) }}\n\nclass config:\n def __init__(self, *args, **kwargs):\n pass\n\n @staticmethod\n def get(key, default=None):\n return config_dict.get(key, default)\n\n @staticmethod\n def meta_get(key, default=None):\n return meta_dict.get(key, default)\n\nclass this:\n \"\"\"dbt.this() or dbt.this.identifier\"\"\"\n database = \"{{ this.database }}\"\n schema = \"{{ this.schema }}\"\n identifier = \"{{ this.identifier }}\"\n {% set this_relation_name = resolve_model_name(this) %}\n def __repr__(self):\n return '{{ this_relation_name }}'\n\n\nclass dbtObj:\n def __init__(self, load_df_function) -> None:\n self.source = lambda *args: source(*args, dbt_load_df_function=load_df_function)\n self.ref = lambda *args, **kwargs: ref(*args, **kwargs, dbt_load_df_function=load_df_function)\n self.config = config\n self.this = this()\n self.is_incremental = {{ is_incremental() }}\n\n# COMMAND ----------\n{{py_script_comment()}}\n{% endmacro %}", + "meta": {}, + "name": "py_script_postfix", + "original_file_path": "macros/python_model/python.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/python_model/python.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.py_script_postfix" + }, + "macro.dbt.refresh_materialized_view": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__refresh_materialized_view" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro refresh_materialized_view(relation) %}\n {{- log('Applying REFRESH to: ' ~ relation) -}}\n {{- adapter.dispatch('refresh_materialized_view', 'dbt')(relation) -}}\n{% endmacro %}", + "meta": {}, + "name": "refresh_materialized_view", + "original_file_path": "macros/relations/materialized_view/refresh.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/materialized_view/refresh.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.refresh_materialized_view" + }, + "macro.dbt.rename_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__rename_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro rename_relation(from_relation, to_relation) -%}\n {{ return(adapter.dispatch('rename_relation', 'dbt')(from_relation, to_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "rename_relation", + "original_file_path": "macros/relations/rename.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/rename.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.rename_relation" + }, + "macro.dbt.replace": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__replace" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro replace(field, old_chars, new_chars) -%}\n {{ return(adapter.dispatch('replace', 'dbt') (field, old_chars, new_chars)) }}\n{% endmacro %}", + "meta": {}, + "name": "replace", + "original_file_path": "macros/utils/replace.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/replace.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.replace" + }, + "macro.dbt.reset_csv_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__reset_csv_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro reset_csv_table(model, full_refresh, old_relation, agate_table) -%}\n {{ adapter.dispatch('reset_csv_table', 'dbt')(model, full_refresh, old_relation, agate_table) }}\n{%- endmacro %}", + "meta": {}, + "name": "reset_csv_table", + "original_file_path": "macros/materializations/seeds/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/seeds/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.reset_csv_table" + }, + "macro.dbt.resolve_model_name": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__resolve_model_name" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro resolve_model_name(input_model_name) %}\n {{ return(adapter.dispatch('resolve_model_name', 'dbt')(input_model_name)) }}\n{% endmacro %}", + "meta": {}, + "name": "resolve_model_name", + "original_file_path": "macros/python_model/python.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/python_model/python.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.resolve_model_name" + }, + "macro.dbt.right": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__right" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro right(string_text, length_expression) -%}\n {{ return(adapter.dispatch('right', 'dbt') (string_text, length_expression)) }}\n{% endmacro %}", + "meta": {}, + "name": "right", + "original_file_path": "macros/utils/right.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/right.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.right" + }, + "macro.dbt.run_hooks": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro run_hooks(hooks, inside_transaction=True) %}\n {% for hook in hooks | selectattr('transaction', 'equalto', inside_transaction) %}\n {% if not inside_transaction and loop.first %}\n {% call statement(auto_begin=inside_transaction) %}\n commit;\n {% endcall %}\n {% endif %}\n {% set rendered = render(hook.get('sql')) | trim %}\n {% if (rendered | length) > 0 %}\n {% call statement(auto_begin=inside_transaction) %}\n {{ rendered }}\n {% endcall %}\n {% endif %}\n {% endfor %}\n{% endmacro %}", + "meta": {}, + "name": "run_hooks", + "original_file_path": "macros/materializations/hooks.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/hooks.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.run_hooks" + }, + "macro.dbt.run_query": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro run_query(sql) %}\n {% call statement(\"run_query_statement\", fetch_result=true, auto_begin=false) %}\n {{ sql }}\n {% endcall %}\n\n {% do return(load_result(\"run_query_statement\").table) %}\n{% endmacro %}", + "meta": {}, + "name": "run_query", + "original_file_path": "macros/etc/statement.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/etc/statement.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.run_query" + }, + "macro.dbt.safe_cast": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__safe_cast" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro safe_cast(field, type) %}\n {{ return(adapter.dispatch('safe_cast', 'dbt') (field, type)) }}\n{% endmacro %}", + "meta": {}, + "name": "safe_cast", + "original_file_path": "macros/utils/safe_cast.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/safe_cast.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.safe_cast" + }, + "macro.dbt.scalar_function_body_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__scalar_function_body_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro scalar_function_body_sql() %}\n {{ return(adapter.dispatch('scalar_function_body_sql', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "scalar_function_body_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.scalar_function_body_sql" + }, + "macro.dbt.scalar_function_create_replace_signature_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__scalar_function_create_replace_signature_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro scalar_function_create_replace_signature_sql(target_relation) %}\n {{ return(adapter.dispatch('scalar_function_create_replace_signature_sql', 'dbt')(target_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "scalar_function_create_replace_signature_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.scalar_function_create_replace_signature_sql" + }, + "macro.dbt.scalar_function_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__scalar_function_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro scalar_function_sql(target_relation) %}\n {{ return(adapter.dispatch('scalar_function_sql', 'dbt')(target_relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "scalar_function_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.scalar_function_sql" + }, + "macro.dbt.scalar_function_volatility_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__scalar_function_volatility_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro scalar_function_volatility_sql() %}\n {{ return(adapter.dispatch('scalar_function_volatility_sql', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "scalar_function_volatility_sql", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.scalar_function_volatility_sql" + }, + "macro.dbt.set_sql_header": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro set_sql_header(config) -%}\n {{ config.set('sql_header', caller()) }}\n{%- endmacro %}", + "meta": {}, + "name": "set_sql_header", + "original_file_path": "macros/materializations/configs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/configs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.set_sql_header" + }, + "macro.dbt.should_full_refresh": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro should_full_refresh() %}\n {% set config_full_refresh = config.get('full_refresh') %}\n {% if config_full_refresh is none %}\n {% set config_full_refresh = flags.FULL_REFRESH %}\n {% endif %}\n {% do return(config_full_refresh) %}\n{% endmacro %}", + "meta": {}, + "name": "should_full_refresh", + "original_file_path": "macros/materializations/configs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/configs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.should_full_refresh" + }, + "macro.dbt.should_revoke": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.copy_grants" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro should_revoke(existing_relation, full_refresh_mode=True) %}\n\n {% if not existing_relation %}\n {#-- The table doesn't already exist, so no grants to copy over --#}\n {{ return(False) }}\n {% elif full_refresh_mode %}\n {#-- The object is being REPLACED -- whether grants are copied over depends on the value of user config --#}\n {{ return(copy_grants()) }}\n {% else %}\n {#-- The table is being merged/upserted/inserted -- grants will be carried over --#}\n {{ return(True) }}\n {% endif %}\n\n{% endmacro %}", + "meta": {}, + "name": "should_revoke", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.should_revoke" + }, + "macro.dbt.should_store_failures": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro should_store_failures() %}\n {% set config_store_failures = config.get('store_failures') %}\n {% if config_store_failures is none %}\n {% set config_store_failures = flags.STORE_FAILURES %}\n {% endif %}\n {% do return(config_store_failures) %}\n{% endmacro %}", + "meta": {}, + "name": "should_store_failures", + "original_file_path": "macros/materializations/configs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/configs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.should_store_failures" + }, + "macro.dbt.snapshot_check_all_get_existing_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_columns_in_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro snapshot_check_all_get_existing_columns(node, target_exists, check_cols_config) -%}\n {%- if not target_exists -%}\n {#-- no table yet -> return whatever the query does --#}\n {{ return((false, query_columns)) }}\n {%- endif -%}\n\n {#-- handle any schema changes --#}\n {%- set target_relation = adapter.get_relation(database=node.database, schema=node.schema, identifier=node.alias) -%}\n\n {% if check_cols_config == 'all' %}\n {%- set query_columns = get_columns_in_query(node['compiled_code']) -%}\n\n {% elif check_cols_config is iterable and (check_cols_config | length) > 0 %}\n {#-- query for proper casing/quoting, to support comparison below --#}\n {%- set select_check_cols_from_target -%}\n {#-- N.B. The whitespace below is necessary to avoid edge case issue with comments --#}\n {#-- See: https://github.com/dbt-labs/dbt-core/issues/6781 --#}\n select {{ check_cols_config | join(', ') }} from (\n {{ node['compiled_code'] }}\n ) subq\n {%- endset -%}\n {% set query_columns = get_columns_in_query(select_check_cols_from_target) %}\n\n {% else %}\n {% do exceptions.raise_compiler_error(\"Invalid value for 'check_cols': \" ~ check_cols_config) %}\n {% endif %}\n\n {%- set existing_cols = adapter.get_columns_in_relation(target_relation) | map(attribute = 'name') | list -%}\n {%- set ns = namespace() -%} {#-- handle for-loop scoping with a namespace --#}\n {%- set ns.column_added = false -%}\n\n {%- set intersection = [] -%}\n {%- for col in query_columns -%}\n {%- if col in existing_cols -%}\n {%- do intersection.append(adapter.quote(col)) -%}\n {%- else -%}\n {% set ns.column_added = true %}\n {%- endif -%}\n {%- endfor -%}\n {{ return((ns.column_added, intersection)) }}\n{%- endmacro %}", + "meta": {}, + "name": "snapshot_check_all_get_existing_columns", + "original_file_path": "macros/materializations/snapshots/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.snapshot_check_all_get_existing_columns" + }, + "macro.dbt.snapshot_check_strategy": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.snapshot_get_time", + "macro.dbt.snapshot_check_all_get_existing_columns", + "macro.dbt.get_true_sql", + "macro.dbt.snapshot_hash_arguments" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro snapshot_check_strategy(node, snapshotted_rel, current_rel, model_config, target_exists) %}\n {# The model_config parameter is no longer used, but is passed in anyway for compatibility. #}\n {% set check_cols_config = config.get('check_cols') %}\n {% set primary_key = config.get('unique_key') %}\n {% set hard_deletes = adapter.get_hard_deletes_behavior(config) %}\n {% set invalidate_hard_deletes = hard_deletes == 'invalidate' %}\n {% set updated_at = config.get('updated_at') or snapshot_get_time() %}\n\n {% set column_added = false %}\n\n {% set column_added, check_cols = snapshot_check_all_get_existing_columns(node, target_exists, check_cols_config) %}\n\n {%- set row_changed_expr -%}\n (\n {%- if column_added -%}\n {{ get_true_sql() }}\n {%- else -%}\n {%- for col in check_cols -%}\n {{ snapshotted_rel }}.{{ col }} != {{ current_rel }}.{{ col }}\n or\n (\n (({{ snapshotted_rel }}.{{ col }} is null) and not ({{ current_rel }}.{{ col }} is null))\n or\n ((not {{ snapshotted_rel }}.{{ col }} is null) and ({{ current_rel }}.{{ col }} is null))\n )\n {%- if not loop.last %} or {% endif -%}\n {%- endfor -%}\n {%- endif -%}\n )\n {%- endset %}\n\n {% set scd_args = api.Relation.scd_args(primary_key, updated_at) %}\n {% set scd_id_expr = snapshot_hash_arguments(scd_args) %}\n\n {% do return({\n \"unique_key\": primary_key,\n \"updated_at\": updated_at,\n \"row_changed\": row_changed_expr,\n \"scd_id\": scd_id_expr,\n \"invalidate_hard_deletes\": invalidate_hard_deletes,\n \"hard_deletes\": hard_deletes\n }) %}\n{% endmacro %}", + "meta": {}, + "name": "snapshot_check_strategy", + "original_file_path": "macros/materializations/snapshots/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.snapshot_check_strategy" + }, + "macro.dbt.snapshot_get_time": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__snapshot_get_time" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro snapshot_get_time() -%}\n {{ adapter.dispatch('snapshot_get_time', 'dbt')() }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "snapshot_get_time", + "original_file_path": "macros/adapters/timestamps.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/timestamps.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.snapshot_get_time" + }, + "macro.dbt.snapshot_hash_arguments": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__snapshot_hash_arguments" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro snapshot_hash_arguments(args) -%}\n {{ adapter.dispatch('snapshot_hash_arguments', 'dbt')(args) }}\n{%- endmacro %}", + "meta": {}, + "name": "snapshot_hash_arguments", + "original_file_path": "macros/materializations/snapshots/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.snapshot_hash_arguments" + }, + "macro.dbt.snapshot_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__snapshot_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro snapshot_merge_sql(target, source, insert_cols) -%}\n {{ adapter.dispatch('snapshot_merge_sql', 'dbt')(target, source, insert_cols) }}\n{%- endmacro %}", + "meta": {}, + "name": "snapshot_merge_sql", + "original_file_path": "macros/materializations/snapshots/snapshot_merge.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/snapshot_merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.snapshot_merge_sql" + }, + "macro.dbt.snapshot_staging_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__snapshot_staging_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro snapshot_staging_table(strategy, source_sql, target_relation) -%}\n {{ adapter.dispatch('snapshot_staging_table', 'dbt')(strategy, source_sql, target_relation) }}\n{% endmacro %}", + "meta": {}, + "name": "snapshot_staging_table", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.snapshot_staging_table" + }, + "macro.dbt.snapshot_string_as_time": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__snapshot_string_as_time" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro snapshot_string_as_time(timestamp) -%}\n {{ adapter.dispatch('snapshot_string_as_time', 'dbt')(timestamp) }}\n{%- endmacro %}", + "meta": {}, + "name": "snapshot_string_as_time", + "original_file_path": "macros/materializations/snapshots/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.snapshot_string_as_time" + }, + "macro.dbt.snapshot_timestamp_strategy": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_snapshot_table_column_names", + "macro.dbt.snapshot_hash_arguments" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro snapshot_timestamp_strategy(node, snapshotted_rel, current_rel, model_config, target_exists) %}\n {# The model_config parameter is no longer used, but is passed in anyway for compatibility. #}\n {% set primary_key = config.get('unique_key') %}\n {% set updated_at = config.get('updated_at') %}\n {% set hard_deletes = adapter.get_hard_deletes_behavior(config) %}\n {% set invalidate_hard_deletes = hard_deletes == 'invalidate' %}\n {% set columns = config.get(\"snapshot_table_column_names\") or get_snapshot_table_column_names() %}\n\n {#/*\n The snapshot relation might not have an {{ updated_at }} value if the\n snapshot strategy is changed from `check` to `timestamp`. We\n should use a dbt-created column for the comparison in the snapshot\n table instead of assuming that the user-supplied {{ updated_at }}\n will be present in the historical data.\n\n See https://github.com/dbt-labs/dbt-core/issues/2350\n */ #}\n {% set row_changed_expr -%}\n ({{ snapshotted_rel }}.{{ columns.dbt_valid_from }} < {{ current_rel }}.{{ updated_at }})\n {%- endset %}\n\n {% set scd_args = api.Relation.scd_args(primary_key, updated_at) %}\n {% set scd_id_expr = snapshot_hash_arguments(scd_args) %}\n\n {% do return({\n \"unique_key\": primary_key,\n \"updated_at\": updated_at,\n \"row_changed\": row_changed_expr,\n \"scd_id\": scd_id_expr,\n \"invalidate_hard_deletes\": invalidate_hard_deletes,\n \"hard_deletes\": hard_deletes\n }) %}\n{% endmacro %}", + "meta": {}, + "name": "snapshot_timestamp_strategy", + "original_file_path": "macros/materializations/snapshots/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.snapshot_timestamp_strategy" + }, + "macro.dbt.split_part": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb__split_part" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro split_part(string_text, delimiter_text, part_number) %}\n {{ return(adapter.dispatch('split_part', 'dbt') (string_text, delimiter_text, part_number)) }}\n{% endmacro %}", + "meta": {}, + "name": "split_part", + "original_file_path": "macros/utils/split_part.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/split_part.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.split_part" + }, + "macro.dbt.sql_convert_columns_in_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro sql_convert_columns_in_relation(table) -%}\n {% set columns = [] %}\n {% for row in table %}\n {% do columns.append(api.Column(*row)) %}\n {% endfor %}\n {{ return(columns) }}\n{% endmacro %}", + "meta": {}, + "name": "sql_convert_columns_in_relation", + "original_file_path": "macros/adapters/columns.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.sql_convert_columns_in_relation" + }, + "macro.dbt.statement": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n{%- macro statement(name=None, fetch_result=False, auto_begin=True, language='sql') -%}\n {%- if execute: -%}\n {%- set compiled_code = caller() -%}\n\n {%- if name == 'main' -%}\n {{ log('Writing runtime {} for node \"{}\"'.format(language, model['unique_id'])) }}\n {{ write(compiled_code) }}\n {%- endif -%}\n {%- if language == 'sql'-%}\n {%- set res, table = adapter.execute(compiled_code, auto_begin=auto_begin, fetch=fetch_result) -%}\n {%- elif language == 'python' -%}\n {%- set res = submit_python_job(model, compiled_code) -%}\n {#-- TODO: What should table be for python models? --#}\n {%- set table = None -%}\n {%- else -%}\n {% do exceptions.raise_compiler_error(\"statement macro didn't get supported language\") %}\n {%- endif -%}\n\n {%- if name is not none -%}\n {{ store_result(name, response=res, agate_table=table) }}\n {%- endif -%}\n\n {%- endif -%}\n{%- endmacro %}", + "meta": {}, + "name": "statement", + "original_file_path": "macros/etc/statement.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/etc/statement.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.statement" + }, + "macro.dbt.strategy_dispatch": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro strategy_dispatch(name) -%}\n{% set original_name = name %}\n {% if '.' in name %}\n {% set package_name, name = name.split(\".\", 1) %}\n {% else %}\n {% set package_name = none %}\n {% endif %}\n\n {% if package_name is none %}\n {% set package_context = context %}\n {% elif package_name in context %}\n {% set package_context = context[package_name] %}\n {% else %}\n {% set error_msg %}\n Could not find package '{{package_name}}', called with '{{original_name}}'\n {% endset %}\n {{ exceptions.raise_compiler_error(error_msg | trim) }}\n {% endif %}\n\n {%- set search_name = 'snapshot_' ~ name ~ '_strategy' -%}\n\n {% if search_name not in package_context %}\n {% set error_msg %}\n The specified strategy macro '{{name}}' was not found in package '{{ package_name }}'\n {% endset %}\n {{ exceptions.raise_compiler_error(error_msg | trim) }}\n {% endif %}\n {{ return(package_context[search_name]) }}\n{%- endmacro %}", + "meta": {}, + "name": "strategy_dispatch", + "original_file_path": "macros/materializations/snapshots/strategies.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/strategies.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.strategy_dispatch" + }, + "macro.dbt.string_literal": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__string_literal" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro string_literal(value) -%}\n {{ return(adapter.dispatch('string_literal', 'dbt') (value)) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "string_literal", + "original_file_path": "macros/utils/literal.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/literal.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.string_literal" + }, + "macro.dbt.support_multiple_grantees_per_dcl_statement": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__support_multiple_grantees_per_dcl_statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro support_multiple_grantees_per_dcl_statement() %}\n {{ return(adapter.dispatch('support_multiple_grantees_per_dcl_statement', 'dbt')()) }}\n{% endmacro %}", + "meta": {}, + "name": "support_multiple_grantees_per_dcl_statement", + "original_file_path": "macros/adapters/apply_grants.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/apply_grants.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.support_multiple_grantees_per_dcl_statement" + }, + "macro.dbt.sync_column_schemas": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__sync_column_schemas" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro sync_column_schemas(on_schema_change, target_relation, schema_changes_dict) %}\n {{ return(adapter.dispatch('sync_column_schemas', 'dbt')(on_schema_change, target_relation, schema_changes_dict)) }}\n{% endmacro %}", + "meta": {}, + "name": "sync_column_schemas", + "original_file_path": "macros/materializations/models/incremental/on_schema_change.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/models/incremental/on_schema_change.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.sync_column_schemas" + }, + "macro.dbt.table_columns_and_constraints": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro table_columns_and_constraints() %}\n {# loop through user_provided_columns to create DDL with data types and constraints #}\n {%- set raw_column_constraints = adapter.render_raw_columns_constraints(raw_columns=model['columns']) -%}\n {%- set raw_model_constraints = adapter.render_raw_model_constraints(raw_constraints=model['constraints']) -%}\n (\n {% for c in raw_column_constraints -%}\n {{ c }}{{ \",\" if not loop.last or raw_model_constraints }}\n {% endfor %}\n {% for c in raw_model_constraints -%}\n {{ c }}{{ \",\" if not loop.last }}\n {% endfor -%}\n )\n{% endmacro %}", + "meta": {}, + "name": "table_columns_and_constraints", + "original_file_path": "macros/relations/column/columns_spec_ddl.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/relations/column/columns_spec_ddl.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.table_columns_and_constraints" + }, + "macro.dbt.test_accepted_values": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__test_accepted_values" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% test accepted_values(model, column_name, values, quote=True) %}\n {% set macro = adapter.dispatch('test_accepted_values', 'dbt') %}\n {{ macro(model, column_name, values, quote) }}\n{% endtest %}", + "meta": {}, + "name": "test_accepted_values", + "original_file_path": "tests/generic/builtin.sql", + "package_name": "dbt", + "patch_path": null, + "path": "tests/generic/builtin.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.test_accepted_values" + }, + "macro.dbt.test_not_null": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__test_not_null" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% test not_null(model, column_name) %}\n {% set macro = adapter.dispatch('test_not_null', 'dbt') %}\n {{ macro(model, column_name) }}\n{% endtest %}", + "meta": {}, + "name": "test_not_null", + "original_file_path": "tests/generic/builtin.sql", + "package_name": "dbt", + "patch_path": null, + "path": "tests/generic/builtin.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.test_not_null" + }, + "macro.dbt.test_relationships": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__test_relationships" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% test relationships(model, column_name, to, field) %}\n {% set macro = adapter.dispatch('test_relationships', 'dbt') %}\n {{ macro(model, column_name, to, field) }}\n{% endtest %}", + "meta": {}, + "name": "test_relationships", + "original_file_path": "tests/generic/builtin.sql", + "package_name": "dbt", + "patch_path": null, + "path": "tests/generic/builtin.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.test_relationships" + }, + "macro.dbt.test_unique": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__test_unique" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% test unique(model, column_name) %}\n {% set macro = adapter.dispatch('test_unique', 'dbt') %}\n {{ macro(model, column_name) }}\n{% endtest %}", + "meta": {}, + "name": "test_unique", + "original_file_path": "tests/generic/builtin.sql", + "package_name": "dbt", + "patch_path": null, + "path": "tests/generic/builtin.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.test_unique" + }, + "macro.dbt.truncate_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__truncate_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro truncate_relation(relation) -%}\n {{ return(adapter.dispatch('truncate_relation', 'dbt')(relation)) }}\n{% endmacro %}", + "meta": {}, + "name": "truncate_relation", + "original_file_path": "macros/adapters/relation.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/relation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.truncate_relation" + }, + "macro.dbt.type_bigint": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__type_bigint" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro type_bigint() -%}\n {{ return(adapter.dispatch('type_bigint', 'dbt')()) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "type_bigint", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.type_bigint" + }, + "macro.dbt.type_boolean": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__type_boolean" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro type_boolean() -%}\n {{ return(adapter.dispatch('type_boolean', 'dbt')()) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "type_boolean", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.type_boolean" + }, + "macro.dbt.type_float": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__type_float" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro type_float() -%}\n {{ return(adapter.dispatch('type_float', 'dbt')()) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "type_float", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.type_float" + }, + "macro.dbt.type_int": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__type_int" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro type_int() -%}\n {{ return(adapter.dispatch('type_int', 'dbt')()) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "type_int", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.type_int" + }, + "macro.dbt.type_numeric": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__type_numeric" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro type_numeric() -%}\n {{ return(adapter.dispatch('type_numeric', 'dbt')()) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "type_numeric", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.type_numeric" + }, + "macro.dbt.type_string": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__type_string" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro type_string() -%}\n {{ return(adapter.dispatch('type_string', 'dbt')()) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "type_string", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.type_string" + }, + "macro.dbt.type_timestamp": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__type_timestamp" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n{%- macro type_timestamp() -%}\n {{ return(adapter.dispatch('type_timestamp', 'dbt')()) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "type_timestamp", + "original_file_path": "macros/utils/data_types.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/utils/data_types.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.type_timestamp" + }, + "macro.dbt.unique_key_fields": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro unique_key_fields(unique_key) %}\n {% if unique_key | is_list %}\n {% for key in unique_key %}\n {{ key }} as dbt_unique_key_{{ loop.index }}\n {%- if not loop.last %} , {%- endif %}\n {% endfor %}\n {% else %}\n {{ unique_key }} as dbt_unique_key\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "unique_key_fields", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.unique_key_fields" + }, + "macro.dbt.unique_key_is_not_null": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro unique_key_is_not_null(unique_key, identifier) %}\n {% if unique_key | is_list %}\n {{ identifier }}.dbt_unique_key_1 is not null\n {% else %}\n {{ identifier }}.dbt_unique_key is not null\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "unique_key_is_not_null", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.unique_key_is_not_null" + }, + "macro.dbt.unique_key_is_null": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro unique_key_is_null(unique_key, identifier) %}\n {% if unique_key | is_list %}\n {{ identifier }}.dbt_unique_key_1 is null\n {% else %}\n {{ identifier }}.dbt_unique_key is null\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "unique_key_is_null", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.unique_key_is_null" + }, + "macro.dbt.unique_key_join_on": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.equals" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro unique_key_join_on(unique_key, identifier, from_identifier) %}\n {% if unique_key | is_list %}\n {% for key in unique_key %}\n\t {% set source_unique_key = (identifier ~ \".dbt_unique_key_\" ~ loop.index) | trim %}\n\t {% set target_unique_key = (from_identifier ~ \".dbt_unique_key_\" ~ loop.index) | trim %}\n\t {{ equals(source_unique_key, target_unique_key) }}\n {%- if not loop.last %} and {%- endif %}\n {% endfor %}\n {% else %}\n {{ identifier }}.dbt_unique_key = {{ from_identifier }}.dbt_unique_key\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "unique_key_join_on", + "original_file_path": "macros/materializations/snapshots/helpers.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/snapshots/helpers.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.unique_key_join_on" + }, + "macro.dbt.unsupported_volatility_warning": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__unsupported_volatility_warning" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro unsupported_volatility_warning(volatility) %}\n {{ return(adapter.dispatch('unsupported_volatility_warning', 'dbt')(volatility)) }}\n{% endmacro %}", + "meta": {}, + "name": "unsupported_volatility_warning", + "original_file_path": "macros/materializations/functions/scalar.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/materializations/functions/scalar.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.unsupported_volatility_warning" + }, + "macro.dbt.validate_doc_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro validate_doc_columns(relation, column_dict, existing_column_names) %}\n {% set existing_lower = existing_column_names | map(\"lower\") | list %}\n {% set missing = [] %}\n {% for col_name in column_dict %}\n {% if col_name | lower not in existing_lower %}\n {% do missing.append(col_name) %}\n {% endif %}\n {% endfor %}\n {% if missing | length > 0 %}\n {{ exceptions.warn(\"In relation \" ~ relation.render() ~ \": The following columns are specified in the schema but are not present in the database: \" ~ missing | join(\", \")) }}\n {% endif %}\n {% set filtered = {} %}\n {% for col_name in column_dict if col_name | lower in existing_lower %}\n {% do filtered.update({col_name: column_dict[col_name]}) %}\n {% endfor %}\n {{ return(filtered) }}\n{% endmacro %}", + "meta": {}, + "name": "validate_doc_columns", + "original_file_path": "macros/adapters/persist_docs.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.validate_doc_columns" + }, + "macro.dbt.validate_fixture_rows": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__validate_fixture_rows" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro validate_fixture_rows(rows, row_number) -%}\n {{ return(adapter.dispatch('validate_fixture_rows', 'dbt')(rows, row_number)) }}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "validate_fixture_rows", + "original_file_path": "macros/unit_test_sql/get_fixture_sql.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/unit_test_sql/get_fixture_sql.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.validate_fixture_rows" + }, + "macro.dbt.validate_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.default__validate_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro validate_sql(sql) -%}\n {{ return(adapter.dispatch('validate_sql', 'dbt')(sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "validate_sql", + "original_file_path": "macros/adapters/validate_sql.sql", + "package_name": "dbt", + "patch_path": null, + "path": "macros/adapters/validate_sql.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt.validate_sql" + }, + "macro.dbt_duckdb.build_snapshot_staging_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.make_temp_relation", + "macro.dbt.snapshot_staging_table", + "macro.dbt.statement", + "macro.dbt.create_table_as" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro build_snapshot_staging_table(strategy, sql, target_relation) %}\n {% set temp_relation = make_temp_relation(target_relation) %}\n\n {% set select = snapshot_staging_table(strategy, sql, target_relation) %}\n\n {% call statement('build_snapshot_staging_relation') %}\n {{ create_table_as(False, temp_relation, select) }}\n {% endcall %}\n\n {% do return(temp_relation) %}\n{% endmacro %}", + "meta": {}, + "name": "build_snapshot_staging_table", + "original_file_path": "macros/snapshot_helper.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/snapshot_helper.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.build_snapshot_staging_table" + }, + "macro.dbt_duckdb.drop_indexes_on_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro drop_indexes_on_relation(relation) -%}\n {% call statement('get_indexes_on_relation', fetch_result=True) %}\n SELECT index_name\n FROM duckdb_indexes()\n WHERE schema_name = '{{ relation.schema }}'\n AND table_name = '{{ relation.identifier }}'\n {% endcall %}\n\n {% set results = load_result('get_indexes_on_relation').table %}\n {% for row in results %}\n {% set index_name = row[0] %}\n {% call statement('drop_index_' + loop.index|string, auto_begin=false) %}\n DROP INDEX \"{{ relation.schema }}\".\"{{ index_name }}\"\n {% endcall %}\n {% endfor %}\n\n {#-- Verify indexes were dropped --#}\n {% call statement('verify_indexes_dropped', fetch_result=True) %}\n SELECT COUNT(*) as remaining_indexes\n FROM duckdb_indexes()\n WHERE schema_name = '{{ relation.schema }}'\n AND table_name = '{{ relation.identifier }}'\n {% endcall %}\n {% set verify_results = load_result('verify_indexes_dropped').table %}\n{%- endmacro %}", + "meta": {}, + "name": "drop_indexes_on_relation", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.drop_indexes_on_relation" + }, + "macro.dbt_duckdb.duckdb__alter_column_comment": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb_escape_comment" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__alter_column_comment(relation, column_dict) %}\n {% set existing_columns = adapter.get_columns_in_relation(relation) | map(attribute=\"name\") | list %}\n {% for column_name in column_dict if (column_name in existing_columns) %}\n {% set comment = column_dict[column_name]['description'] %}\n {% set escaped_comment = duckdb_escape_comment(comment) %}\n comment on column {{ relation }}.{{ adapter.quote(column_name) if column_dict[column_name]['quote'] else column_name }} is {{ escaped_comment }};\n {% endfor %}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__alter_column_comment", + "original_file_path": "macros/persist_docs.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__alter_column_comment" + }, + "macro.dbt_duckdb.duckdb__alter_relation_add_remove_columns": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.run_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__alter_relation_add_remove_columns(relation, add_columns, remove_columns) %}\n\n {% if add_columns %}\n {% for column in add_columns %}\n {% set sql -%}\n alter {{ relation.type }} {{ relation }} add column\n {{ api.Relation.create(identifier=column.name) }} {{ column.data_type }}\n {%- endset -%}\n {% do run_query(sql) %}\n {% endfor %}\n {% endif %}\n\n {% if remove_columns %}\n {% for column in remove_columns %}\n {% set sql -%}\n alter {{ relation.type }} {{ relation }} drop column\n {{ api.Relation.create(identifier=column.name) }}\n {%- endset -%}\n {% do run_query(sql) %}\n {% endfor %}\n {% endif %}\n\n{% endmacro %}", + "meta": {}, + "name": "duckdb__alter_relation_add_remove_columns", + "original_file_path": "macros/columns.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/columns.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__alter_relation_add_remove_columns" + }, + "macro.dbt_duckdb.duckdb__alter_relation_comment": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.duckdb_escape_comment" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__alter_relation_comment(relation, comment) %}\n {% set escaped_comment = duckdb_escape_comment(comment) %}\n comment on {{ relation.type }} {{ relation }} is {{ escaped_comment }};\n{% endmacro %}", + "meta": {}, + "name": "duckdb__alter_relation_comment", + "original_file_path": "macros/persist_docs.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__alter_relation_comment" + }, + "macro.dbt_duckdb.duckdb__any_value": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__any_value(expression) -%}\n\n arbitrary({{ expression }})\n\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__any_value", + "original_file_path": "macros/utils/any_value.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/any_value.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__any_value" + }, + "macro.dbt_duckdb.duckdb__apply_grants": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__apply_grants(relation, grant_config, should_revoke=True) %}\n {#-- If grant_config is {} or None, this is a no-op --#}\n {% if grant_config %}\n {{ adapter.warn_once('Grants for relations are not supported by DuckDB') }}\n {% endif %}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__apply_grants", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__apply_grants" + }, + "macro.dbt_duckdb.duckdb__check_schema_exists": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.run_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__check_schema_exists(information_schema, schema) -%}\n {% set sql -%}\n select count(*)\n from system.information_schema.schemata\n where lower(schema_name) = '{{ schema | lower }}'\n and lower(catalog_name) = '{{ information_schema.database | lower }}'\n {%- endset %}\n {{ return(run_query(sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__check_schema_exists", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__check_schema_exists" + }, + "macro.dbt_duckdb.duckdb__create_schema": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement", + "macro.dbt.run_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__create_schema(relation) -%}\n {%- call statement('create_schema') -%}\n {% set sql %}\n select type from duckdb_databases()\n where lower(database_name)='{{ relation.database | lower }}'\n and type='sqlite'\n {% endset %}\n {% set results = run_query(sql) %}\n {% if results|length == 0 %}\n create schema if not exists {{ relation.without_identifier() }}\n {% else %}\n {% if relation.schema!='main' %}\n {{ exceptions.raise_compiler_error(\n \"Schema must be 'main' when writing to sqlite \"\n ~ \"instead got \" ~ relation.schema\n )}}\n {% endif %}\n {% endif %}\n {%- endcall -%}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__create_schema", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__create_schema" + }, + "macro.dbt_duckdb.duckdb__create_table_as": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_assert_columns_equivalent", + "macro.dbt.get_table_columns_and_constraints", + "macro.dbt_duckdb.get_column_names", + "macro.dbt.get_select_subquery", + "macro.dbt_duckdb.py_write_table" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__create_table_as(temporary, relation, compiled_code, language='sql') -%}\n {%- if language == 'sql' -%}\n {% set contract_config = config.get('contract') %}\n {% if contract_config.enforced %}\n {{ get_assert_columns_equivalent(compiled_code) }}\n {% endif %}\n {%- set sql_header = config.get('sql_header', none) -%}\n\n {{ sql_header if sql_header is not none }}\n\n create {% if temporary: -%}temporary{%- endif %} table\n {{ relation.include(database=(not temporary), schema=(not temporary)) }}\n {% if contract_config.enforced and not temporary %}\n {#-- DuckDB doesnt support constraints on temp tables --#}\n {{ get_table_columns_and_constraints() }} ;\n insert into {{ relation }} {{ get_column_names() }} (\n {{ get_select_subquery(compiled_code) }}\n );\n {% else %}\n as (\n {{ compiled_code }}\n );\n {% endif %}\n {%- elif language == 'python' -%}\n {{ py_write_table(temporary=temporary, relation=relation, compiled_code=compiled_code) }}\n {%- else -%}\n {% do exceptions.raise_compiler_error(\"duckdb__create_table_as macro didn't get supported language, it got %s\" % language) %}\n {%- endif -%}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__create_table_as", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__create_table_as" + }, + "macro.dbt_duckdb.duckdb__create_view_as": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_assert_columns_equivalent" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__create_view_as(relation, sql) -%}\n {% set contract_config = config.get('contract') %}\n {% if contract_config.enforced %}\n {{ get_assert_columns_equivalent(sql) }}\n {%- endif %}\n {%- set sql_header = config.get('sql_header', none) -%}\n\n {{ sql_header if sql_header is not none }}\n create view {{ relation }} as (\n {{ sql }}\n );\n{% endmacro %}", + "meta": {}, + "name": "duckdb__create_view_as", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__create_view_as" + }, + "macro.dbt_duckdb.duckdb__current_timestamp": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__current_timestamp() -%}\n now()\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__current_timestamp", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__current_timestamp" + }, + "macro.dbt_duckdb.duckdb__dateadd": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__dateadd(datepart, interval, from_date_or_timestamp) %}\n\n {#\n Support both literal and expression intervals (e.g., column references)\n by multiplying an INTERVAL by the value. This avoids DuckDB parser issues\n with \"interval () \" and works across versions.\n\n Also map unsupported units:\n - quarter => 3 months\n - week => 7 days (DuckDB supports WEEK as a literal, but keep it explicit)\n #}\n\n {%- set unit = datepart | lower -%}\n {%- if unit == 'quarter' -%}\n ({{ from_date_or_timestamp }} + (cast({{ interval }} as bigint) * 3) * interval 1 month)\n {%- elif unit == 'week' -%}\n ({{ from_date_or_timestamp }} + (cast({{ interval }} as bigint) * 7) * interval 1 day)\n {%- else -%}\n ({{ from_date_or_timestamp }} + cast({{ interval }} as bigint) * interval 1 {{ unit }})\n {%- endif -%}\n\n{% endmacro %}", + "meta": {}, + "name": "duckdb__dateadd", + "original_file_path": "macros/utils/dateadd.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/dateadd.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__dateadd" + }, + "macro.dbt_duckdb.duckdb__datediff": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.datediff" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__datediff(first_date, second_date, datepart) -%}\n {% if datepart == 'week' %}\n ({{ datediff(first_date, second_date, 'day') }} // 7 + case\n when date_part('dow', ({{first_date}})::timestamp) <= date_part('dow', ({{second_date}})::timestamp) then\n case when {{first_date}} <= {{second_date}} then 0 else -1 end\n else\n case when {{first_date}} <= {{second_date}} then 1 else 0 end\n end)\n {% else %}\n (date_diff('{{ datepart }}', {{ first_date }}::timestamp, {{ second_date}}::timestamp ))\n {% endif %}\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__datediff", + "original_file_path": "macros/utils/datediff.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/datediff.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__datediff" + }, + "macro.dbt_duckdb.duckdb__drop_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__drop_relation(relation) -%}\n {% call statement('drop_relation', auto_begin=False) -%}\n {% if adapter.is_ducklake(relation) %}\n drop {{ relation.type }} if exists {{ relation }}\n {% else %}\n drop {{ relation.type }} if exists {{ relation }} cascade\n {% endif %}\n {%- endcall %}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__drop_relation", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__drop_relation" + }, + "macro.dbt_duckdb.duckdb__drop_schema": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__drop_schema(relation) -%}\n {%- call statement('drop_schema') -%}\n drop schema if exists {{ relation.without_identifier() }} cascade\n {%- endcall -%}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__drop_schema", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__drop_schema" + }, + "macro.dbt_duckdb.duckdb__generate_series": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__generate_series(upper_bound) %}\n select\n generate_series as generated_number\n from generate_series(1, {{ upper_bound }})\n{% endmacro %}", + "meta": {}, + "name": "duckdb__generate_series", + "original_file_path": "macros/utils/generate_series.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/generate_series.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__generate_series" + }, + "macro.dbt_duckdb.duckdb__get_batch_size": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_batch_size() %}\n {{ return(10000) }}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__get_batch_size", + "original_file_path": "macros/seed.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/seed.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_batch_size" + }, + "macro.dbt_duckdb.duckdb__get_binding_char": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_binding_char() %}\n {{ return(adapter.get_binding_char()) }}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__get_binding_char", + "original_file_path": "macros/seed.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/seed.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_binding_char" + }, + "macro.dbt_duckdb.duckdb__get_catalog": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_catalog(information_schema, schemas) -%}\n {%- call statement('catalog', fetch_result=True) -%}\n with relations AS (\n select\n t.table_name\n , t.database_name\n , t.schema_name\n , 'BASE TABLE' as table_type\n , t.comment as table_comment\n from duckdb_tables() t\n WHERE t.database_name = '{{ database }}'\n UNION ALL\n SELECT v.view_name as table_name\n , v.database_name\n , v.schema_name\n , 'VIEW' as table_type\n , v.comment as table_comment\n from duckdb_views() v\n WHERE v.database_name = '{{ database }}'\n )\n select\n '{{ database }}' as table_database,\n r.schema_name as table_schema,\n r.table_name,\n r.table_type,\n r.table_comment,\n c.column_name,\n c.column_index as column_index,\n c.data_type as column_type,\n c.comment as column_comment,\n NULL as table_owner\n FROM relations r JOIN duckdb_columns() c ON r.schema_name = c.schema_name AND r.table_name = c.table_name\n WHERE (\n {%- for schema in schemas -%}\n upper(r.schema_name) = upper('{{ schema }}'){%- if not loop.last %} or {% endif -%}\n {%- endfor -%}\n )\n ORDER BY\n r.schema_name,\n r.table_name,\n c.column_index\n {%- endcall -%}\n {{ return(load_result('catalog').table) }}\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__get_catalog", + "original_file_path": "macros/catalog.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/catalog.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_catalog" + }, + "macro.dbt_duckdb.duckdb__get_columns_in_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement", + "macro.dbt.sql_convert_columns_in_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_columns_in_relation(relation) -%}\n {% call statement('get_columns_in_relation', fetch_result=True) %}\n select\n column_name,\n data_type,\n character_maximum_length,\n numeric_precision,\n numeric_scale\n\n from system.information_schema.columns\n where table_name = '{{ relation.identifier }}'\n {% if relation.schema %}\n and lower(table_schema) = '{{ relation.schema | lower }}'\n {% endif %}\n {% if relation.database %}\n and lower(table_catalog) = '{{ relation.database | lower }}'\n {% endif %}\n order by ordinal_position\n\n {% endcall %}\n {% set table = load_result('get_columns_in_relation').table %}\n {{ return(sql_convert_columns_in_relation(table)) }}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__get_columns_in_relation", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_columns_in_relation" + }, + "macro.dbt_duckdb.duckdb__get_create_index_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_create_index_sql(relation, index_dict) -%}\n {%- set index_config = adapter.parse_index(index_dict) -%}\n {%- set comma_separated_columns = \", \".join(index_config.columns) -%}\n {%- set index_name = index_config.render(relation) -%}\n\n create {% if index_config.unique -%}\n unique\n {%- endif %} index\n \"{{ index_name }}\"\n on {{ relation }}\n ({{ comma_separated_columns }});\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__get_create_index_sql", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_create_index_sql" + }, + "macro.dbt_duckdb.duckdb__get_delete_insert_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_quoted_csv" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_delete_insert_merge_sql(target, source, unique_key, dest_columns, incremental_predicates) -%}\n\n {%- set dest_cols_csv = get_quoted_csv(dest_columns | map(attribute=\"name\")) -%}\n\n {% if unique_key %}\n {% if unique_key is sequence and unique_key is not string %}\n delete from {{target }} as DBT_INCREMENTAL_TARGET\n using {{ source }}\n where (\n {% for key in unique_key %}\n {{ source }}.{{ key }} = DBT_INCREMENTAL_TARGET.{{ key }}\n {{ \"and \" if not loop.last}}\n {% endfor %}\n {% if incremental_predicates %}\n {% for predicate in incremental_predicates %}\n and {{ predicate }}\n {% endfor %}\n {% endif %}\n );\n {% else %}\n delete from {{ target }}\n where (\n {{ unique_key }}) in (\n select ({{ unique_key }})\n from {{ source }}\n )\n {%- if incremental_predicates %}\n {% for predicate in incremental_predicates %}\n and {{ predicate }}\n {% endfor %}\n {%- endif -%};\n\n {% endif %}\n {% endif %}\n\n insert into {{ target }} ({{ dest_cols_csv }})\n (\n select {{ dest_cols_csv }}\n from {{ source }}\n )\n\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__get_delete_insert_merge_sql", + "original_file_path": "macros/materializations/incremental_strategy/delete_insert.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/delete_insert.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_delete_insert_merge_sql" + }, + "macro.dbt_duckdb.duckdb__get_incremental_default_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_incremental_delete_insert_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_incremental_default_sql(arg_dict) %}\n {% do return(get_incremental_delete_insert_sql(arg_dict)) %}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__get_incremental_default_sql", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_incremental_default_sql" + }, + "macro.dbt_duckdb.duckdb__get_incremental_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.normalize_incremental_predicates", + "macro.dbt_duckdb.duckdb__get_merge_sql" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_incremental_merge_sql(args_dict) %}\n {%- set target_relation = args_dict['target_relation'] -%}\n {%- set temp_relation = args_dict['temp_relation'] -%}\n {%- set unique_key = args_dict['unique_key'] -%}\n {%- set dest_columns = args_dict['dest_columns'] -%}\n {%- set incremental_predicates = normalize_incremental_predicates(args_dict.get('incremental_predicates')) -%}\n\n {%- set build_sql = duckdb__get_merge_sql(target_relation, temp_relation, unique_key, dest_columns, incremental_predicates) -%}\n\n {{ return(build_sql) }}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__get_incremental_merge_sql", + "original_file_path": "macros/materializations/incremental_strategy/merge.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_incremental_merge_sql" + }, + "macro.dbt_duckdb.duckdb__get_incremental_microbatch_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.normalize_incremental_predicates", + "macro.dbt.get_quoted_csv" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_incremental_microbatch_sql(arg_dict) -%}\n {# Extract and validate required config #}\n {%- set event_time = config.get('event_time') -%}\n {%- if not event_time -%}\n {{ exceptions.raise_compiler_error(\"microbatch incremental strategy requires an 'event_time' model config\") }}\n {%- endif -%}\n\n {# microbatch is implemented as delete+insert on event_time; unique_key is ignored and misleading #}\n {%- set unique_key = config.get('unique_key') -%}\n {%- if unique_key -%}\n {{ exceptions.raise_compiler_error(\"microbatch incremental strategy does not support 'unique_key'. Microbatch runs delete+insert per batch based on 'event_time' and does not do key-based upserts. Remove 'unique_key' or use incremental_strategy='merge'.\") }}\n {%- endif -%}\n\n {# Extract batch context - dbt sets these per batch run based on lookback window #}\n {%- set batch_ctx = model.get('batch') -%}\n {%- set batch_start = batch_ctx.get('event_time_start') if batch_ctx else none -%}\n {%- set batch_end = batch_ctx.get('event_time_end') if batch_ctx else none -%}\n\n {%- if not (batch_start and batch_end) -%}\n {{ exceptions.raise_compiler_error(\"microbatch incremental strategy requires 'batch.event_time_start' and 'batch.event_time_end' to be set in the context\") }}\n {%- endif -%}\n\n {# Extract remaining arguments #}\n {%- set target = arg_dict[\"target_relation\"] -%}\n {%- set source = arg_dict[\"temp_relation\"] -%}\n {%- set dest_columns = arg_dict[\"dest_columns\"] -%}\n {%- set incremental_predicates = normalize_incremental_predicates(arg_dict.get(\"incremental_predicates\")) -%}\n {%- set dest_cols_csv = get_quoted_csv(dest_columns | map(attribute=\"name\")) -%}\n\n {# Build the batch time filter predicate #}\n {# batch_start and batch_end are already UTC timestamps from dbt Python code #}\n {%- set batch_predicate -%}\n {{ event_time }} >= '{{ batch_start }}'\n and {{ event_time }} < '{{ batch_end }}'\n {%- endset -%}\n\n {# Build combined WHERE clause with optional incremental predicates #}\n {%- set where_clause -%}\n {{ batch_predicate }}\n {%- for predicate in incremental_predicates %}\n and ({{ predicate }})\n {%- endfor %}\n {%- endset -%}\n\n {# Generate delete + insert SQL #}\n {%- set build_sql -%}\n delete from {{ target }}\n where {{ where_clause }};\n\n insert into {{ target }} ({{ dest_cols_csv }})\n select {{ dest_cols_csv }}\n from {{ source }}\n where {{ batch_predicate }};\n {%- endset -%}\n\n {{ return(build_sql) }}\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__get_incremental_microbatch_sql", + "original_file_path": "macros/materializations/incremental_strategy/microbatch.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/microbatch.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_incremental_microbatch_sql" + }, + "macro.dbt_duckdb.duckdb__get_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.normalize_incremental_predicates", + "macro.dbt_duckdb.validate_merge_config", + "macro.dbt_duckdb.merge_clause_defaults", + "macro.dbt_duckdb.duckdb__merge_join_clause", + "macro.dbt.get_merge_update_columns", + "macro.dbt.replace" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__get_merge_sql(target, source, unique_key, dest_columns, incremental_predicates=none) -%}\n {%- set predicates = normalize_incremental_predicates(incremental_predicates) -%}\n\n {{ validate_merge_config(config, target) }}\n\n {%- set sql_header = config.get('sql_header', none) -%}\n {%- set merge_on_using_columns = config.get('merge_on_using_columns', []) -%}\n {%- set merge_update_condition = config.get('merge_update_condition', none) -%}\n {%- set merge_insert_condition = config.get('merge_insert_condition', none) -%}\n {%- set merge_update_columns = config.get('merge_update_columns', []) -%}\n {%- set merge_exclude_columns = config.get('merge_exclude_columns', []) -%}\n {%- set merge_update_set_expressions = config.get('merge_update_set_expressions', {}) -%}\n {%- set merge_returning_columns = config.get('merge_returning_columns', []) -%}\n\n {%- set merge_clause_defaults = merge_clause_defaults(\n merge_update_condition,\n merge_insert_condition,\n merge_update_columns,\n merge_exclude_columns,\n merge_update_set_expressions\n ) -%}\n\n {%- set merge_clauses = config.get('merge_clauses', {}) -%}\n {%- set when_matched_clauses = merge_clauses.get('when_matched', [\n merge_clause_defaults.when_matched_update_explicit\n if (merge_update_columns|length != 0 or merge_exclude_columns|length != 0 or merge_update_set_expressions|length != 0)\n else merge_clause_defaults.when_matched_update_by_name\n ]) -%}\n\n {%- set when_not_matched_clauses = merge_clauses.get('when_not_matched', [\n merge_clause_defaults.when_not_matched_insert_by_name\n ]) -%}\n\n {{ sql_header if sql_header is not none }}\n\n MERGE INTO {{ target }} AS DBT_INTERNAL_DEST\n USING {{ source }} AS DBT_INTERNAL_SOURCE\n {{ duckdb__merge_join_clause(unique_key, merge_on_using_columns, incremental_predicates) }}\n\n {%- for when_matched in when_matched_clauses %}\n WHEN MATCHED\n {%- if when_matched.get('condition') -%}\n {%- if when_matched.get('condition') is string %} AND {{ when_matched.get('condition') }}\n {%- else %} AND ({{ when_matched.get('condition') | join(') AND (') }})\n {%- endif -%}\n {%- endif %}\n THEN\n {% if when_matched.get('action') == 'update' %}\n {%- if when_matched.get('mode') == 'by_name' -%}\n UPDATE BY NAME\n {%- elif when_matched.get('mode') == 'by_position' -%}\n UPDATE BY POSITION\n {%- elif when_matched.get('mode') == 'star' -%}\n UPDATE SET *\n {%- elif when_matched.get('mode') == 'explicit' -%}\n {%- set include_columns = when_matched.get('update', {}).get('include', []) -%}\n {%- set exclude_columns = when_matched.get('update', {}).get('exclude', []) -%}\n {%- set set_expressions = when_matched.get('update', {}).get('set_expressions', {}) -%}\n\n {%- set update_columns = get_merge_update_columns(include_columns, exclude_columns, dest_columns) -%}\n\n {%- set update_columns_after_overrides = [] -%}\n {%- for column in update_columns -%}\n {%- set unquoted_column = column.replace('\"', '').replace(\"'\", \"\") -%}\n {%- if unquoted_column not in set_expressions -%}\n {%- do update_columns_after_overrides.append(column) -%}\n {%- endif -%}\n {%- endfor -%}\n\n UPDATE SET\n {% for column, expression in set_expressions.items() -%}\n {{ column }} = {{ expression }}{% if not loop.last or update_columns_after_overrides|length > 0 %}, {% endif %}\n {%- endfor %}\n {%- for column in update_columns_after_overrides -%}\n {{ column }} = DBT_INTERNAL_SOURCE.{{ column }}{% if not loop.last %}, {% endif %}\n {%- endfor %}\n {%- endif -%}\n\n {%- elif when_matched.get('action') == 'delete' %}\n DELETE\n {%- elif when_matched.get('action') == 'do_nothing' %}\n DO NOTHING\n {%- elif when_matched.get('action') == 'error' %}\n {%- set error_message = (when_matched.get('error_message', '') or '') | replace(\"'\", \"''\") -%}\n ERROR{% if when_matched.get('error_message') %} '{{ error_message }}'{% endif %}\n {%- endif %}\n {%- endfor %}\n\n {%- for when_not_matched in when_not_matched_clauses %}\n WHEN NOT MATCHED\n {% if when_not_matched.get('by') %}BY {{ when_not_matched.get('by') | upper }} {% endif %}\n {%- if when_not_matched.get('condition') -%}\n {%- if when_not_matched.get('condition') is string %} AND {{ when_not_matched.get('condition') }}\n {%- else %} AND ({{ when_not_matched.get('condition') | join(') AND (') }})\n {%- endif -%}\n {%- endif %}\n THEN\n {% if when_not_matched.get('action') == 'update' %}\n {%- set set_expressions = when_not_matched.get('set_expressions', {}) -%}\n\n UPDATE SET\n {% for column, expression in set_expressions.items() -%}\n {{ column }} = {{ expression }}{% if not loop.last %}, {% endif %}\n {%- endfor %}\n\n {%- elif when_not_matched.get('action') == 'insert' %}\n {%- if when_not_matched.get('mode') == 'by_name' -%}\n INSERT BY NAME\n {%- elif when_not_matched.get('mode') == 'by_position' -%}\n INSERT BY POSITION\n {%- elif when_not_matched.get('mode') == 'star' -%}\n INSERT *\n {%- elif when_not_matched.get('mode') == 'explicit' -%}\n {%- set insert_columns = when_not_matched.get('insert', {}).get('columns', []) -%}\n {%- set insert_values = when_not_matched.get('insert', {}).get('values', []) -%}\n\n INSERT\n ({{ insert_columns | join(', ') }})\n VALUES ({{ insert_values | join(', ') }})\n {%- endif -%}\n {%- elif when_not_matched.get('action') == 'delete' %}\n DELETE\n {%- elif when_not_matched.get('action') == 'do_nothing' %}\n DO NOTHING\n {%- elif when_not_matched.get('action') == 'error' %}\n {%- set error_message = (when_not_matched.get('error_message', '') or '') | replace(\"'\", \"''\") -%}\n ERROR{% if when_not_matched.get('error_message') %} '{{ error_message }}'{% endif %}\n {%- endif %}\n {%- endfor %}\n\n {%- if merge_returning_columns %}\n RETURNING {{ merge_returning_columns if merge_returning_columns is string else merge_returning_columns | join(', ') }}\n {%- endif %}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__get_merge_sql", + "original_file_path": "macros/materializations/incremental_strategy/merge.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__get_merge_sql" + }, + "macro.dbt_duckdb.duckdb__last_day": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.dateadd", + "macro.dbt.date_trunc", + "macro.dbt.default_last_day" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__last_day(date, datepart) -%}\n\n {%- if datepart == 'quarter' -%}\n -- duckdb dateadd does not support quarter interval.\n cast(\n {{dbt.dateadd('day', '-1',\n dbt.dateadd('month', '3', dbt.date_trunc(datepart, date))\n )}}\n as date)\n {%- else -%}\n {{dbt.default_last_day(date, datepart)}}\n {%- endif -%}\n\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__last_day", + "original_file_path": "macros/utils/lastday.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/lastday.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__last_day" + }, + "macro.dbt_duckdb.duckdb__list_relations_without_caching": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__list_relations_without_caching(schema_relation) %}\n {% call statement('list_relations_without_caching', fetch_result=True) -%}\n select\n '{{ schema_relation.database }}' as database,\n table_name as name,\n table_schema as schema,\n CASE table_type\n WHEN 'BASE TABLE' THEN 'table'\n WHEN 'VIEW' THEN 'view'\n WHEN 'LOCAL TEMPORARY' THEN 'table'\n END as type\n from system.information_schema.tables\n where lower(table_schema) = '{{ schema_relation.schema | lower }}'\n and lower(table_catalog) = '{{ schema_relation.database | lower }}'\n {% endcall %}\n {{ return(load_result('list_relations_without_caching').table) }}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__list_relations_without_caching", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__list_relations_without_caching" + }, + "macro.dbt_duckdb.duckdb__list_schemas": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.run_query" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__list_schemas(database) -%}\n {% set sql %}\n select schema_name\n from system.information_schema.schemata\n {% if database is not none %}\n where lower(catalog_name) = '{{ database | lower }}'\n {% endif %}\n {% endset %}\n {{ return(run_query(sql)) }}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__list_schemas", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__list_schemas" + }, + "macro.dbt_duckdb.duckdb__listagg": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__listagg(measure, delimiter_text, order_by_clause, limit_num) -%}\n {% if limit_num -%}\n list_aggr(\n (array_agg(\n {{ measure }}\n {% if order_by_clause -%}\n {{ order_by_clause }}\n {%- endif %}\n ))[1:{{ limit_num }}],\n 'string_agg',\n {{ delimiter_text }}\n )\n {%- else %}\n string_agg(\n {{ measure }},\n {{ delimiter_text }}\n {% if order_by_clause -%}\n {{ order_by_clause }}\n {%- endif %}\n )\n {%- endif %}\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__listagg", + "original_file_path": "macros/utils/listagg.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/listagg.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__listagg" + }, + "macro.dbt_duckdb.duckdb__load_csv_rows": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_batch_size", + "macro.dbt.get_seed_column_quoted_csv", + "macro.dbt.get_binding_char" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__load_csv_rows(model, agate_table) %}\n {% if config.get('fast', true) %}\n {% set seed_file_path = adapter.get_seed_file_path(model) %}\n {% set delimiter = config.get('delimiter', ',') %}\n {% set sql %}\n COPY {{ this.render() }} FROM '{{ seed_file_path }}' (FORMAT CSV, HEADER TRUE, DELIMITER '{{ delimiter }}')\n {% endset %}\n {% do adapter.add_query(sql, abridge_sql_log=True) %}\n {{ return(sql) }}\n {% endif %}\n\n {% set batch_size = get_batch_size() %}\n {% set agate_table = adapter.convert_datetimes_to_strs(agate_table) %}\n {% set cols_sql = get_seed_column_quoted_csv(model, agate_table.column_names) %}\n {% set bindings = [] %}\n\n {% set statements = [] %}\n\n {% for chunk in agate_table.rows | batch(batch_size) %}\n {% set bindings = [] %}\n\n {% for row in chunk %}\n {% do bindings.extend(row) %}\n {% endfor %}\n\n {% set sql %}\n insert into {{ this.render() }} ({{ cols_sql }}) values\n {% for row in chunk -%}\n ({%- for column in agate_table.column_names -%}\n {{ get_binding_char() }}\n {%- if not loop.last%},{%- endif %}\n {%- endfor -%})\n {%- if not loop.last%},{%- endif %}\n {%- endfor %}\n {% endset %}\n\n {% do adapter.add_query(sql, bindings=bindings, abridge_sql_log=True) %}\n\n {% if loop.index0 == 0 %}\n {% do statements.append(sql) %}\n {% endif %}\n {% endfor %}\n\n {# Return SQL so we can render it out into the compiled files #}\n {{ return(statements[0]) }}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__load_csv_rows", + "original_file_path": "macros/seed.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/seed.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__load_csv_rows" + }, + "macro.dbt_duckdb.duckdb__make_temp_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.py_current_timestring" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__make_temp_relation(base_relation, suffix) %}\n {% set tmp_identifier = base_relation.identifier ~ suffix ~ py_current_timestring() %}\n {% do return(base_relation.incorporate(\n path={\n \"identifier\": tmp_identifier,\n \"schema\": none,\n \"database\": none\n })) -%}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__make_temp_relation", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__make_temp_relation" + }, + "macro.dbt_duckdb.duckdb__merge_join_clause": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__merge_join_clause(unique_key, merge_on_using_columns, incremental_predicates) -%}\n {%- set incremental_filters = [] if incremental_predicates is none else [] + incremental_predicates -%}\n {%- set join_predicates = [] -%}\n {%- set using_clause_requested = merge_on_using_columns and merge_on_using_columns | length > 0 -%}\n\n {%- if not using_clause_requested -%}\n {% if unique_key %}\n {% if unique_key is sequence and unique_key is not mapping and unique_key is not string %}\n {% for key in unique_key %}\n {% do join_predicates.append(\"DBT_INTERNAL_SOURCE.\" ~ key ~ \" = DBT_INTERNAL_DEST.\" ~ key) %}\n {% endfor %}\n {% else %}\n {% do join_predicates.append(\"DBT_INTERNAL_SOURCE.\" ~ unique_key ~ \" = DBT_INTERNAL_DEST.\" ~ unique_key) %}\n {% endif %}\n {% else %}\n {% do join_predicates.append('FALSE') %}\n {% endif %}\n {%- endif -%}\n\n {% if using_clause_requested %}\n USING ({{ merge_on_using_columns | join(', ') }})\n {%- if incremental_filters | length > 0 %}\n ON ({{ incremental_filters | join(\") AND (\") }})\n {%- endif %}\n {% else %}\n ON ({{ (join_predicates + incremental_filters) | join(\") AND (\") }})\n {% endif %}\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__merge_join_clause", + "original_file_path": "macros/materializations/incremental_strategy/merge.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/merge.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__merge_join_clause" + }, + "macro.dbt_duckdb.duckdb__post_snapshot": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.drop_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__post_snapshot(staging_relation) %}\n {% do return(drop_relation(staging_relation)) %}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__post_snapshot", + "original_file_path": "macros/snapshot_helper.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/snapshot_helper.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__post_snapshot" + }, + "macro.dbt_duckdb.duckdb__rename_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__rename_relation(from_relation, to_relation) -%}\n {% set target_name = adapter.quote_as_configured(to_relation.identifier, 'identifier') %}\n {% call statement('rename_relation') -%}\n alter {{ to_relation.type }} {{ from_relation }} rename to {{ target_name }}\n {%- endcall %}\n{% endmacro %}", + "meta": {}, + "name": "duckdb__rename_relation", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__rename_relation" + }, + "macro.dbt_duckdb.duckdb__snapshot_get_time": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.current_timestamp" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__snapshot_get_time() -%}\n {{ current_timestamp() }}::timestamp\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__snapshot_get_time", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__snapshot_get_time" + }, + "macro.dbt_duckdb.duckdb__snapshot_merge_sql": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.get_snapshot_table_column_names" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__snapshot_merge_sql(target, source, insert_cols) -%}\n {%- set insert_cols_csv = insert_cols | join(', ') -%}\n\n {%- set columns = config.get(\"snapshot_table_column_names\") or get_snapshot_table_column_names() -%}\n\n update {{ target }} as DBT_INTERNAL_TARGET\n set {{ columns.dbt_valid_to }} = DBT_INTERNAL_SOURCE.{{ columns.dbt_valid_to }}\n from {{ source }} as DBT_INTERNAL_SOURCE\n where DBT_INTERNAL_SOURCE.{{ columns.dbt_scd_id }}::text = DBT_INTERNAL_TARGET.{{ columns.dbt_scd_id }}::text\n and DBT_INTERNAL_SOURCE.dbt_change_type::text in ('update'::text, 'delete'::text)\n {% if config.get(\"dbt_valid_to_current\") %}\n and (DBT_INTERNAL_TARGET.{{ columns.dbt_valid_to }} = {{ config.get('dbt_valid_to_current') }} or DBT_INTERNAL_TARGET.{{ columns.dbt_valid_to }} is null);\n {% else %}\n and DBT_INTERNAL_TARGET.{{ columns.dbt_valid_to }} is null;\n {% endif %}\n\n insert into {{ target }} ({{ insert_cols_csv }})\n select {% for column in insert_cols -%}\n DBT_INTERNAL_SOURCE.{{ column }} {%- if not loop.last %}, {%- endif %}\n {%- endfor %}\n from {{ source }} as DBT_INTERNAL_SOURCE\n where DBT_INTERNAL_SOURCE.dbt_change_type::text = 'insert'::text;\n\n{% endmacro %}", + "meta": {}, + "name": "duckdb__snapshot_merge_sql", + "original_file_path": "macros/snapshot_helper.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/snapshot_helper.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__snapshot_merge_sql" + }, + "macro.dbt_duckdb.duckdb__snapshot_string_as_time": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__snapshot_string_as_time(timestamp) -%}\n {%- set result = \"'\" ~ timestamp ~ \"'::timestamp\" -%}\n {{ return(result) }}\n{%- endmacro %}", + "meta": {}, + "name": "duckdb__snapshot_string_as_time", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__snapshot_string_as_time" + }, + "macro.dbt_duckdb.duckdb__split_part": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb__split_part(string_text, delimiter_text, part_number) %}\n string_split({{ string_text }}, {{ delimiter_text }})[ {{ part_number }} ]\n{% endmacro %}", + "meta": {}, + "name": "duckdb__split_part", + "original_file_path": "macros/utils/splitpart.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/splitpart.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb__split_part" + }, + "macro.dbt_duckdb.duckdb_escape_comment": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro duckdb_escape_comment(comment) -%}\n {% if comment is not string %}\n {% do exceptions.raise_compiler_error('cannot escape a non-string: ' ~ comment) %}\n {% endif %}\n {%- set magic = '$dbt_comment_literal_block$' -%}\n {%- if magic in comment -%}\n {%- do exceptions.raise_compiler_error('The string ' ~ magic ~ ' is not allowed in comments.') -%}\n {%- endif -%}\n {{ magic }}{{ comment }}{{ magic }}\n{%- endmacro %}", + "meta": {}, + "name": "duckdb_escape_comment", + "original_file_path": "macros/persist_docs.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/persist_docs.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.duckdb_escape_comment" + }, + "macro.dbt_duckdb.external_location": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro external_location(relation, config) -%}\n {%- if config.get('options', {}).get('partition_by') is none -%}\n {%- set format = config.get('format', 'parquet') -%}\n {{- adapter.external_root() }}/{{ relation.identifier }}.{{ format }}\n {%- else -%}\n {{- adapter.external_root() }}/{{ relation.identifier }}\n {%- endif -%}\n{%- endmacro -%}", + "meta": {}, + "name": "external_location", + "original_file_path": "macros/utils/external_location.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/external_location.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.external_location" + }, + "macro.dbt_duckdb.get_column_names": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro get_column_names() %}\n {# loop through user_provided_columns to get column names #}\n {%- set user_provided_columns = model['columns'] -%}\n (\n {% for i in user_provided_columns %}\n {% set col = user_provided_columns[i] %}\n {{ col['name'] }} {{ \",\" if not loop.last }}\n {% endfor %}\n )\n{% endmacro %}", + "meta": {}, + "name": "get_column_names", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.get_column_names" + }, + "macro.dbt_duckdb.location_exists": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro location_exists(location) -%}\n {% do return(adapter.location_exists(location)) %}\n{% endmacro %}", + "meta": {}, + "name": "location_exists", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.location_exists" + }, + "macro.dbt_duckdb.materialization_external_duckdb": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.external_location", + "macro.dbt_duckdb.render_write_options", + "macro.dbt.load_cached_relation", + "macro.dbt.make_intermediate_relation", + "macro.dbt.make_backup_relation", + "macro.dbt.drop_relation_if_exists", + "macro.dbt_duckdb.run_hooks", + "macro.dbt.statement", + "macro.dbt.create_table_as", + "macro.dbt.run_query", + "macro.dbt.get_columns_in_relation", + "macro.dbt_duckdb.write_to_file", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs", + "macro.dbt_duckdb.store_relation" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization external, adapter=\"duckdb\", supported_languages=['sql', 'python'] %}\n\n {%- set location = render(config.get('location', default=external_location(this, config))) -%})\n {%- set rendered_options = render_write_options(config) -%}\n\n {%- set format = config.get('format') -%}\n {%- set allowed_formats = ['csv', 'parquet', 'json'] -%}\n {%- if format -%}\n {%- if format not in allowed_formats -%}\n {{ exceptions.raise_compiler_error(\"Invalid format: \" ~ format ~ \". Allowed formats are: \" ~ allowed_formats | join(', ')) }}\n {%- endif -%}\n {%- else -%}\n {%- set format = location.split('.')[-1].lower() if '.' in location else 'parquet' -%}\n {%- set format = format if format in allowed_formats else 'parquet' -%}\n {%- endif -%}\n\n {%- set write_options = adapter.external_write_options(location, rendered_options) -%}\n {%- set read_location = adapter.external_read_location(location, rendered_options) -%}\n {%- set parquet_read_options = config.get('parquet_read_options', {'union_by_name': False}) -%}\n {%- set json_read_options = config.get('json_read_options', {'auto_detect': True}) -%}\n {%- set csv_read_options = config.get('csv_read_options', {'auto_detect': True}) -%}\n\n -- set language - python or sql\n {%- set language = model['language'] -%}\n\n {%- set target_relation = this.incorporate(type='view') %}\n\n -- Continue as normal materialization\n {%- set existing_relation = load_cached_relation(this) -%}\n {%- set temp_relation = make_intermediate_relation(this.incorporate(type='table'), suffix='__dbt_tmp') -%}\n {%- set intermediate_relation = make_intermediate_relation(target_relation, suffix='__dbt_int') -%}\n -- the intermediate_relation should not already exist in the database; get_relation\n -- will return None in that case. Otherwise, we get a relation that we can drop\n -- later, before we try to use this name for the current operation\n {%- set preexisting_temp_relation = load_cached_relation(temp_relation) -%}\n {%- set preexisting_intermediate_relation = load_cached_relation(intermediate_relation) -%}\n /*\n See ../view/view.sql for more information about this relation.\n */\n {%- set backup_relation_type = 'table' if existing_relation is none else existing_relation.type -%}\n {%- set backup_relation = make_backup_relation(target_relation, backup_relation_type) -%}\n -- as above, the backup_relation should not already exist\n {%- set preexisting_backup_relation = load_cached_relation(backup_relation) -%}\n -- grab current tables grants config for comparision later on\n {% set grant_config = config.get('grants') %}\n\n -- drop the temp relations if they exist already in the database\n {{ drop_relation_if_exists(preexisting_intermediate_relation) }}\n {{ drop_relation_if_exists(preexisting_temp_relation) }}\n {{ drop_relation_if_exists(preexisting_backup_relation) }}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n -- build model\n {% call statement('create_table', language=language) -%}\n {{- create_table_as(False, temp_relation, compiled_code, language) }}\n {%- endcall %}\n\n -- check if relation is empty\n {%- set count_query -%}\n select count(*) as row_count from {{ temp_relation }}\n {%- endset -%}\n {%- set row_count = run_query(count_query) -%}\n\n -- if relation is empty, write a non-empty table with column names and null values\n {% call statement('main', language='sql') -%}\n {% if row_count[0][0] == 0 %}\n insert into {{ temp_relation }} values (\n {%- for col in get_columns_in_relation(temp_relation) -%}\n NULL,\n {%- endfor -%}\n )\n {% endif %}\n {%- endcall %}\n\n -- write a temp relation into file\n {{ write_to_file(temp_relation, location, write_options) }}\n\n-- create a view on top of the location\n {% call statement('main', language='sql') -%}\n {% if format == 'json' %}\n create or replace view {{ intermediate_relation }} as (\n select * from read_json('{{ read_location }}'\n {%- for key, value in json_read_options.items() -%}\n , {{ key }}=\n {%- if value is string -%}\n '{{ value }}'\n {%- else -%}\n {{ value }}\n {%- endif -%}\n {%- endfor -%}\n )\n -- if relation is empty, filter by all columns having null values\n {% if row_count[0][0] == 0 %}\n where 1\n {%- for col in get_columns_in_relation(temp_relation) -%}\n {{ '' }} AND \"{{ col.column }}\" is not NULL\n {%- endfor -%}\n {% endif %}\n );\n {% elif format == 'parquet' %}\n create or replace view {{ intermediate_relation }} as (\n select * from read_parquet('{{ read_location }}'\n {%- for key, value in parquet_read_options.items() -%}\n , {{ key }}=\n {%- if value is string -%}\n '{{ value }}'\n {%- else -%}\n {{ value }}\n {%- endif -%}\n {%- endfor -%}\n )\n -- if relation is empty, filter by all columns having null values\n {% if row_count[0][0] == 0 %}\n where 1\n {%- for col in get_columns_in_relation(temp_relation) -%}\n {{ '' }} AND \"{{ col.column }}\" is not NULL\n {%- endfor -%}\n {% endif %}\n );\n {% elif format == 'csv' %}\n create or replace view {{ intermediate_relation }} as (\n select * from read_csv('{{ read_location }}'\n {%- for key, value in csv_read_options.items() -%}\n , {{ key }}=\n {%- if value is string -%}\n '{{ value }}'\n {%- else -%}\n {{ value }}\n {%- endif -%}\n {%- endfor -%}\n )\n -- if relation is empty, filter by all columns having null values\n {% if row_count[0][0] == 0 %}\n where 1\n {%- for col in get_columns_in_relation(temp_relation) -%}\n {{ '' }} AND \"{{ col.column }}\" is not NULL\n {%- endfor -%}\n {% endif %}\n );\n {% endif %}\n {%- endcall %}\n\n -- cleanup\n {% if existing_relation is not none %}\n {{ adapter.rename_relation(existing_relation, backup_relation) }}\n {% endif %}\n\n {{ adapter.rename_relation(intermediate_relation, target_relation) }}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n -- `COMMIT` happens here\n {{ adapter.commit() }}\n\n -- finally, drop the existing/backup relation after the commit\n {{ drop_relation_if_exists(backup_relation) }}\n {{ drop_relation_if_exists(temp_relation) }}\n\n -- register table into glue\n {%- set plugin_name = config.get('plugin') -%}\n {%- set glue_register = config.get('glue_register', default=false) -%}\n {%- set partition_columns = config.get('partition_columns', []) -%}\n {% if plugin_name is not none or glue_register is true %}\n {% if glue_register %}\n {# legacy hack to set the glue database name, deprecate this #}\n {%- set plugin_name = 'glue|' ~ config.get('glue_database', 'default') -%}\n {% endif %}\n {% do store_relation(plugin_name, target_relation, location, format, config) %}\n {% endif %}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{% endmaterialization %}", + "meta": {}, + "name": "materialization_external_duckdb", + "original_file_path": "macros/materializations/external.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/external.sql", + "resource_type": "macro", + "supported_languages": [ + "sql", + "python" + ], + "unique_id": "macro.dbt_duckdb.materialization_external_duckdb" + }, + "macro.dbt_duckdb.materialization_incremental_duckdb": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.make_temp_relation", + "macro.dbt.make_intermediate_relation", + "macro.dbt.make_backup_relation", + "macro.dbt.should_full_refresh", + "macro.dbt.incremental_validate_on_schema_change", + "macro.dbt.drop_relation_if_exists", + "macro.dbt.run_query", + "macro.dbt.create_schema", + "macro.dbt_duckdb.run_hooks", + "macro.dbt.create_table_as", + "macro.dbt.statement", + "macro.dbt.process_schema_changes", + "macro.dbt_duckdb.drop_indexes_on_relation", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.create_indexes", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization incremental, adapter=\"duckdb\", supported_languages=['sql', 'python'] -%}\n\n {%- set language = model['language'] -%}\n -- only create temp tables if using local duckdb, as it is not currently supported for remote databases\n {%- set temporary = not adapter.is_motherduck() -%}\n\n -- relations\n {%- set existing_relation = load_cached_relation(this) -%}\n {%- set target_relation = this.incorporate(type='table') -%}\n {%- set temp_relation = make_temp_relation(target_relation)-%}\n {%- set intermediate_relation = make_intermediate_relation(target_relation)-%}\n {%- set backup_relation_type = 'table' if existing_relation is none else existing_relation.type -%}\n {%- set backup_relation = make_backup_relation(target_relation, backup_relation_type) -%}\n\n -- configs\n {%- set unique_key = config.get('unique_key') -%}\n {%- set full_refresh_mode = (should_full_refresh() or existing_relation.is_view) -%}\n {%- set on_schema_change = incremental_validate_on_schema_change(config.get('on_schema_change'), default='ignore') -%}\n\n -- the temp_ and backup_ relations should not already exist in the database; get_relation\n -- will return None in that case. Otherwise, we get a relation that we can drop\n -- later, before we try to use this name for the current operation. This has to happen before\n -- BEGIN, in a separate transaction\n {%- set preexisting_intermediate_relation = load_cached_relation(intermediate_relation)-%}\n {%- set preexisting_backup_relation = load_cached_relation(backup_relation) -%}\n -- grab current tables grants config for comparision later on\n {% set grant_config = config.get('grants') %}\n {{ drop_relation_if_exists(preexisting_intermediate_relation) }}\n {{ drop_relation_if_exists(preexisting_backup_relation) }}\n\n {% set to_drop = [] %}\n {% if not temporary %}\n -- if not using a temporary table we will update the temp relation to use a different temp schema (\"dbt_temp\" by default)\n -- for microbatch with concurrent batches, include batch timestamps in the identifier to avoid collisions\n {%- set batch_id = '' -%}\n {%- set batch_ctx = model.get('batch') -%}\n {%- if batch_ctx and batch_ctx.get('event_time_start') -%}\n {%- set batch_id = batch_ctx.get('event_time_start') | string | replace('-', '') | replace(':', '') | replace(' ', '_') | replace('+', '') -%}\n {%- endif -%}\n {% set temp_relation = temp_relation.incorporate(path=adapter.get_temp_relation_path(this, batch_id)) %}\n {% do run_query(create_schema(temp_relation)) %}\n {% if not adapter.disable_transactions() %}\n {% do adapter.commit() %}\n {% endif %}\n -- then drop the temp relation after we insert the incremental data into the target relation\n {% do to_drop.append(temp_relation) %}\n {% endif %}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n {% if existing_relation is none %}\n {% set build_sql = create_table_as(False, target_relation, compiled_code, language) %}\n {% elif full_refresh_mode %}\n {% set build_sql = create_table_as(False, intermediate_relation, compiled_code, language) %}\n {% set need_swap = true %}\n {% else %}\n {% if language == 'python' %}\n {% set build_python = create_table_as(temporary, temp_relation, compiled_code, language) %}\n {% call statement(\"pre\", language=language) %}\n {{- build_python }}\n {% endcall %}\n {% else %} {# SQL #}\n {% do run_query(create_table_as(temporary, temp_relation, compiled_code, language)) %}\n {% endif %}\n {% do adapter.expand_target_column_types(\n from_relation=temp_relation,\n to_relation=target_relation) %}\n {#-- Process schema changes. Returns dict of changes if successful. Use source columns for upserting/merging --#}\n {% set dest_columns = process_schema_changes(on_schema_change, temp_relation, existing_relation) %}\n {% if not dest_columns %}\n {% set dest_columns = adapter.get_columns_in_relation(existing_relation) %}\n {% endif %}\n\n {#-- Get the incremental_strategy, the macro to use for the strategy, and build the sql --#}\n {% set incremental_strategy = config.get('incremental_strategy') or 'default' %}\n {% set incremental_predicates = config.get('predicates', none) or config.get('incremental_predicates', none) %}\n {% set strategy_sql_macro_func = adapter.get_incremental_strategy_macro(context, incremental_strategy) %}\n {% set strategy_arg_dict = ({'target_relation': target_relation, 'temp_relation': temp_relation, 'unique_key': unique_key, 'dest_columns': dest_columns, 'incremental_predicates': incremental_predicates }) %}\n {% set build_sql = strategy_sql_macro_func(strategy_arg_dict) %}\n {% set language = \"sql\" %}\n\n {% endif %}\n\n {% call statement(\"main\", language=language) %}\n {{- build_sql }}\n {% endcall %}\n\n {% if need_swap %}\n {#-- Drop indexes on target relation before renaming to backup to avoid dependency errors --#}\n {% do drop_indexes_on_relation(target_relation) %}\n {% do adapter.rename_relation(target_relation, backup_relation) %}\n {% do adapter.rename_relation(intermediate_relation, target_relation) %}\n {% do to_drop.append(backup_relation) %}\n {% endif %}\n\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {# Align order with table materialization to avoid MotherDuck alter conflicts #}\n {% if existing_relation is none or existing_relation.is_view or should_full_refresh() %}\n {% do create_indexes(target_relation) %}\n {% endif %}\n\n {% do persist_docs(target_relation, model) %}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n -- `COMMIT` happens here\n {% do adapter.commit() %}\n\n {% for rel in to_drop %}\n {# On MotherDuck the temp relation is a real table; dropping it cascades indexes. Avoid extra ALTERs. #}\n {% if not adapter.is_motherduck() %}\n {% do drop_indexes_on_relation(rel) %}\n {% endif %}\n {% do adapter.drop_relation(rel) %}\n {% endfor %}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{%- endmaterialization %}", + "meta": {}, + "name": "materialization_incremental_duckdb", + "original_file_path": "macros/materializations/incremental.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental.sql", + "resource_type": "macro", + "supported_languages": [ + "sql", + "python" + ], + "unique_id": "macro.dbt_duckdb.materialization_incremental_duckdb" + }, + "macro.dbt_duckdb.materialization_table_duckdb": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.load_cached_relation", + "macro.dbt.make_intermediate_relation", + "macro.dbt.make_backup_relation", + "macro.dbt.drop_relation_if_exists", + "macro.dbt_duckdb.run_hooks", + "macro.dbt.statement", + "macro.dbt.create_table_as", + "macro.dbt_duckdb.drop_indexes_on_relation", + "macro.dbt.create_indexes", + "macro.dbt.should_revoke", + "macro.dbt.apply_grants", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization table, adapter=\"duckdb\", supported_languages=['sql', 'python'] %}\n\n {%- set language = model['language'] -%}\n\n {%- set existing_relation = load_cached_relation(this) -%}\n {%- set target_relation = this.incorporate(type='table') %}\n {%- set intermediate_relation = make_intermediate_relation(target_relation) -%}\n -- the intermediate_relation should not already exist in the database; get_relation\n -- will return None in that case. Otherwise, we get a relation that we can drop\n -- later, before we try to use this name for the current operation\n {%- set preexisting_intermediate_relation = load_cached_relation(intermediate_relation) -%}\n /*\n See ../view/view.sql for more information about this relation.\n */\n {%- set backup_relation_type = 'table' if existing_relation is none else existing_relation.type -%}\n {%- set backup_relation = make_backup_relation(target_relation, backup_relation_type) -%}\n -- as above, the backup_relation should not already exist\n {%- set preexisting_backup_relation = load_cached_relation(backup_relation) -%}\n -- grab current tables grants config for comparision later on\n {% set grant_config = config.get('grants') %}\n\n -- drop the temp relations if they exist already in the database\n {{ drop_relation_if_exists(preexisting_intermediate_relation) }}\n {{ drop_relation_if_exists(preexisting_backup_relation) }}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n -- build model\n {% call statement('main', language=language) -%}\n {{- create_table_as(False, intermediate_relation, compiled_code, language) }}\n {%- endcall %}\n\n -- cleanup\n {% if existing_relation is not none %}\n {#-- Drop indexes before renaming to avoid dependency errors --#}\n {% do drop_indexes_on_relation(existing_relation) %}\n {{ adapter.rename_relation(existing_relation, backup_relation) }}\n {% endif %}\n\n {{ adapter.rename_relation(intermediate_relation, target_relation) }}\n\n {% do create_indexes(target_relation) %}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n {% set should_revoke = should_revoke(existing_relation, full_refresh_mode=True) %}\n {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %}\n\n {% do persist_docs(target_relation, model) %}\n\n -- `COMMIT` happens here\n {{ adapter.commit() }}\n\n -- finally, drop the existing/backup relation after the commit\n {{ drop_relation_if_exists(backup_relation) }}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n{% endmaterialization %}", + "meta": {}, + "name": "materialization_table_duckdb", + "original_file_path": "macros/materializations/table.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/table.sql", + "resource_type": "macro", + "supported_languages": [ + "sql", + "python" + ], + "unique_id": "macro.dbt_duckdb.materialization_table_duckdb" + }, + "macro.dbt_duckdb.materialization_table_function_duckdb": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.run_hooks", + "macro.dbt.statement", + "macro.dbt.persist_docs" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% materialization table_function, adapter='duckdb' %}\n -- This materialization uses DuckDB's Table Function / Table Macro feature to provide parameterized views.\n -- Why use this?\n -- Late binding of functions means that the underlying table can change (have new columns added), and\n -- the function does not need to be recreated. (With a view, the create view statement would need to be re-run).\n -- This allows for skipping parts of the dbt DAG, even if the underlying table changed.\n -- Parameters can force filter pushdown\n -- Functions can provide advanced features like dynamic SQL (the query and query_table functions)\n\n -- For usage examples, see the tests at /dbt-duckdb/tests/functional/adapter/test_table_function.py\n -- (Don't forget parentheses when you pull from a table_function!)\n\n -- Using Redshift as an example:\n -- https://github.com/dbt-labs/dbt-adapters/blob/main/dbt-redshift/src/dbt/include/redshift/macros/materializations/table.sql\n {%- set identifier = model['alias'] -%}\n {%- set target_relation = api.Relation.create(\n identifier=identifier,\n schema=schema,\n database=database,\n type='view') -%}\n {%- set backup_relation = none -%}\n\n -- The parameters config is used to pass in the names of the parameters that will be used within the table function.\n -- parameters can be a single string value (with or without commas), or a list of strings.\n {%- set parameters=config.get('parameters') -%}\n\n {{ run_hooks(pre_hooks, inside_transaction=False) }}\n\n -- `BEGIN` happens here:\n {{ run_hooks(pre_hooks, inside_transaction=True) }}\n\n -- Create or replace the function (macro)\n -- By using create or replace (and a transaction), we do not need an old version and new version.\n {% call statement('main') -%}\n create or replace function {{ target_relation.render() }}(\n {% if not parameters %}\n {% elif parameters is string or parameters is number %}\n {{ parameters if parameters }}\n {% else %}\n {{ parameters|join(', ') }}\n {% endif %}\n ) as table (\n {{ sql }});\n {%- endcall %}\n\n {{ run_hooks(post_hooks, inside_transaction=True) }}\n\n {% do persist_docs(target_relation, model) %}\n\n -- `COMMIT` happens here:\n {{ adapter.commit() }}\n\n {{ run_hooks(post_hooks, inside_transaction=False) }}\n\n {{ return({'relations': [target_relation]}) }}\n\n{% endmaterialization %}", + "meta": {}, + "name": "materialization_table_function_duckdb", + "original_file_path": "macros/materializations/table_function.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/table_function.sql", + "resource_type": "macro", + "supported_languages": [ + "sql" + ], + "unique_id": "macro.dbt_duckdb.materialization_table_function_duckdb" + }, + "macro.dbt_duckdb.merge_clause_defaults": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro merge_clause_defaults(\n merge_update_condition,\n merge_insert_condition,\n merge_update_columns=[],\n merge_exclude_columns=[],\n merge_update_set_expressions={}\n) -%}\n\n {{ return({\n 'when_matched_update_by_name': {\n 'action': 'update',\n 'condition': merge_update_condition,\n 'mode': 'by_name'\n },\n 'when_not_matched_insert_by_name': {\n 'action': 'insert',\n 'condition': merge_insert_condition,\n 'mode': 'by_name'\n },\n 'when_matched_update_explicit': {\n 'action': 'update',\n 'condition': merge_update_condition,\n 'mode': 'explicit',\n 'update': {\n 'include': merge_update_columns,\n 'exclude': merge_exclude_columns,\n 'set_expressions': merge_update_set_expressions\n }\n }\n }) }}\n{%- endmacro %}", + "meta": {}, + "name": "merge_clause_defaults", + "original_file_path": "macros/materializations/incremental_strategy/merge_defaults.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/merge_defaults.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.merge_clause_defaults" + }, + "macro.dbt_duckdb.normalize_incremental_predicates": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro normalize_incremental_predicates(incremental_predicates) -%}\n {%- if incremental_predicates is none -%}\n {%- set incremental_predicates = [] -%}\n {%- elif incremental_predicates is mapping -%}\n {{ exceptions.raise_compiler_error(\"incremental_predicates must be a list of strings or a string\") }}\n {%- elif incremental_predicates is string -%}\n {%- set incremental_predicates = [incremental_predicates] -%}\n {%- elif incremental_predicates is sequence -%}\n {%- set incremental_predicates = incremental_predicates | list -%}\n {%- else -%}\n {{ exceptions.raise_compiler_error(\"incremental_predicates must be a list of strings or a string\") }}\n {%- endif -%}\n {{ return(incremental_predicates) }}\n{%- endmacro -%}", + "meta": {}, + "name": "normalize_incremental_predicates", + "original_file_path": "macros/materializations/incremental_strategy/validation_helper.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/validation_helper.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.normalize_incremental_predicates" + }, + "macro.dbt_duckdb.py_write_table": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro py_write_table(temporary, relation, compiled_code) -%}\n{{ compiled_code }}\n\ndef materialize(df, con):\n try:\n import pyarrow\n pyarrow_available = True\n except ImportError:\n pyarrow_available = False\n finally:\n if pyarrow_available and isinstance(df, pyarrow.Table):\n # https://github.com/duckdb/duckdb/issues/6584\n import pyarrow.dataset\n tmp_name = '__dbt_python_model_df_' + '{{ relation.identifier }}'\n con.register(tmp_name, df)\n con.execute('create table {{ relation }} as select * from ' + tmp_name)\n con.unregister(tmp_name)\n{% endmacro %}", + "meta": {}, + "name": "py_write_table", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.py_write_table" + }, + "macro.dbt_duckdb.register_upstream_external_models": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.external_location", + "macro.dbt_duckdb.render_write_options", + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro register_upstream_external_models() -%}\n{% if execute %}\n{% set upstream_nodes = {} %}\n{% set upstream_schemas = {} %}\n{% for node in selected_resources %}\n {% if node not in graph['nodes'] %}{% continue %}{% endif %}\n {% for upstream_node in graph['nodes'][node]['depends_on']['nodes'] %}\n {% if upstream_node not in upstream_nodes and upstream_node not in selected_resources %}\n {% do upstream_nodes.update({upstream_node: None}) %}\n {% set upstream = graph['nodes'].get(upstream_node) %}\n {% if upstream\n and upstream.resource_type in ('model', 'seed')\n and upstream.config.materialized=='external'\n %}\n {%- set upstream_rel = api.Relation.create(\n database=upstream['database'],\n schema=upstream['schema'],\n identifier=upstream['alias']\n ) -%}\n {%- set location = upstream.config.get('location', external_location(upstream_rel, upstream.config)) -%}\n {%- set rendered_options = render_write_options(upstream.config) -%}\n {%- set upstream_location = adapter.external_read_location(location, rendered_options) -%}\n {% if upstream_rel.schema not in upstream_schemas %}\n {% call statement('main', language='sql') -%}\n create schema if not exists {{ upstream_rel.without_identifier() }}\n {%- endcall %}\n {% do upstream_schemas.update({upstream_rel.schema: None}) %}\n {% endif %}\n {% call statement('main', language='sql') -%}\n create or replace view {{ upstream_rel }} as (\n select * from '{{ upstream_location }}'\n );\n {%- endcall %}\n {%- endif %}\n {% endif %}\n {% endfor %}\n{% endfor %}\n{% if upstream_schemas %}\n {% do adapter.commit() %}\n{% endif %}\n{% endif %}\n{%- endmacro -%}", + "meta": {}, + "name": "register_upstream_external_models", + "original_file_path": "macros/utils/upstream.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/utils/upstream.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.register_upstream_external_models" + }, + "macro.dbt_duckdb.render_write_options": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro render_write_options(config) -%}\n {% set options = config.get('options', {}) %}\n {% if options is not mapping %}\n {% do exceptions.raise_compiler_error(\"The options argument must be a dictionary\") %}\n {% endif %}\n\n {% for k in options %}\n {% set _ = options.update({k: render(options[k])}) %}\n {% endfor %}\n\n {# legacy top-level write options #}\n {% if config.get('format') %}\n {% set _ = options.update({'format': render(config.get('format'))}) %}\n {% endif %}\n {% if config.get('delimiter') %}\n {% set _ = options.update({'delimiter': render(config.get('delimiter'))}) %}\n {% endif %}\n\n {% do return(options) %}\n{%- endmacro %}", + "meta": {}, + "name": "render_write_options", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.render_write_options" + }, + "macro.dbt_duckdb.run_hooks": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro run_hooks(hooks, inside_transaction=True) %}\n {% for hook in hooks | selectattr('transaction', 'equalto', inside_transaction) %}\n {% set rendered = render(hook.get('sql')) | trim %}\n {% if (rendered | length) > 0 %}\n {% call statement(auto_begin=inside_transaction) %}\n {{ rendered }}\n {% endcall %}\n {% endif %}\n {% endfor %}\n{% endmacro %}", + "meta": {}, + "name": "run_hooks", + "original_file_path": "macros/materializations/hooks.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/hooks.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.run_hooks" + }, + "macro.dbt_duckdb.store_relation": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro store_relation(plugin, relation, location, format, config) -%}\n {%- set column_list = adapter.get_columns_in_relation(relation) -%}\n {% do adapter.store_relation(plugin, relation, column_list, location, format, config) %}\n{% endmacro %}", + "meta": {}, + "name": "store_relation", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.store_relation" + }, + "macro.dbt_duckdb.validate_dict_field": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro validate_dict_field(field_value, field_name, errors) -%}\n {%- if field_value is not none and field_value is not mapping -%}\n {%- do errors.append(field_name ~ \" must be a dictionary, found: \" ~ field_value) -%}\n {%- endif -%}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "validate_dict_field", + "original_file_path": "macros/materializations/incremental_strategy/validation_helper.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/validation_helper.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.validate_dict_field" + }, + "macro.dbt_duckdb.validate_ducklake_restrictions": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro validate_ducklake_restrictions(config, target_relation, errors) -%}\n {%- if target_relation and adapter.is_ducklake(target_relation) -%}\n {%- set merge_clauses = config.get('merge_clauses', {}) -%}\n {%- if merge_clauses and 'when_matched' in merge_clauses -%}\n {%- set when_matched_clauses = merge_clauses.get('when_matched', []) -%}\n {%- set update_delete_count = 0 -%}\n\n {%- for clause in when_matched_clauses -%}\n {%- if clause is mapping and clause.get('action') in ['update', 'delete'] -%}\n {%- set update_delete_count = update_delete_count + 1 -%}\n {%- endif -%}\n {%- endfor -%}\n\n {%- if update_delete_count > 1 -%}\n {%- do errors.append(\"DuckLake MERGE restrictions: when_matched clauses can contain only a single UPDATE or DELETE action. Found \" ~ update_delete_count ~ \" UPDATE/DELETE actions. DuckLake currently supports only one UPDATE or DELETE operation per MERGE statement.\") -%}\n {%- endif -%}\n {%- endif -%}\n {%- endif -%}\n{%- endmacro -%}", + "meta": {}, + "name": "validate_ducklake_restrictions", + "original_file_path": "macros/materializations/incremental_strategy/merge_config_validation.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/merge_config_validation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.validate_ducklake_restrictions" + }, + "macro.dbt_duckdb.validate_merge_clause_list": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro validate_merge_clause_list(merge_clauses, clause_type, errors) -%}\n {%- if merge_clauses.get(clause_type) is not sequence or merge_clauses.get(clause_type) is mapping or merge_clauses.get(clause_type) is string -%}\n {%- do errors.append(\"merge_clauses.\" ~ clause_type ~ \" must be a list\") -%}\n {%- elif merge_clauses.get(clause_type)|length == 0 -%}\n {%- do errors.append(\"merge_clauses.\" ~ clause_type ~ \" must contain at least one element\") -%}\n {%- else -%}\n {%- for clause in merge_clauses.get(clause_type) -%}\n {%- if clause is not mapping -%}\n {%- do errors.append(\"merge_clauses.\" ~ clause_type ~ \" elements must be dictionaries, found: \" ~ clause) -%}\n {%- endif -%}\n {%- endfor -%}\n {%- endif -%}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "validate_merge_clause_list", + "original_file_path": "macros/materializations/incremental_strategy/merge_config_validation.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/merge_config_validation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.validate_merge_clause_list" + }, + "macro.dbt_duckdb.validate_merge_clauses": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.validate_merge_clause_list" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "\n\n\n{%- macro validate_merge_clauses(config, base_configuration_fields, errors) -%}\n {%- if config.get('merge_clauses') is not none -%}\n {%- if config.get('merge_clauses') is not mapping -%}\n {%- do errors.append(\"merge_clauses must be a dictionary, found: \" ~ config.get('merge_clauses')) -%}\n {%- else -%}\n {%- set merge_clauses = config.get('merge_clauses') -%}\n {%- set clause_types = ['when_matched', 'when_not_matched'] -%}\n\n {%- set has_when_matched = 'when_matched' in merge_clauses -%}\n {%- set has_when_not_matched = 'when_not_matched' in merge_clauses -%}\n\n {%- if not has_when_matched and not has_when_not_matched -%}\n {%- do errors.append(\"merge_clauses must contain at least one of 'when_matched' or 'when_not_matched' keys\") -%}\n {%- endif -%}\n\n {%- for clause_type in clause_types -%}\n {%- if clause_type in merge_clauses -%}\n {%- do validate_merge_clause_list(merge_clauses, clause_type, errors) -%}\n {%- endif -%}\n {%- endfor -%}\n\n {%- set conflicting_configs = [] -%}\n {%- for config_name, config_type in base_configuration_fields.items() -%}\n {%- if config_name not in ['merge_on_using_columns', 'merge_returning_columns'] -%}\n {%- set config_value = config.get(config_name) -%}\n {%- if config_value is not none -%}\n {%- if config_type == 'sequence' -%}\n {%- if config_value|length > 0 -%}\n {%- do conflicting_configs.append(config_name) -%}\n {%- endif -%}\n {%- elif config_type == 'mapping' -%}\n {%- if config_value.keys()|length > 0 -%}\n {%- do conflicting_configs.append(config_name) -%}\n {%- endif -%}\n {%- else -%}\n {%- do conflicting_configs.append(config_name) -%}\n {%- endif -%}\n {%- endif -%}\n {%- endif -%}\n {%- endfor -%}\n\n {%- if conflicting_configs|length > 0 -%}\n {%- do errors.append(\"When merge_clauses is specified, the following basic merge configurations will be ignored and should be removed: \" ~ conflicting_configs|join(', ') ~ \". Define your merge behavior within merge_clauses instead.\") -%}\n {%- endif -%}\n {%- endif -%}\n {%- endif -%}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "validate_merge_clauses", + "original_file_path": "macros/materializations/incremental_strategy/merge_config_validation.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/merge_config_validation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.validate_merge_clauses" + }, + "macro.dbt_duckdb.validate_merge_config": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt_duckdb.validate_string_field", + "macro.dbt_duckdb.validate_string_list_field", + "macro.dbt_duckdb.validate_dict_field", + "macro.dbt_duckdb.validate_ducklake_restrictions", + "macro.dbt_duckdb.validate_merge_clauses" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro validate_merge_config(config, target_relation=none) %}\n {%- set errors = [] -%}\n\n {%- set base_configuration_fields = {\n 'merge_update_condition': 'string',\n 'merge_insert_condition': 'string',\n 'merge_on_using_columns': 'sequence',\n 'merge_update_columns': 'sequence',\n 'merge_update_set_expressions': 'mapping',\n 'merge_exclude_columns': 'sequence',\n 'merge_returning_columns': 'sequence'\n } -%}\n\n {%- for field_name, field_type in base_configuration_fields.items() -%}\n {%- set field_value = config.get(field_name) -%}\n {%- if field_type == 'string' -%}\n {%- do validate_string_field(field_value, field_name, errors) -%}\n {%- elif field_type == 'sequence' -%}\n {%- do validate_string_list_field(field_value, field_name, errors) -%}\n {%- elif field_type == 'mapping' -%}\n {%- do validate_dict_field(field_value, field_name, errors) -%}\n {%- endif -%}\n {%- endfor -%}\n\n {%- do validate_ducklake_restrictions(config, target_relation, errors) -%}\n\n {%- do validate_merge_clauses(config, base_configuration_fields, errors) -%}\n\n {%- if errors -%}\n {{ exceptions.raise_compiler_error(\"MERGE configuration errors:\\n\" ~ errors|join('\\n')) }}\n {%- endif -%}\n{% endmacro %}", + "meta": {}, + "name": "validate_merge_config", + "original_file_path": "macros/materializations/incremental_strategy/merge_config_validation.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/merge_config_validation.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.validate_merge_config" + }, + "macro.dbt_duckdb.validate_string_field": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro validate_string_field(field_value, field_name, errors) -%}\n {%- if field_value is not none and field_value is not string -%}\n {%- do errors.append(field_name ~ \" must be a string, found: \" ~ field_value) -%}\n {%- endif -%}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "validate_string_field", + "original_file_path": "macros/materializations/incremental_strategy/validation_helper.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/validation_helper.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.validate_string_field" + }, + "macro.dbt_duckdb.validate_string_list_field": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{%- macro validate_string_list_field(field_value, field_name, errors) -%}\n {%- if field_value is not none -%}\n {%- if field_value is not sequence or field_value is mapping or field_value is string -%}\n {%- do errors.append(field_name ~ \" must be a list\") -%}\n {%- else -%}\n {%- for item in field_value -%}\n {%- if item is not string -%}\n {%- do errors.append(field_name ~ \" must contain only string values, found: \" ~ item) -%}\n {%- endif -%}\n {%- endfor -%}\n {%- endif -%}\n {%- endif -%}\n{%- endmacro -%}\n\n", + "meta": {}, + "name": "validate_string_list_field", + "original_file_path": "macros/materializations/incremental_strategy/validation_helper.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/materializations/incremental_strategy/validation_helper.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.validate_string_list_field" + }, + "macro.dbt_duckdb.write_to_file": { + "arguments": [], + "config": { + "docs": { + "node_color": null, + "show": true + }, + "meta": {} + }, + "created_at": 1785000000.0, + "depends_on": { + "macros": [ + "macro.dbt.statement" + ] + }, + "description": "", + "docs": { + "node_color": null, + "show": true + }, + "macro_sql": "{% macro write_to_file(relation, location, options) -%}\n {% call statement('write_to_file') -%}\n copy {{ relation }} to '{{ location }}' ({{ options }})\n {%- endcall %}\n{% endmacro %}", + "meta": {}, + "name": "write_to_file", + "original_file_path": "macros/adapters.sql", + "package_name": "dbt_duckdb", + "patch_path": null, + "path": "macros/adapters.sql", + "resource_type": "macro", + "supported_languages": null, + "unique_id": "macro.dbt_duckdb.write_to_file" + } + }, + "metadata": { + "adapter_type": "duckdb", + "dbt_schema_version": "https://schemas.getdbt.com/dbt/manifest/v12.json", + "dbt_version": "1.11.8", + "generated_at": "2026-07-24T00:00:00Z", + "invocation_id": "00000000-0000-0000-0000-000000000000", + "invocation_started_at": "2026-07-24T00:00:00Z", + "project_id": "00000000-0000-0000-0000-000000000000", + "project_name": "jaffle_shop", + "quoting": { + "column": null, + "database": true, + "identifier": true, + "schema": true + }, + "run_started_at": "2026-07-24T00:00:00Z", + "send_anonymous_usage_stats": false, + "user_id": "00000000-0000-0000-0000-000000000000" + }, + "metrics": {}, + "nodes": { + "model.jaffle_shop.customers": { + "access": "protected", + "alias": "customers", + "build_path": null, + "checksum": { + "checksum": "bea22f794f0f242d91e49e605c9c2eeab03bbce750775a4e907dddcaa419a2c6", + "name": "sha256" + }, + "columns": { + "customer_id": { + "config": { + "meta": {}, + "tags": [] + }, + "constraints": [], + "data_type": null, + "description": "Primary key.", + "doc_blocks": [], + "granularity": null, + "meta": {}, + "name": "customer_id", + "quote": null, + "tags": [] + }, + "order_count": { + "config": { + "meta": {}, + "tags": [] + }, + "constraints": [], + "data_type": null, + "description": "Number of orders placed by the customer (0 when none).", + "doc_blocks": [], + "granularity": null, + "meta": {}, + "name": "order_count", + "quote": null, + "tags": [] + }, + "total_amount": { + "config": { + "meta": {}, + "tags": [] + }, + "constraints": [], + "data_type": null, + "description": "Sum of all order amounts (0 when none).", + "doc_blocks": [], + "granularity": null, + "meta": {}, + "name": "total_amount", + "quote": null, + "tags": [] + } + }, + "compiled": true, + "compiled_code": "select\n c.customer_id,\n c.first_name,\n c.last_name,\n count(o.order_id) as order_count,\n coalesce(sum(o.amount), 0) as total_amount\nfrom \"jaffle\".\"main\".\"stg_customers\" c\nleft join \"jaffle\".\"main\".\"stg_orders\" o on c.customer_id = o.customer_id\ngroup by c.customer_id, c.first_name, c.last_name", + "compiled_path": "target/compiled/jaffle_shop/models/marts/customers.sql", + "config": { + "access": "protected", + "alias": null, + "batch_size": null, + "begin": null, + "column_types": {}, + "concurrent_batches": null, + "contract": { + "alias_types": true, + "enforced": false + }, + "database": null, + "docs": { + "node_color": null, + "show": true + }, + "enabled": true, + "event_time": null, + "freshness": null, + "full_refresh": null, + "grants": {}, + "group": null, + "incremental_strategy": null, + "lookback": 1, + "materialized": "table", + "meta": {}, + "on_configuration_change": "apply", + "on_schema_change": "ignore", + "packages": [], + "persist_docs": {}, + "post-hook": [], + "pre-hook": [], + "quoting": {}, + "schema": null, + "tags": [], + "unique_key": null + }, + "constraints": [], + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [], + "nodes": [ + "model.jaffle_shop.stg_customers", + "model.jaffle_shop.stg_orders" + ] + }, + "deprecation_date": null, + "description": "One row per customer with total order count and revenue.", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "fqn": [ + "jaffle_shop", + "marts", + "customers" + ], + "functions": [], + "group": null, + "language": "sql", + "latest_version": null, + "meta": {}, + "metrics": [], + "name": "customers", + "original_file_path": "models/marts/customers.sql", + "package_name": "jaffle_shop", + "patch_path": "jaffle_shop://models/marts/schema.yml", + "path": "marts/customers.sql", + "primary_key": [ + "customer_id" + ], + "raw_code": "select\n c.customer_id,\n c.first_name,\n c.last_name,\n count(o.order_id) as order_count,\n coalesce(sum(o.amount), 0) as total_amount\nfrom {{ ref('stg_customers') }} c\nleft join {{ ref('stg_orders') }} o on c.customer_id = o.customer_id\ngroup by c.customer_id, c.first_name, c.last_name", + "refs": [ + { + "name": "stg_customers", + "package": null, + "version": null + }, + { + "name": "stg_orders", + "package": null, + "version": null + } + ], + "relation_name": "\"jaffle\".\"main\".\"customers\"", + "resource_type": "model", + "schema": "main", + "sources": [], + "tags": [], + "time_spine": null, + "unique_id": "model.jaffle_shop.customers", + "unrendered_config": { + "materialized": "table" + }, + "version": null + }, + "model.jaffle_shop.orders": { + "access": "protected", + "alias": "orders", + "build_path": null, + "checksum": { + "checksum": "2096364a1cbdcadb686bcd3fb7e3fd3c8bb05f6e290dabfeedb4fb9c0135fb7c", + "name": "sha256" + }, + "columns": { + "customer_id": { + "config": { + "meta": {}, + "tags": [] + }, + "constraints": [], + "data_type": null, + "description": "Foreign key to `stg_customers`.", + "doc_blocks": [], + "granularity": null, + "meta": {}, + "name": "customer_id", + "quote": null, + "tags": [] + }, + "order_id": { + "config": { + "meta": {}, + "tags": [] + }, + "constraints": [], + "data_type": null, + "description": "Primary key.", + "doc_blocks": [], + "granularity": null, + "meta": {}, + "name": "order_id", + "quote": null, + "tags": [] + } + }, + "compiled": true, + "compiled_code": "select\n o.order_id,\n o.customer_id,\n c.first_name || ' ' || c.last_name as customer_name,\n o.order_date,\n o.amount\nfrom \"jaffle\".\"main\".\"stg_orders\" o\njoin \"jaffle\".\"main\".\"stg_customers\" c on o.customer_id = c.customer_id", + "compiled_path": "target/compiled/jaffle_shop/models/marts/orders.sql", + "config": { + "access": "protected", + "alias": null, + "batch_size": null, + "begin": null, + "column_types": {}, + "concurrent_batches": null, + "contract": { + "alias_types": true, + "enforced": false + }, + "database": null, + "docs": { + "node_color": null, + "show": true + }, + "enabled": true, + "event_time": null, + "freshness": null, + "full_refresh": null, + "grants": {}, + "group": null, + "incremental_strategy": null, + "lookback": 1, + "materialized": "table", + "meta": {}, + "on_configuration_change": "apply", + "on_schema_change": "ignore", + "packages": [], + "persist_docs": {}, + "post-hook": [], + "pre-hook": [], + "quoting": {}, + "schema": null, + "tags": [], + "unique_key": null + }, + "constraints": [], + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [], + "nodes": [ + "model.jaffle_shop.stg_orders", + "model.jaffle_shop.stg_customers" + ] + }, + "deprecation_date": null, + "description": "One row per order with a joined customer name.", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "fqn": [ + "jaffle_shop", + "marts", + "orders" + ], + "functions": [], + "group": null, + "language": "sql", + "latest_version": null, + "meta": {}, + "metrics": [], + "name": "orders", + "original_file_path": "models/marts/orders.sql", + "package_name": "jaffle_shop", + "patch_path": "jaffle_shop://models/marts/schema.yml", + "path": "marts/orders.sql", + "primary_key": [ + "order_id" + ], + "raw_code": "select\n o.order_id,\n o.customer_id,\n c.first_name || ' ' || c.last_name as customer_name,\n o.order_date,\n o.amount\nfrom {{ ref('stg_orders') }} o\njoin {{ ref('stg_customers') }} c on o.customer_id = c.customer_id", + "refs": [ + { + "name": "stg_orders", + "package": null, + "version": null + }, + { + "name": "stg_customers", + "package": null, + "version": null + } + ], + "relation_name": "\"jaffle\".\"main\".\"orders\"", + "resource_type": "model", + "schema": "main", + "sources": [], + "tags": [], + "time_spine": null, + "unique_id": "model.jaffle_shop.orders", + "unrendered_config": { + "materialized": "table" + }, + "version": null + }, + "model.jaffle_shop.stg_customers": { + "access": "protected", + "alias": "stg_customers", + "build_path": null, + "checksum": { + "checksum": "0c62570c561beac088db9a1b5688412b85f5c19feb6e5b78c4e52220f47865a8", + "name": "sha256" + }, + "columns": { + "customer_id": { + "config": { + "meta": {}, + "tags": [] + }, + "constraints": [], + "data_type": null, + "description": "Primary key of the customer.", + "doc_blocks": [], + "granularity": null, + "meta": {}, + "name": "customer_id", + "quote": null, + "tags": [] + } + }, + "compiled": true, + "compiled_code": "select\n id as customer_id,\n first_name,\n last_name\nfrom \"jaffle\".\"main\".\"raw_customers\"", + "compiled_path": "target/compiled/jaffle_shop/models/staging/stg_customers.sql", + "config": { + "access": "protected", + "alias": null, + "batch_size": null, + "begin": null, + "column_types": {}, + "concurrent_batches": null, + "contract": { + "alias_types": true, + "enforced": false + }, + "database": null, + "docs": { + "node_color": null, + "show": true + }, + "enabled": true, + "event_time": null, + "freshness": null, + "full_refresh": null, + "grants": {}, + "group": null, + "incremental_strategy": null, + "lookback": 1, + "materialized": "view", + "meta": {}, + "on_configuration_change": "apply", + "on_schema_change": "ignore", + "packages": [], + "persist_docs": {}, + "post-hook": [], + "pre-hook": [], + "quoting": {}, + "schema": null, + "tags": [], + "unique_key": null + }, + "constraints": [], + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [], + "nodes": [ + "seed.jaffle_shop.raw_customers" + ] + }, + "deprecation_date": null, + "description": "Renamed customer columns from the raw seed.", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "fqn": [ + "jaffle_shop", + "staging", + "stg_customers" + ], + "functions": [], + "group": null, + "language": "sql", + "latest_version": null, + "meta": {}, + "metrics": [], + "name": "stg_customers", + "original_file_path": "models/staging/stg_customers.sql", + "package_name": "jaffle_shop", + "patch_path": "jaffle_shop://models/staging/schema.yml", + "path": "staging/stg_customers.sql", + "primary_key": [ + "customer_id" + ], + "raw_code": "select\n id as customer_id,\n first_name,\n last_name\nfrom {{ ref('raw_customers') }}", + "refs": [ + { + "name": "raw_customers", + "package": null, + "version": null + } + ], + "relation_name": "\"jaffle\".\"main\".\"stg_customers\"", + "resource_type": "model", + "schema": "main", + "sources": [], + "tags": [], + "time_spine": null, + "unique_id": "model.jaffle_shop.stg_customers", + "unrendered_config": { + "materialized": "view" + }, + "version": null + }, + "model.jaffle_shop.stg_orders": { + "access": "protected", + "alias": "stg_orders", + "build_path": null, + "checksum": { + "checksum": "3904103d77935e355e32fc6c6d969098f7053ab6f419c57c27f600042b3e36fa", + "name": "sha256" + }, + "columns": { + "customer_id": { + "config": { + "meta": {}, + "tags": [] + }, + "constraints": [], + "data_type": null, + "description": "Foreign key to `stg_customers`.", + "doc_blocks": [], + "granularity": null, + "meta": {}, + "name": "customer_id", + "quote": null, + "tags": [] + }, + "order_id": { + "config": { + "meta": {}, + "tags": [] + }, + "constraints": [], + "data_type": null, + "description": "Primary key of the order.", + "doc_blocks": [], + "granularity": null, + "meta": {}, + "name": "order_id", + "quote": null, + "tags": [] + } + }, + "compiled": true, + "compiled_code": "select\n id as order_id,\n customer_id,\n order_date,\n amount\nfrom \"jaffle\".\"main\".\"raw_orders\"", + "compiled_path": "target/compiled/jaffle_shop/models/staging/stg_orders.sql", + "config": { + "access": "protected", + "alias": null, + "batch_size": null, + "begin": null, + "column_types": {}, + "concurrent_batches": null, + "contract": { + "alias_types": true, + "enforced": false + }, + "database": null, + "docs": { + "node_color": null, + "show": true + }, + "enabled": true, + "event_time": null, + "freshness": null, + "full_refresh": null, + "grants": {}, + "group": null, + "incremental_strategy": null, + "lookback": 1, + "materialized": "view", + "meta": {}, + "on_configuration_change": "apply", + "on_schema_change": "ignore", + "packages": [], + "persist_docs": {}, + "post-hook": [], + "pre-hook": [], + "quoting": {}, + "schema": null, + "tags": [], + "unique_key": null + }, + "constraints": [], + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [], + "nodes": [ + "seed.jaffle_shop.raw_orders" + ] + }, + "deprecation_date": null, + "description": "Renamed order columns from the raw seed.", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "fqn": [ + "jaffle_shop", + "staging", + "stg_orders" + ], + "functions": [], + "group": null, + "language": "sql", + "latest_version": null, + "meta": {}, + "metrics": [], + "name": "stg_orders", + "original_file_path": "models/staging/stg_orders.sql", + "package_name": "jaffle_shop", + "patch_path": "jaffle_shop://models/staging/schema.yml", + "path": "staging/stg_orders.sql", + "primary_key": [ + "order_id" + ], + "raw_code": "select\n id as order_id,\n customer_id,\n order_date,\n amount\nfrom {{ ref('raw_orders') }}", + "refs": [ + { + "name": "raw_orders", + "package": null, + "version": null + } + ], + "relation_name": "\"jaffle\".\"main\".\"stg_orders\"", + "resource_type": "model", + "schema": "main", + "sources": [], + "tags": [], + "time_spine": null, + "unique_id": "model.jaffle_shop.stg_orders", + "unrendered_config": { + "materialized": "view" + }, + "version": null + }, + "seed.jaffle_shop.raw_customers": { + "alias": "raw_customers", + "build_path": null, + "checksum": { + "checksum": "f87999c47abb3e34275aa99b3f4356130e1c07c9fc90769009c9bc1c559a8882", + "name": "sha256" + }, + "columns": {}, + "config": { + "alias": null, + "batch_size": null, + "begin": null, + "column_types": {}, + "concurrent_batches": null, + "contract": { + "alias_types": true, + "enforced": false + }, + "database": null, + "delimiter": ",", + "docs": { + "node_color": null, + "show": true + }, + "enabled": true, + "event_time": null, + "full_refresh": null, + "grants": {}, + "group": null, + "incremental_strategy": null, + "lookback": 1, + "materialized": "seed", + "meta": {}, + "on_configuration_change": "apply", + "on_schema_change": "ignore", + "packages": [], + "persist_docs": {}, + "post-hook": [], + "pre-hook": [], + "quote_columns": null, + "quoting": {}, + "schema": null, + "tags": [], + "unique_key": null + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "fqn": [ + "jaffle_shop", + "raw_customers" + ], + "group": null, + "meta": {}, + "name": "raw_customers", + "original_file_path": "seeds/raw_customers.csv", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "raw_customers.csv", + "raw_code": "", + "relation_name": "\"jaffle\".\"main\".\"raw_customers\"", + "resource_type": "seed", + "root_path": "{{SAMPLE_ROOT}}", + "schema": "main", + "tags": [], + "unique_id": "seed.jaffle_shop.raw_customers", + "unrendered_config": {} + }, + "seed.jaffle_shop.raw_orders": { + "alias": "raw_orders", + "build_path": null, + "checksum": { + "checksum": "43631074be58d896a15ebc0b36dfacf28ea35b71ccbae7d59394bac8ed99df4a", + "name": "sha256" + }, + "columns": {}, + "config": { + "alias": null, + "batch_size": null, + "begin": null, + "column_types": {}, + "concurrent_batches": null, + "contract": { + "alias_types": true, + "enforced": false + }, + "database": null, + "delimiter": ",", + "docs": { + "node_color": null, + "show": true + }, + "enabled": true, + "event_time": null, + "full_refresh": null, + "grants": {}, + "group": null, + "incremental_strategy": null, + "lookback": 1, + "materialized": "seed", + "meta": {}, + "on_configuration_change": "apply", + "on_schema_change": "ignore", + "packages": [], + "persist_docs": {}, + "post-hook": [], + "pre-hook": [], + "quote_columns": null, + "quoting": {}, + "schema": null, + "tags": [], + "unique_key": null + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "fqn": [ + "jaffle_shop", + "raw_orders" + ], + "group": null, + "meta": {}, + "name": "raw_orders", + "original_file_path": "seeds/raw_orders.csv", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "raw_orders.csv", + "raw_code": "", + "relation_name": "\"jaffle\".\"main\".\"raw_orders\"", + "resource_type": "seed", + "root_path": "{{SAMPLE_ROOT}}", + "schema": "main", + "tags": [], + "unique_id": "seed.jaffle_shop.raw_orders", + "unrendered_config": {} + }, + "test.jaffle_shop.not_null_customers_customer_id.5c9bf9911d": { + "alias": "not_null_customers_customer_id", + "attached_node": "model.jaffle_shop.customers", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "customer_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\n\n\nselect customer_id\nfrom \"jaffle\".\"main\".\"customers\"\nwhere customer_id is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/marts/schema.yml/not_null_customers_customer_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_not_null", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.customers" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.customers", + "fqn": [ + "jaffle_shop", + "marts", + "not_null_customers_customer_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "not_null_customers_customer_id", + "original_file_path": "models/marts/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "not_null_customers_customer_id.sql", + "raw_code": "{{ test_not_null(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "customers", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "customer_id", + "model": "{{ get_where_subquery(ref('customers')) }}" + }, + "name": "not_null", + "namespace": null + }, + "unique_id": "test.jaffle_shop.not_null_customers_customer_id.5c9bf9911d", + "unrendered_config": {} + }, + "test.jaffle_shop.not_null_customers_order_count.f60dfe3b39": { + "alias": "not_null_customers_order_count", + "attached_node": "model.jaffle_shop.customers", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "order_count", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\n\n\nselect order_count\nfrom \"jaffle\".\"main\".\"customers\"\nwhere order_count is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/marts/schema.yml/not_null_customers_order_count.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_not_null", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.customers" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.customers", + "fqn": [ + "jaffle_shop", + "marts", + "not_null_customers_order_count" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "not_null_customers_order_count", + "original_file_path": "models/marts/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "not_null_customers_order_count.sql", + "raw_code": "{{ test_not_null(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "customers", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "order_count", + "model": "{{ get_where_subquery(ref('customers')) }}" + }, + "name": "not_null", + "namespace": null + }, + "unique_id": "test.jaffle_shop.not_null_customers_order_count.f60dfe3b39", + "unrendered_config": {} + }, + "test.jaffle_shop.not_null_customers_total_amount.83faa92c8a": { + "alias": "not_null_customers_total_amount", + "attached_node": "model.jaffle_shop.customers", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "total_amount", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\n\n\nselect total_amount\nfrom \"jaffle\".\"main\".\"customers\"\nwhere total_amount is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/marts/schema.yml/not_null_customers_total_amount.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_not_null", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.customers" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.customers", + "fqn": [ + "jaffle_shop", + "marts", + "not_null_customers_total_amount" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "not_null_customers_total_amount", + "original_file_path": "models/marts/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "not_null_customers_total_amount.sql", + "raw_code": "{{ test_not_null(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "customers", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "total_amount", + "model": "{{ get_where_subquery(ref('customers')) }}" + }, + "name": "not_null", + "namespace": null + }, + "unique_id": "test.jaffle_shop.not_null_customers_total_amount.83faa92c8a", + "unrendered_config": {} + }, + "test.jaffle_shop.not_null_orders_customer_id.c5f02694af": { + "alias": "not_null_orders_customer_id", + "attached_node": "model.jaffle_shop.orders", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "customer_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\n\n\nselect customer_id\nfrom \"jaffle\".\"main\".\"orders\"\nwhere customer_id is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/marts/schema.yml/not_null_orders_customer_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_not_null", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.orders" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.orders", + "fqn": [ + "jaffle_shop", + "marts", + "not_null_orders_customer_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "not_null_orders_customer_id", + "original_file_path": "models/marts/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "not_null_orders_customer_id.sql", + "raw_code": "{{ test_not_null(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "orders", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "customer_id", + "model": "{{ get_where_subquery(ref('orders')) }}" + }, + "name": "not_null", + "namespace": null + }, + "unique_id": "test.jaffle_shop.not_null_orders_customer_id.c5f02694af", + "unrendered_config": {} + }, + "test.jaffle_shop.not_null_orders_order_id.cf6c17daed": { + "alias": "not_null_orders_order_id", + "attached_node": "model.jaffle_shop.orders", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "order_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\n\n\nselect order_id\nfrom \"jaffle\".\"main\".\"orders\"\nwhere order_id is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/marts/schema.yml/not_null_orders_order_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_not_null", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.orders" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.orders", + "fqn": [ + "jaffle_shop", + "marts", + "not_null_orders_order_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "not_null_orders_order_id", + "original_file_path": "models/marts/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "not_null_orders_order_id.sql", + "raw_code": "{{ test_not_null(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "orders", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "order_id", + "model": "{{ get_where_subquery(ref('orders')) }}" + }, + "name": "not_null", + "namespace": null + }, + "unique_id": "test.jaffle_shop.not_null_orders_order_id.cf6c17daed", + "unrendered_config": {} + }, + "test.jaffle_shop.not_null_stg_customers_customer_id.e2cfb1f9aa": { + "alias": "not_null_stg_customers_customer_id", + "attached_node": "model.jaffle_shop.stg_customers", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "customer_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\n\n\nselect customer_id\nfrom \"jaffle\".\"main\".\"stg_customers\"\nwhere customer_id is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/staging/schema.yml/not_null_stg_customers_customer_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_not_null", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.stg_customers" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.stg_customers", + "fqn": [ + "jaffle_shop", + "staging", + "not_null_stg_customers_customer_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "not_null_stg_customers_customer_id", + "original_file_path": "models/staging/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "not_null_stg_customers_customer_id.sql", + "raw_code": "{{ test_not_null(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "stg_customers", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "customer_id", + "model": "{{ get_where_subquery(ref('stg_customers')) }}" + }, + "name": "not_null", + "namespace": null + }, + "unique_id": "test.jaffle_shop.not_null_stg_customers_customer_id.e2cfb1f9aa", + "unrendered_config": {} + }, + "test.jaffle_shop.not_null_stg_orders_customer_id.af79d5e4b5": { + "alias": "not_null_stg_orders_customer_id", + "attached_node": "model.jaffle_shop.stg_orders", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "customer_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\n\n\nselect customer_id\nfrom \"jaffle\".\"main\".\"stg_orders\"\nwhere customer_id is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/staging/schema.yml/not_null_stg_orders_customer_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_not_null", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.stg_orders" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.stg_orders", + "fqn": [ + "jaffle_shop", + "staging", + "not_null_stg_orders_customer_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "not_null_stg_orders_customer_id", + "original_file_path": "models/staging/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "not_null_stg_orders_customer_id.sql", + "raw_code": "{{ test_not_null(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "stg_orders", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "customer_id", + "model": "{{ get_where_subquery(ref('stg_orders')) }}" + }, + "name": "not_null", + "namespace": null + }, + "unique_id": "test.jaffle_shop.not_null_stg_orders_customer_id.af79d5e4b5", + "unrendered_config": {} + }, + "test.jaffle_shop.not_null_stg_orders_order_id.81cfe2fe64": { + "alias": "not_null_stg_orders_order_id", + "attached_node": "model.jaffle_shop.stg_orders", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "order_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\n\n\nselect order_id\nfrom \"jaffle\".\"main\".\"stg_orders\"\nwhere order_id is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/staging/schema.yml/not_null_stg_orders_order_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_not_null", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.stg_orders" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.stg_orders", + "fqn": [ + "jaffle_shop", + "staging", + "not_null_stg_orders_order_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "not_null_stg_orders_order_id", + "original_file_path": "models/staging/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "not_null_stg_orders_order_id.sql", + "raw_code": "{{ test_not_null(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "stg_orders", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "order_id", + "model": "{{ get_where_subquery(ref('stg_orders')) }}" + }, + "name": "not_null", + "namespace": null + }, + "unique_id": "test.jaffle_shop.not_null_stg_orders_order_id.81cfe2fe64", + "unrendered_config": {} + }, + "test.jaffle_shop.relationships_stg_orders_customer_id__customer_id__ref_stg_customers_.430bf21500": { + "alias": "relationships_stg_orders_96411fe0c89b49c3f4da955dfd358ba0", + "attached_node": "model.jaffle_shop.stg_orders", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "customer_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\nwith child as (\n select customer_id as from_field\n from \"jaffle\".\"main\".\"stg_orders\"\n where customer_id is not null\n),\n\nparent as (\n select customer_id as to_field\n from \"jaffle\".\"main\".\"stg_customers\"\n)\n\nselect\n from_field\n\nfrom child\nleft join parent\n on child.from_field = parent.to_field\n\nwhere parent.to_field is null\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/staging/schema.yml/relationships_stg_orders_96411fe0c89b49c3f4da955dfd358ba0.sql", + "config": { + "alias": "relationships_stg_orders_96411fe0c89b49c3f4da955dfd358ba0", + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_relationships", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.stg_customers", + "model.jaffle_shop.stg_orders" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.stg_orders", + "fqn": [ + "jaffle_shop", + "staging", + "relationships_stg_orders_customer_id__customer_id__ref_stg_customers_" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "relationships_stg_orders_customer_id__customer_id__ref_stg_customers_", + "original_file_path": "models/staging/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "relationships_stg_orders_96411fe0c89b49c3f4da955dfd358ba0.sql", + "raw_code": "{{ test_relationships(**_dbt_generic_test_kwargs) }}{{ config(alias=\"relationships_stg_orders_96411fe0c89b49c3f4da955dfd358ba0\") }}", + "refs": [ + { + "name": "stg_customers", + "package": null, + "version": null + }, + { + "name": "stg_orders", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "customer_id", + "field": "customer_id", + "model": "{{ get_where_subquery(ref('stg_orders')) }}", + "to": "ref('stg_customers')" + }, + "name": "relationships", + "namespace": null + }, + "unique_id": "test.jaffle_shop.relationships_stg_orders_customer_id__customer_id__ref_stg_customers_.430bf21500", + "unrendered_config": { + "alias": "relationships_stg_orders_96411fe0c89b49c3f4da955dfd358ba0" + } + }, + "test.jaffle_shop.unique_customers_customer_id.c5af1ff4b1": { + "alias": "unique_customers_customer_id", + "attached_node": "model.jaffle_shop.customers", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "customer_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\nselect\n customer_id as unique_field,\n count(*) as n_records\n\nfrom \"jaffle\".\"main\".\"customers\"\nwhere customer_id is not null\ngroup by customer_id\nhaving count(*) > 1\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/marts/schema.yml/unique_customers_customer_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_unique", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.customers" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.customers", + "fqn": [ + "jaffle_shop", + "marts", + "unique_customers_customer_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "unique_customers_customer_id", + "original_file_path": "models/marts/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "unique_customers_customer_id.sql", + "raw_code": "{{ test_unique(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "customers", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "customer_id", + "model": "{{ get_where_subquery(ref('customers')) }}" + }, + "name": "unique", + "namespace": null + }, + "unique_id": "test.jaffle_shop.unique_customers_customer_id.c5af1ff4b1", + "unrendered_config": {} + }, + "test.jaffle_shop.unique_orders_order_id.fed79b3a6e": { + "alias": "unique_orders_order_id", + "attached_node": "model.jaffle_shop.orders", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "order_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\nselect\n order_id as unique_field,\n count(*) as n_records\n\nfrom \"jaffle\".\"main\".\"orders\"\nwhere order_id is not null\ngroup by order_id\nhaving count(*) > 1\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/marts/schema.yml/unique_orders_order_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_unique", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.orders" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.orders", + "fqn": [ + "jaffle_shop", + "marts", + "unique_orders_order_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "unique_orders_order_id", + "original_file_path": "models/marts/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "unique_orders_order_id.sql", + "raw_code": "{{ test_unique(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "orders", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "order_id", + "model": "{{ get_where_subquery(ref('orders')) }}" + }, + "name": "unique", + "namespace": null + }, + "unique_id": "test.jaffle_shop.unique_orders_order_id.fed79b3a6e", + "unrendered_config": {} + }, + "test.jaffle_shop.unique_stg_customers_customer_id.c7614daada": { + "alias": "unique_stg_customers_customer_id", + "attached_node": "model.jaffle_shop.stg_customers", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "customer_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\nselect\n customer_id as unique_field,\n count(*) as n_records\n\nfrom \"jaffle\".\"main\".\"stg_customers\"\nwhere customer_id is not null\ngroup by customer_id\nhaving count(*) > 1\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/staging/schema.yml/unique_stg_customers_customer_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_unique", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.stg_customers" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.stg_customers", + "fqn": [ + "jaffle_shop", + "staging", + "unique_stg_customers_customer_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "unique_stg_customers_customer_id", + "original_file_path": "models/staging/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "unique_stg_customers_customer_id.sql", + "raw_code": "{{ test_unique(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "stg_customers", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "customer_id", + "model": "{{ get_where_subquery(ref('stg_customers')) }}" + }, + "name": "unique", + "namespace": null + }, + "unique_id": "test.jaffle_shop.unique_stg_customers_customer_id.c7614daada", + "unrendered_config": {} + }, + "test.jaffle_shop.unique_stg_orders_order_id.e3b841c71a": { + "alias": "unique_stg_orders_order_id", + "attached_node": "model.jaffle_shop.stg_orders", + "build_path": null, + "checksum": { + "checksum": "", + "name": "none" + }, + "column_name": "order_id", + "columns": {}, + "compiled": true, + "compiled_code": "\n \n \n\nselect\n order_id as unique_field,\n count(*) as n_records\n\nfrom \"jaffle\".\"main\".\"stg_orders\"\nwhere order_id is not null\ngroup by order_id\nhaving count(*) > 1\n\n\n", + "compiled_path": "target/compiled/jaffle_shop/models/staging/schema.yml/unique_stg_orders_order_id.sql", + "config": { + "alias": null, + "database": null, + "enabled": true, + "error_if": "!= 0", + "fail_calc": "count(*)", + "group": null, + "limit": null, + "materialized": "test", + "meta": {}, + "schema": "dbt_test__audit", + "severity": "ERROR", + "store_failures": null, + "store_failures_as": null, + "tags": [], + "warn_if": "!= 0", + "where": null + }, + "contract": { + "alias_types": true, + "checksum": null, + "enforced": false + }, + "created_at": 1785000000.0, + "database": "jaffle", + "depends_on": { + "macros": [ + "macro.dbt.test_unique", + "macro.dbt.get_where_subquery" + ], + "nodes": [ + "model.jaffle_shop.stg_orders" + ] + }, + "description": "", + "doc_blocks": [], + "docs": { + "node_color": null, + "show": true + }, + "extra_ctes": [], + "extra_ctes_injected": true, + "file_key_name": "models.stg_orders", + "fqn": [ + "jaffle_shop", + "staging", + "unique_stg_orders_order_id" + ], + "functions": [], + "group": null, + "language": "sql", + "meta": {}, + "metrics": [], + "name": "unique_stg_orders_order_id", + "original_file_path": "models/staging/schema.yml", + "package_name": "jaffle_shop", + "patch_path": null, + "path": "unique_stg_orders_order_id.sql", + "raw_code": "{{ test_unique(**_dbt_generic_test_kwargs) }}", + "refs": [ + { + "name": "stg_orders", + "package": null, + "version": null + } + ], + "relation_name": null, + "resource_type": "test", + "schema": "main_dbt_test__audit", + "sources": [], + "tags": [], + "test_metadata": { + "kwargs": { + "column_name": "order_id", + "model": "{{ get_where_subquery(ref('stg_orders')) }}" + }, + "name": "unique", + "namespace": null + }, + "unique_id": "test.jaffle_shop.unique_stg_orders_order_id.e3b841c71a", + "unrendered_config": {} + } + }, + "parent_map": { + "model.jaffle_shop.customers": [ + "model.jaffle_shop.stg_customers", + "model.jaffle_shop.stg_orders" + ], + "model.jaffle_shop.orders": [ + "model.jaffle_shop.stg_customers", + "model.jaffle_shop.stg_orders" + ], + "model.jaffle_shop.stg_customers": [ + "seed.jaffle_shop.raw_customers" + ], + "model.jaffle_shop.stg_orders": [ + "seed.jaffle_shop.raw_orders" + ], + "seed.jaffle_shop.raw_customers": [], + "seed.jaffle_shop.raw_orders": [], + "test.jaffle_shop.not_null_customers_customer_id.5c9bf9911d": [ + "model.jaffle_shop.customers" + ], + "test.jaffle_shop.not_null_customers_order_count.f60dfe3b39": [ + "model.jaffle_shop.customers" + ], + "test.jaffle_shop.not_null_customers_total_amount.83faa92c8a": [ + "model.jaffle_shop.customers" + ], + "test.jaffle_shop.not_null_orders_customer_id.c5f02694af": [ + "model.jaffle_shop.orders" + ], + "test.jaffle_shop.not_null_orders_order_id.cf6c17daed": [ + "model.jaffle_shop.orders" + ], + "test.jaffle_shop.not_null_stg_customers_customer_id.e2cfb1f9aa": [ + "model.jaffle_shop.stg_customers" + ], + "test.jaffle_shop.not_null_stg_orders_customer_id.af79d5e4b5": [ + "model.jaffle_shop.stg_orders" + ], + "test.jaffle_shop.not_null_stg_orders_order_id.81cfe2fe64": [ + "model.jaffle_shop.stg_orders" + ], + "test.jaffle_shop.relationships_stg_orders_customer_id__customer_id__ref_stg_customers_.430bf21500": [ + "model.jaffle_shop.stg_customers", + "model.jaffle_shop.stg_orders" + ], + "test.jaffle_shop.unique_customers_customer_id.c5af1ff4b1": [ + "model.jaffle_shop.customers" + ], + "test.jaffle_shop.unique_orders_order_id.fed79b3a6e": [ + "model.jaffle_shop.orders" + ], + "test.jaffle_shop.unique_stg_customers_customer_id.c7614daada": [ + "model.jaffle_shop.stg_customers" + ], + "test.jaffle_shop.unique_stg_orders_order_id.e3b841c71a": [ + "model.jaffle_shop.stg_orders" + ] + }, + "saved_queries": {}, + "selectors": {}, + "semantic_models": {}, + "sources": {}, + "unit_tests": {} +} diff --git a/packages/opencode/sample-projects/jaffle-shop-duckdb/verify-freshness.test.ts b/packages/opencode/sample-projects/jaffle-shop-duckdb/verify-freshness.test.ts new file mode 100644 index 0000000000..cc95613e31 --- /dev/null +++ b/packages/opencode/sample-projects/jaffle-shop-duckdb/verify-freshness.test.ts @@ -0,0 +1,158 @@ +/** + * Freshness guard for the committed pre-compiled dbt manifest. + * + * `sample-projects/regenerate.sh` advertises this test in its docblock: + * "the freshness test will fail if source hashes don't match what the + * committed manifest was generated from — that's the guard against a + * source edit landing without a matching artifact refresh." Consensus + * review flagged that no such test existed. This IS that test. + * + * For every model / seed node in the shipped `target/manifest.json` that + * carries a `checksum.name === "sha256"`, re-hash the source file it + * refers to and assert the digests match. If a maintainer edits a model + * without re-running `regenerate.sh`, this fails — pointing at the + * specific file and expected checksum. + * + * dbt's convention: the hash is computed on the file contents with the + * trailing newline stripped (see dbt-core's `hash_file` in + * `dbt.parser.base.BaseParser`; `contents.rstrip("\n")` then sha256). + * We reproduce that here so a maintainer's editor-added or -stripped + * trailing newline doesn't false-positive the check. + */ + +import { describe, expect, test } from "bun:test" +import { createHash } from "node:crypto" +import fs from "node:fs" +import path from "node:path" + +const SAMPLE_DIR = path.resolve(__dirname) +const MANIFEST_PATH = path.join(SAMPLE_DIR, "target", "manifest.json") + +interface ChecksumStanza { + name: string + checksum: string +} + +interface DbtNode { + original_file_path?: string + checksum?: ChecksumStanza + resource_type?: string +} + +/** + * dbt's file-hash convention. See dbt-core `hash_file`. + * The sha256 is over the contents with the FINAL trailing newline stripped + * (only one — not all consecutive trailing newlines). + */ +function dbtFileHash(absPath: string): string { + const raw = fs.readFileSync(absPath, "utf8") + // Strip one trailing \n if present. Windows line endings survive as \r\n; + // dbt hashes the file contents as-read from disk in text mode, so we + // match that. + const stripped = raw.endsWith("\n") ? raw.slice(0, -1) : raw + return createHash("sha256").update(stripped, "utf8").digest("hex") +} + +describe("verify-freshness — committed manifest matches source files", () => { + test("target/manifest.json is present", () => { + expect(fs.existsSync(MANIFEST_PATH), `expected shipped manifest at ${MANIFEST_PATH}`).toBe(true) + }) + + test("every checksummed node in the manifest matches its source file's dbt hash", () => { + const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, "utf8")) as { + nodes?: Record + } + const nodes = manifest.nodes ?? {} + const failures: string[] = [] + let checked = 0 + + for (const [id, node] of Object.entries(nodes)) { + const checksum = node.checksum + if (!checksum || checksum.name !== "sha256") continue + const origPath = node.original_file_path + if (!origPath) continue + const abs = path.join(SAMPLE_DIR, origPath) + if (!fs.existsSync(abs)) { + failures.push( + `${id}: manifest references ${origPath} but the file does not exist at ${abs}. Rerun regenerate.sh?`, + ) + continue + } + const expected = checksum.checksum + const actual = dbtFileHash(abs) + if (actual !== expected) { + failures.push( + `${id}: sha256 mismatch for ${origPath}\n committed manifest: ${expected}\n current file: ${actual}\n → run sample-projects/regenerate.sh and commit the refreshed manifest`, + ) + } + checked++ + } + + // Sanity: the manifest must actually contain checksummed nodes; a zero + // count would silently pass this test on any breakage. + expect(checked, "no sha256-checksummed nodes found in manifest — has the shape changed?").toBeGreaterThan(0) + expect(failures).toEqual([]) + }) + + test("shipped source files are all represented in the manifest (codex NEW-10 — set membership, not just per-node hashing)", () => { + // Companion to the per-node hash test. That test iterates nodes and + // hashes what's there; it says nothing about whether new source files + // added under models/ or seeds/ actually made it into the manifest. + // A maintainer who adds a new .sql model but forgets to re-run + // regenerate.sh would sail past the per-node hash check (the model + // simply has no manifest node to compare against). This test locks + // the source SET so that gap is caught. + const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, "utf8")) as { + nodes?: Record + } + const nodes = manifest.nodes ?? {} + const manifestPaths = new Set() + for (const node of Object.values(nodes)) { + if (node.original_file_path) manifestPaths.add(node.original_file_path) + } + // Enumerate the actual source tree the maintainer curates. Only files + // dbt itself would compile: .sql models, .csv seeds. Docs, YAML, + // manifest metadata files are not per-file compiled — dbt notices + // schema.yml through its own parser and doesn't emit a checksum'd + // node for it. + const expectedFiles: string[] = [] + const walk = (relDir: string, exts: RegExp) => { + const absDir = path.join(SAMPLE_DIR, relDir) + if (!fs.existsSync(absDir)) return + for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) { + const relPath = path.join(relDir, entry.name) + if (entry.isDirectory()) walk(relPath, exts) + else if (exts.test(entry.name)) expectedFiles.push(relPath) + } + } + walk("models", /\.sql$/) + walk("seeds", /\.csv$/) + const missing = expectedFiles.filter((f) => !manifestPaths.has(f)) + expect( + missing, + `source files exist under models/ or seeds/ but have no manifest node — re-run sample-projects/regenerate.sh: ${missing.join(", ")}`, + ).toEqual([]) + }) + + test("manifest identity fields are scrubbed (no maintainer UUID or wall-clock times leaked to installers)", () => { + // Companion to regenerate.sh's sanitizer: identity + wall-clock fields + // must be zeroed/pinned. If a maintainer runs `dbt compile` by hand + // without going through regenerate.sh and commits, this catches it. + const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, "utf8")) as { + metadata?: Record + nodes?: Record + } + const md = manifest.metadata ?? {} + const ZERO_UUID = "00000000-0000-0000-0000-000000000000" + const FIXED_ISO = "2026-07-24T00:00:00Z" + expect(md.user_id, "user_id would leak the maintainer's persistent dbt telemetry UUID").toBe(ZERO_UUID) + expect(md.project_id).toBe(ZERO_UUID) + expect(md.invocation_id).toBe(ZERO_UUID) + expect(md.generated_at).toBe(FIXED_ISO) + expect(md.invocation_started_at).toBe(FIXED_ISO) + expect(md.run_started_at).toBe(FIXED_ISO) + expect(md.send_anonymous_usage_stats).toBe(false) + // env stripped — carries USER/PWD/HOME. + expect(md.env).toBeUndefined() + }) +}) diff --git a/packages/opencode/sample-projects/regenerate.sh b/packages/opencode/sample-projects/regenerate.sh new file mode 100755 index 0000000000..96574a0a21 --- /dev/null +++ b/packages/opencode/sample-projects/regenerate.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# Regenerate the pre-compiled target/manifest.json that ships alongside the +# jaffle-shop-duckdb starter sample. Run this after editing any source file +# under jaffle-shop-duckdb/{dbt_project.yml,profiles.yml,models,seeds}, +# then commit the refreshed target/manifest.json in the same commit as the +# source change. +# +# The freshness test (jaffle-shop-duckdb/verify-freshness.test.ts) will +# fail if source hashes don't match what the committed manifest was +# generated from — that's the guard against a source edit landing without +# a matching artifact refresh. +# +# Requires: dbt-core + dbt-duckdb on PATH. +set -euo pipefail + +SAMPLE_DIR="$(cd "$(dirname "$0")/jaffle-shop-duckdb" && pwd)" +cd "$SAMPLE_DIR" + +if ! command -v dbt >/dev/null 2>&1; then + echo "ERROR: dbt is not on PATH. Install with: pip install dbt-duckdb" >&2 + exit 127 +fi + +# Compile against the sample's own profile file (not the user's ~/.dbt/profiles.yml). +export DBT_PROFILES_DIR="$SAMPLE_DIR" + +# Clean stale artifacts so what we commit is fully derived from current source. +rm -rf target dbt_packages + +# `dbt compile` produces target/manifest.json and target/graph.gpickle. We only +# ship manifest.json (graph.gpickle is a Python pickle and adds no value for +# our TypeScript consumers). `dbt parse` also produces manifest.json but omits +# the compiled_code field, which /review needs — so we compile. +dbt compile --project-dir "$SAMPLE_DIR" --profiles-dir "$SAMPLE_DIR" + +# Sanitize the manifest so no committed bytes are host-specific: +# 1. Replace the absolute sample path with the {{SAMPLE_ROOT}} sentinel +# ONLY inside JSON string values — never in object keys and never at +# the raw-text level. A raw text.replace() would silently mangle any +# legitimate string in the manifest that happens to contain the +# maintainer's home directory (e.g. a model description or a compiled +# SQL literal referencing a real path). +# 2. Wipe the maintainer's identity + all wall-clock timestamps. dbt +# writes `user_id` from ~/.dbt/.user.yml (a persistent UUID that +# identifies whoever compiled the manifest) and every node/macro +# carries a real `created_at` epoch. Ship none of it. Set +# send_anonymous_usage_stats to false so an installer's dbt is not +# steered toward opt-in telemetry that the sample author already +# accepted upstream. +# 3. Pin `generated_at`, `invocation_started_at`, `run_started_at`, and +# every node/macro `created_at` to the same fixed release-day +# timestamp so committed diffs only change when source changes. Zero +# epoch is avoided because some downstream freshness-check tools may +# treat it as pathological — a plausible past date is safer. +python3 - "$SAMPLE_DIR/target/manifest.json" "$SAMPLE_DIR" <<'PY' +import json, sys, os +manifest_path, sample_dir = sys.argv[1], sys.argv[2] +sample_dir = os.path.abspath(sample_dir) +parent_dir = os.path.dirname(sample_dir) +SENTINEL_ROOT = "{{SAMPLE_ROOT}}" +SENTINEL_PARENT = "{{SAMPLE_ROOT_PARENT}}" +FIXED_ISO = "2026-07-24T00:00:00Z" +FIXED_EPOCH = 1785000000.0 # 2026-07-24 near midnight UTC; matches FIXED_ISO closely enough +ZERO_UUID = "00000000-0000-0000-0000-000000000000" + +def replace_paths(v): + if isinstance(v, str): + if sample_dir in v or parent_dir in v: + return v.replace(sample_dir, SENTINEL_ROOT).replace(parent_dir, SENTINEL_PARENT) + return v + if isinstance(v, list): + return [replace_paths(x) for x in v] + if isinstance(v, dict): + return {k: replace_paths(x) for k, x in v.items()} + return v + +def scrub_created_at(v): + """Every node + macro carries a `created_at` epoch float. Walk the + tree and pin every one of them so regeneration doesn't rewrite ~500 + timestamps just because the wall clock moved.""" + if isinstance(v, dict): + if "created_at" in v and isinstance(v["created_at"], (int, float)): + v["created_at"] = FIXED_EPOCH + for value in v.values(): + scrub_created_at(value) + elif isinstance(v, list): + for item in v: + scrub_created_at(item) + +with open(manifest_path) as f: + obj = json.load(f) +obj = replace_paths(obj) +scrub_created_at(obj) +if isinstance(obj.get("metadata"), dict): + md = obj["metadata"] + # Identity — persistent UUIDs from the compiler's ~/.dbt/.user.yml + # and dbt-internal project fingerprint. Neither should ship. + md["user_id"] = ZERO_UUID + md["project_id"] = ZERO_UUID + md["invocation_id"] = ZERO_UUID + # Every wall-clock timestamp in metadata. + md["generated_at"] = FIXED_ISO + md["invocation_started_at"] = FIXED_ISO + md["run_started_at"] = FIXED_ISO + # Do not steer the installer's dbt toward "yes I accepted telemetry" — + # the compilation author's preference is not the installer's preference. + md["send_anonymous_usage_stats"] = False + # env can carry USER, PWD, HOME — strip it entirely. + md.pop("env", None) +with open(manifest_path, "w") as f: + json.dump(obj, f, indent=2, sort_keys=True) + f.write("\n") +PY + +# Force-include the committed manifest despite the .gitignore exclusion of +# target/ (the .gitignore has an explicit `!target/manifest.json` re-include). +git -C "$SAMPLE_DIR/../../.." add -f "$SAMPLE_DIR/target/manifest.json" + +echo "" +echo "Regenerated + sanitized $SAMPLE_DIR/target/manifest.json" +echo "Stage + commit alongside your source change." diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts index 109eafefec..cc46cf9216 100755 --- a/packages/opencode/script/publish.ts +++ b/packages/opencode/script/publish.ts @@ -78,6 +78,29 @@ async function copyAssets(targetDir: string) { if (fs.existsSync("../dbt-tools/dist/altimate_python_packages")) { await $`cp -r ../dbt-tools/dist/altimate_python_packages ${targetDir}/dbt-tools/dist/` } + // altimate_change start — ship the starter sample dbt project alongside + // the wrapper package. Runtime resolver in + // src/altimate/onboarding/sample-source-resolver.ts finds it here in + // production. Excludes target/ except the pre-compiled manifest.json + // (source of truth for /discover + /review on the shipped sample). + await $`mkdir -p ${targetDir}/sample-projects/jaffle-shop-duckdb/target` + // Keep this list in sync with MATERIALIZE_ENTRIES in + // packages/opencode/src/altimate/onboarding/materialize.ts — if publish + // omits a file the materializer expects, dev works but prod ships without + // it. `.gitignore` is intentionally shipped: the materialized sample + // becomes a working dbt project in the user's home and needs it to + // ignore compiled artifacts. + await $`cp -r ./sample-projects/jaffle-shop-duckdb/.gitignore \ + ./sample-projects/jaffle-shop-duckdb/README.md \ + ./sample-projects/jaffle-shop-duckdb/dbt_project.yml \ + ./sample-projects/jaffle-shop-duckdb/profiles.yml \ + ./sample-projects/jaffle-shop-duckdb/sample-manifest.json \ + ./sample-projects/jaffle-shop-duckdb/models \ + ./sample-projects/jaffle-shop-duckdb/seeds \ + ${targetDir}/sample-projects/jaffle-shop-duckdb/` + await $`cp ./sample-projects/jaffle-shop-duckdb/target/manifest.json \ + ${targetDir}/sample-projects/jaffle-shop-duckdb/target/manifest.json` + // altimate_change end await Bun.file(`${targetDir}/LICENSE`).write(await Bun.file("../../LICENSE").text()) await Bun.file(`${targetDir}/CHANGELOG.md`).write(await Bun.file("../../CHANGELOG.md").text()) } diff --git a/packages/opencode/src/altimate/api/client.ts b/packages/opencode/src/altimate/api/client.ts index e1a0f8fef4..85531e36b3 100644 --- a/packages/opencode/src/altimate/api/client.ts +++ b/packages/opencode/src/altimate/api/client.ts @@ -1,5 +1,6 @@ import z from "zod" import path from "path" +import { rm } from "node:fs/promises" import { Global } from "../../global" import { Filesystem } from "../../util/filesystem" @@ -134,8 +135,18 @@ export namespace AltimateApi { ) } + /** Remove the stored gateway credential file (sign out). No-op if absent. */ + export async function clearCredentials(): Promise { + await rm(credentialsPath(), { force: true }) + } + const VALID_TENANT_REGEX = /^[a-z_][a-z0-9_-]*$/ + /** True if `name` is a well-formed instance/tenant name. */ + export function isValidInstanceName(name: string): boolean { + return VALID_TENANT_REGEX.test(name) + } + /** Validates credentials against the Altimate API. * Mirrors AltimateSettingsHelper.validateSettings from altimate-mcp-engine. */ export async function validateCredentials(creds: { @@ -185,6 +196,35 @@ export namespace AltimateApi { } } + /** + * Exchange a short-lived social `login_token` for the user's gateway + * `auth_token` via POST {altimateUrl}/auth/social/exchange. The token is + * one-time and short-lived, which keeps the raw api_key out of the loopback + * callback URL. Throws on a non-ok response or a missing `auth_token`. + */ + export async function exchangeSocialToken(altimateUrl: string, instance: string, token: string): Promise { + const url = `${altimateUrl.replace(/\/+$/, "")}/auth/social/exchange` + // upstream_fix parity: bound the request so a network stall can't hang the callback. + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), 15_000) + const res = await fetch(url, { + method: "POST", + headers: { + "x-tenant": instance, + "Content-Type": "application/json", + }, + body: JSON.stringify({ token }), + signal: controller.signal, + }).finally(() => clearTimeout(timeout)) + if (!res.ok) { + const body = await res.text().catch(() => "") + throw new Error(`Social token exchange failed (${res.status} ${res.statusText}) ${body}`) + } + const data = (await res.json()) as { auth_token?: string } + if (!data.auth_token) throw new Error("Social token exchange did not return an auth_token") + return data.auth_token + } + async function request(creds: AltimateCredentials, method: string, endpoint: string, body?: unknown) { const url = `${creds.altimateUrl}${endpoint}` const res = await fetch(url, { @@ -278,8 +318,7 @@ export namespace AltimateApi { const allIntegrations = await listIntegrations() return integrationIds.map((id) => { const def = allIntegrations.find((i) => i.id === id) - const tools = - def?.tools?.flatMap((t) => (t.enable_all ?? [t.key]).map((k) => ({ key: k }))) ?? [] + const tools = def?.tools?.flatMap((t) => (t.enable_all ?? [t.key]).map((k) => ({ key: k }))) ?? [] return { id, tools } }) } diff --git a/packages/opencode/src/altimate/onboarding/marker.ts b/packages/opencode/src/altimate/onboarding/marker.ts new file mode 100644 index 0000000000..67cfca34af --- /dev/null +++ b/packages/opencode/src/altimate/onboarding/marker.ts @@ -0,0 +1,251 @@ +import { randomBytes } from "node:crypto" +import fs from "node:fs" +import path from "node:path" + +/** + * Marker file that identifies a directory as an altimate-code-materialized + * starter sample. Written into the sample dir on first materialize; + * consulted before any subsequent write to decide reuse / reset / suffix / + * refuse. + * + * The marker is authoritative for filesystem safety. + * + * A "looks like our sample" heuristic (files present, right names) would + * mis-classify a user's real dbt project that happens to have the same + * layout. A dedicated JSON marker with a required `kind` field avoids + * that entire failure mode. + */ + +export const MARKER_FILE_NAME = ".altimate-sample.json" +export const MARKER_KIND = "altimate-starter-sample" + +export interface SampleMarker { + /** Always the constant MARKER_KIND. Any other value (or missing key) means + * "not our sample" and blocks overwrite. */ + kind: typeof MARKER_KIND + /** Sample name (e.g. "jaffle-shop-duckdb"). Matches the source dir name. */ + sampleName: string + /** Version from the sample's own sample-manifest.json at write time. */ + version: string + /** ISO timestamp of materialization. */ + materializedAt: string + /** altimate-code CLI version that wrote this marker. */ + cliVersion: string +} + +/** Classification of a filesystem path as a candidate target for materializing + * the sample. Drives the conflict policy in materialize.ts. */ +export type TargetState = + | { kind: "empty" } + | { kind: "our-sample-current"; marker: SampleMarker; path: string } + | { kind: "our-sample-different-version"; marker: SampleMarker; path: string } + | { kind: "unknown-dir"; path: string; reason: string } + +/** Read `.altimate-sample.json` from a directory. Returns undefined if the + * file is missing, unreadable, malformed, or doesn't carry our `kind` tag. */ +export function readMarker(dir: string): SampleMarker | undefined { + const markerPath = path.join(dir, MARKER_FILE_NAME) + try { + const raw = fs.readFileSync(markerPath, "utf8") + const parsed = JSON.parse(raw) as unknown + if (!isSampleMarker(parsed)) return undefined + return parsed + } catch { + return undefined + } +} + +/** Write the marker atomically. Overwrites any existing marker in the dir. + * Caller MUST have already decided the dir is safe to write (via + * classifyTarget) — this function does not itself refuse. */ +export function writeMarker(dir: string, marker: SampleMarker): void { + const markerPath = path.join(dir, MARKER_FILE_NAME) + const tmpPath = `${markerPath}.tmp-${process.pid}` + fs.mkdirSync(dir, { recursive: true }) + fs.writeFileSync(tmpPath, JSON.stringify(marker, null, 2) + "\n") + fs.renameSync(tmpPath, markerPath) // atomic on POSIX +} + +/** Classify a candidate materialization target. Never throws. + * + * Decision table (documented here so the callers stay dumb): + * - No such dir → empty (safe to create + materialize) + * - Empty dir → empty (safe to materialize into) + * - Has our marker, + * sampleName differs → unknown-dir (marker is ours but belongs to + * a DIFFERENT sample — never reuse or upgrade + * through it; treat as a foreign directory) + * - Has our marker, + * sampleName matches, + * version matches → our-sample-current (reuse — nothing to do) + * - Has our marker, + * sampleName matches, + * version differs → our-sample-different-version (offer upgrade) + * - Non-empty dir, + * no marker (or bad kind) → unknown-dir (NEVER overwrite; caller must + * suffix `-2`, `-3` etc. or refuse) + * + * `expectedSampleName` gates the marker's `sampleName` field so an existing + * sample-A marker never satisfies a sample-B request. Currently the shipped + * CLI only carries jaffle-shop-duckdb, so this branch is unreachable in + * practice — but a future second sample must not silently reuse an existing + * first-sample dir just because the version happens to match. + */ +export function classifyTarget( + dir: string, + expectedVersion: string, + expectedSampleName: string, +): TargetState { + // lstatSync (not statSync) so a symlinked target is classified as + // unknown-dir rather than by what it points at. If a user has + // ~/altimate-sample-dbt -> /somewhere-else, we must not "reuse" the + // symlink target through the link (that would place downstream operations + // outside the parent our containment check validated) and must not + // silently unlink the symlink when overwriting an "empty" directory. + let stat: fs.Stats | undefined + try { + stat = fs.lstatSync(dir) + } catch { + return { kind: "empty" } + } + if (stat.isSymbolicLink()) { + return { kind: "unknown-dir", path: dir, reason: "target is a symlink — refusing to follow" } + } + if (!stat.isDirectory()) { + return { kind: "unknown-dir", path: dir, reason: "target exists but is not a directory" } + } + let entries: fs.Dirent[] + try { + entries = fs.readdirSync(dir, { withFileTypes: true }) + } catch (err) { + return { + kind: "unknown-dir", + path: dir, + reason: `target unreadable: ${err instanceof Error ? err.message : String(err)}`, + } + } + if (entries.length === 0) return { kind: "empty" } + + const marker = readMarker(dir) + if (!marker) { + return { + kind: "unknown-dir", + path: dir, + reason: "directory not empty and has no altimate-code marker (would clobber unrelated content)", + } + } + // Gate on sampleName BEFORE version. A marker whose sampleName differs + // from what we're materializing is not "ours" for THIS request — even + // though it was written by an altimate-code CLI. Treat it as a foreign + // directory: never reuse-through it, never authorize an + // allowInPlaceUpgrade against it. Falls into the same suffix escalation + // path as an unrelated non-sample dir. + if (marker.sampleName !== expectedSampleName) { + return { + kind: "unknown-dir", + path: dir, + reason: `marker belongs to sample '${marker.sampleName}', not '${expectedSampleName}' — refusing to reuse or overwrite a different sample`, + } + } + if (marker.version === expectedVersion) { + return { kind: "our-sample-current", marker, path: dir } + } + return { kind: "our-sample-different-version", marker, path: dir } +} + +/** Verify the parent dir is writable BEFORE we start hunting candidates — + * gives the caller a specific "target parent unwritable" error instead of + * a raw `EACCES` from `mkdirSync` deep in the materialize step. + * Returns undefined when writable; a message string when not. */ +export function checkParentWritable(parentDir: string): string | undefined { + try { + fs.accessSync(parentDir, fs.constants.W_OK) + return undefined + } catch (err) { + return `Target parent directory ${parentDir} is not writable: ${err instanceof Error ? err.message : String(err)}` + } +} + +/** Given a base directory (parent) and preferred name, find the first + * candidate path that isn't blocked by unknown-dir content. + * + * Attempts, in order: + * 1. `//` + * 2. `/-2/`, `-3/`, …, up to `attemptLimit` + * 3. If ALL of the numbered slots are held by unrelated content, one + * final randomized fallback `-<6-hex-chars>/` — this + * keeps activation working for the pathological case where a user + * has 100 unrelated altimate-sample-dbt-N/ dirs (retries, support + * copies, benchmark runs). Better a weird name than a hard failure. + * 4. If even the randomized slot collides (statistical near-impossible + * given 16.7M random values), THEN throw. + * + * Bumped attemptLimit from 10 → 100 after cubic feedback that 10 is easy + * to blow past in real environments. + */ +/** After this many consecutive UNRELATED-content hits, findSafeTarget stops + * scanning numbered slots and jumps straight to the randomized fallback. + * A user with 5+ contiguous unknown dirs under their preferred name is in + * a genuinely crowded parent; scanning the rest of the 100 numbered slots + * would burn ~95 unnecessary stat syscalls to arrive at the same answer. + * Kept generous enough to survive the common "installer created 2-3 + * numbered copies during retries" pattern without escalating to hex. */ +const CONSECUTIVE_UNKNOWN_LIMIT = 10 + +export function findSafeTarget( + parentDir: string, + preferredName: string, + expectedVersion: string, + expectedSampleName: string, + attemptLimit: number = 100, + opts: { + /** When true, treat `our-sample-different-version` the same as `unknown-dir` + * during slot scanning — skip it and try the next slot. Used by the + * install-alongside upgrade flow so a user with slot 0 holding an + * older-version sample can materialize the new version into slot 1 + * (`-2`) without touching the old one. Without this option, + * findSafeTarget stops at a version-mismatched slot 0 and returns — + * which is the right default for reuse detection, but blocks the + * "install the new version alongside" UX. */ + skipVersionMismatch?: boolean + } = {}, +): { path: string; state: TargetState; suffix: number | string } { + const skippable = (kind: TargetState["kind"]): boolean => + kind === "unknown-dir" || (Boolean(opts.skipVersionMismatch) && kind === "our-sample-different-version") + let consecutiveSkipped = 0 + for (let i = 0; i < attemptLimit; i++) { + const name = i === 0 ? preferredName : `${preferredName}-${i + 1}` + const candidate = path.join(parentDir, name) + const state = classifyTarget(candidate, expectedVersion, expectedSampleName) + if (!skippable(state.kind)) return { path: candidate, state, suffix: i } + consecutiveSkipped++ + // The parent has enough unrelated content that continuing the numeric + // scan is unlikely to find a free slot. Skip to the hex fallback which + // has a ~16.7M-value collision space and will resolve in one syscall. + if (consecutiveSkipped >= CONSECUTIVE_UNKNOWN_LIMIT) break + } + // Randomized fallback — 6 hex chars is ~16.7M values; if it collides we + // give up (the environment is genuinely hostile). + const randomTag = randomBytes(3).toString("hex") + const randomName = `${preferredName}-${randomTag}` + const randomCandidate = path.join(parentDir, randomName) + const state = classifyTarget(randomCandidate, expectedVersion, expectedSampleName) + if (!skippable(state.kind)) { + return { path: randomCandidate, state, suffix: randomTag } + } + throw new Error( + `No safe target found under ${parentDir} — numbered candidates AND a randomized fallback ${randomName} all held unrelated content`, + ) +} + +function isSampleMarker(v: unknown): v is SampleMarker { + if (v === null || typeof v !== "object") return false + const obj = v as Record + return ( + obj["kind"] === MARKER_KIND && + typeof obj["sampleName"] === "string" && + typeof obj["version"] === "string" && + typeof obj["materializedAt"] === "string" && + typeof obj["cliVersion"] === "string" + ) +} diff --git a/packages/opencode/src/altimate/onboarding/materialize.ts b/packages/opencode/src/altimate/onboarding/materialize.ts new file mode 100644 index 0000000000..7401c069ff --- /dev/null +++ b/packages/opencode/src/altimate/onboarding/materialize.ts @@ -0,0 +1,476 @@ +import { randomBytes } from "node:crypto" +import fs from "node:fs" +import os from "node:os" +import path from "node:path" +import { Flock } from "@opencode-ai/core/util/flock" +import { MARKER_KIND, checkParentWritable, classifyTarget, findSafeTarget, writeMarker, type TargetState } from "./marker" +import { DEFAULT_SAMPLE_NAME, loadShippedManifest, resolveSampleSource, type SampleSourceLocation } from "./sample-source-resolver" + +/** + * Materialize the shipped starter sample onto the user's filesystem. + * + * The user-facing contract: + * - Default target: `~/altimate-sample-dbt/` (visible, user can `cd` in). + * - If the target already holds our sample at the same version → reuse + * the existing dir (no rewrite). + * - If the target holds our sample at a different version → reset in + * place (the caller has already confirmed via UX). + * - If the target holds anything unrelated → find `-2/`, `-3/`, + * … and materialize there. Never overwrite unknown content. + * - If HOME resolves to a suspicious location (npm sudo `/root`, an + * ephemeral `/tmp/*` runner, a container's `/`) → refuse with an + * actionable error. The user probably didn't mean to write there. + * + * The copy is done through a whitelist of files that the shipped sample + * source is known to contain, rather than a wholesale recursive copy — + * this keeps sample-projects/ contributor edits from accidentally shipping + * developer scratch files (partial_parse.msgpack, .venv, .DS_Store, …) to + * end users. + */ + +/** Files/dirs relative to the sample source that get materialized to the + * user's target dir. Explicitly enumerated (no glob) so future changes + * to the sample layout are a deliberate opt-in edit here. */ +const MATERIALIZE_ENTRIES: ReadonlyArray<{ from: string; kind: "file" | "dir"; required: boolean }> = [ + { from: "README.md", kind: "file", required: true }, + { from: "dbt_project.yml", kind: "file", required: true }, + { from: "profiles.yml", kind: "file", required: true }, + { from: "sample-manifest.json", kind: "file", required: true }, + // .gitignore is the ONLY optional entry — dev checkouts may lack it and + // materialize should not fail. Every other entry is load-bearing for + // /discover, /review, or the "Build & query it" flow; missing any of + // them means a broken package and we must fail loudly rather than mark + // the incomplete copy as reused-forever. + { from: ".gitignore", kind: "file", required: false }, + { from: "models", kind: "dir", required: true }, + { from: "seeds", kind: "dir", required: true }, + { from: "target/manifest.json", kind: "file", required: true }, +] + +export interface MaterializeOptions { + /** Sample-source lookup name (defaults to jaffle-shop-duckdb). */ + sampleName?: string + /** Preferred target directory NAME (not full path). Suffixed to + * `/` unless already taken. */ + preferredTargetName?: string + /** Parent directory that the target lands in. Defaults to `os.homedir()` + * after passing the safety guard (see rejectUnsafeHome). */ + targetParent?: string + /** altimate-code version, written into the marker. */ + cliVersion: string + /** Sample version, written into the marker. Should mirror the value in + * sample-manifest.json (materialize.ts DOES NOT read it — the caller + * is expected to pass the same version it stamped into publish). */ + sampleVersion: string + /** If true and the classifier returned `our-sample-different-version`, + * overwrite in place. If false and versions differ, the caller gets a + * MaterializeResult with `reused: false` + a hint to prompt the user. */ + allowInPlaceUpgrade?: boolean + /** If true, the finder skips a version-mismatched slot 0 and materializes + * into slot 1 (`-2`), leaving the older version in place. + * This is the "install alongside" upgrade path — the user gets the new + * version to try without losing their old copy or its edits. Overrides + * `allowInPlaceUpgrade` when both are set (alongside is less destructive). */ + installAlongside?: boolean + /** Escape hatch for internal callers (e.g. tests) that need to materialize + * under a path `rejectUnsafeHome` would ordinarily block (`/tmp/*`). + * NEVER exposed on the LLM-facing tool schema — a prompt-injected model + * turn must not be able to escape the safe-parent check by setting a + * flag. Leave false unless you have a specific reason. */ + allowUnsafeParent?: boolean + /** Optionally pass an ALREADY-RESOLVED sample source to skip the internal + * `resolveSampleSource` sweep. sample_setup.ts resolves once at the top + * of `execute()` so the manifest read and the materialize copy share the + * same source; if you pass this, materializeSample uses it verbatim + * instead of running the candidate hunt a second time. */ + preResolvedSource?: SampleSourceLocation +} + +export interface MaterializeResult { + /** Absolute path where the sample ended up. */ + targetPath: string + /** true when the target already held our sample at the requested version + * (no write was performed except possibly a marker-timestamp refresh). */ + reused: boolean + /** Which slot was used: 0 for the preferred name, N for `-`, + * a hex string for the randomized fallback slot. */ + suffix: number | string + /** Debug-worthy note about the state classification at write time. */ + note: string +} + +/** + * Refuse to write into a `HOME` that's almost certainly wrong. + * Concrete cases: + * - `/root` — someone ran `sudo npm install -g` and their shell isn't + * actually root; the sample would land in root's home and be invisible. + * - `/tmp/*` — ephemeral runners; user won't find it later. + * - `/` — misconfigured containers. + * - `os.tmpdir()` and its subdirs — macOS `/var/folders/…`, Windows + * `%TEMP%`; same "gone on reboot" issue as `/tmp` but the paths look + * like real homes at a glance. + * - Windows system dirs — `%SYSTEMROOT%`, `%PROGRAMFILES%`. + * - unset — leave to the caller. + */ +export function rejectUnsafeHome(home: string | undefined): string | undefined { + if (!home) return "HOME environment variable is not set" + if (home === "/" || home === "") return `HOME='${home}' is not a usable directory` + if (home === "/root" && process.getuid?.() !== 0) { + return "HOME=/root but this process is not running as root (likely `sudo npm install -g` — the sample would materialize into root's home and be invisible from your normal shell). Re-run without sudo, or pass an explicit `--target-parent`." + } + // Canonicalize BOTH sides of the tmp comparison before matching — + // otherwise a caller who passes `/private/tmp/foo` on macOS bypasses + // the `/tmp/*` check (because `/tmp` is a symlink to `/private/tmp`, + // so the raw string comparison sees no shared prefix) and the + // `os.tmpdir()` check (macOS reports `/var/folders/…` while realpath + // of the same tmpdir returns `/private/var/folders/…`). A codex sweep + // of this class flagged the raw comparison as a bypass. realpathSync + // fails on nonexistent paths — fall back to `path.resolve` so the + // check still handles relative parents like `./tmp`. + const canonicalize = (p: string): string => { + try { return fs.realpathSync(p) } catch { return path.resolve(p) } + } + const canonicalHome = canonicalize(home) + // Hoist references OUT of check() — realpathSync is a syscall on each + // call, and check() runs up to twice per invocation (once against the + // canonical home, once against the raw home). Neither ref depends on + // the candidate, so compute them once here. Some references (like the + // string literal "/tmp") can't always be realpathed as a directory, + // so we include them raw; the canonical versions catch the + // /private/tmp and /private/var/folders/... bypasses. + const refs: string[] = ["/tmp", canonicalize("/tmp"), os.tmpdir(), canonicalize(os.tmpdir())] + const check = (candidate: string): string | undefined => { + for (const ref of refs) { + if (!ref) continue + if (candidate === ref || candidate.startsWith(ref + path.sep)) { + return `HOME='${home}' resolves to '${candidate}' which is under an ephemeral system tmp path (${ref}) — the sample would be hard to find or swept by tmp-cleanup jobs. Pass an explicit --target-parent to override.` + } + } + return undefined + } + // Check canonical form first (catches the bypasses above); then the + // raw literal for a redundant check against the caller's input. + const canonicalReject = check(canonicalHome) + if (canonicalReject) return canonicalReject + if (canonicalHome !== home) { + const rawReject = check(home) + if (rawReject) return rawReject + } + // Windows-specific system paths. Cheap conservative checks — we're not + // trying to enumerate every dangerous Windows dir, just the two an + // installer would most obviously misconfigure to. + if (process.platform === "win32") { + const systemRoot = process.env["SYSTEMROOT"] || process.env["WINDIR"] // e.g. C:\Windows + if (systemRoot) { + const normHome = home.toLowerCase() + const normSys = systemRoot.toLowerCase() + if (normHome === normSys || normHome.startsWith(normSys + path.sep)) { + return `HOME='${home}' is under the Windows system directory (${systemRoot}) — refusing to write there. Set HOME to a real user profile before materializing.` + } + } + const programFiles = process.env["PROGRAMFILES"] + if (programFiles) { + const normHome = home.toLowerCase() + const normPF = programFiles.toLowerCase() + if (normHome === normPF || normHome.startsWith(normPF + path.sep)) { + return `HOME='${home}' is under Program Files (${programFiles}) — refusing to write there. Set HOME to a real user profile before materializing.` + } + } + } + return undefined +} + +/** + * Directory names permitted for `preferredTargetName`. Deliberately restrictive: + * one path segment, no traversal characters, no leading dot (which would create + * a hidden dir the user might not notice). + * + * Path traversal guard: the LLM-facing sample_setup tool exposes + * `preferredTargetName` as a caller-controlled string. Without a strict + * allowlist a caller (or a prompt-injected model turn) could pass + * `preferredTargetName: "../somewhere"` and escape `targetParent`. The + * secondary containment check in `materializeSample` catches anything the + * regex misses. + */ +const SAFE_TARGET_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/ + +export async function materializeSample(opts: MaterializeOptions): Promise { + const sampleName = opts.sampleName ?? DEFAULT_SAMPLE_NAME + const preferredName = opts.preferredTargetName ?? "altimate-sample-dbt" + + // Fail loudly on caller-supplied names that could escape `targetParent` or + // create surprising layouts (hidden dirs, absolute paths, `..` segments). + if (!SAFE_TARGET_NAME_RE.test(preferredName)) { + throw new Error( + `preferredTargetName '${preferredName}' is not a plain directory name. ` + + `Expected letters, digits, dot, dash, underscore only; no path separators or leading dot.`, + ) + } + + const targetParent = opts.targetParent ?? os.homedir() + // rejectUnsafeHome applies to ANY targetParent, not just the defaulted-from-HOME + // one. A prompt-injected model turn (or a misconfigured caller) that passes + // targetParent: "/root" or "/tmp/x" must be refused; the guard exists to + // prevent materializing sample projects somewhere the user won't find them + // (or would be surprised by), and the risk is orthogonal to whether HOME + // itself is safe. If the caller genuinely wants to install under an unsafe + // path they can pass `allowUnsafeParent: true` — that's an internal option + // never exposed on the LLM-facing tool schema. + if (!opts.allowUnsafeParent) { + const parentReject = rejectUnsafeHome(targetParent) + if (parentReject) { + throw new Error(parentReject) + } + } + + // Fail loudly on unwritable parents (read-only home, NFS glitch, container + // mount) BEFORE we start hunting candidate names — gives the caller a + // clean error instead of a raw EACCES from mkdirSync mid-copy. + const writableError = checkParentWritable(targetParent) + if (writableError) { + throw new Error(writableError) + } + + const source = opts.preResolvedSource ?? resolveSampleSource(sampleName) + if (!source) { + throw new Error( + `Could not locate the starter sample source ('${sampleName}'). This usually means the CLI was installed without its wrapper package assets. Reinstall with: npm i -g @altimateai/altimate-code@latest`, + ) + } + + // Everything below — slot classification, staging copy, marker write, and + // atomic rename — happens under a Flock keyed on the parent + preferred + // name. Concurrent materialize() calls with the same preferredName under + // the same parent are serialized; different names or different parents + // never block each other. Without this lock, two callers could both see + // slot 0 as empty (or as our-sample-different-version + allowInPlaceUpgrade) + // and race, with the second's rename either failing or silently clobbering + // the first's completed materialize. + const lockKey = `altimate-onboarding:materialize:${path.resolve(targetParent)}:${preferredName}` + return await Flock.withLock(lockKey, async () => { + return await materializeUnderLock(opts, sampleName, preferredName, targetParent, source.path) + }) +} + +async function materializeUnderLock( + opts: MaterializeOptions, + sampleName: string, + preferredName: string, + targetParent: string, + sourcePath: string, +): Promise { + const { path: targetPath, state, suffix } = findSafeTarget( + targetParent, + preferredName, + opts.sampleVersion, + sampleName, + 100, + { skipVersionMismatch: opts.installAlongside === true }, + ) + + // Belt-and-suspenders containment check. The name-regex above should already + // guarantee this, but findSafeTarget also joins a numeric/hex suffix and any + // future edit to that logic must not sneak the target outside targetParent. + const resolvedParent = path.resolve(targetParent) + const resolvedTarget = path.resolve(targetPath) + if (resolvedTarget !== resolvedParent && !resolvedTarget.startsWith(resolvedParent + path.sep)) { + throw new Error( + `refusing to materialize outside targetParent: resolved '${resolvedTarget}' is not under '${resolvedParent}'`, + ) + } + + // If we found our sample at the requested version, we're done — no + // write. The user's existing edits (SQL tweaks, seed additions) are + // preserved intact. + if (state.kind === "our-sample-current") { + return { + targetPath, + reused: true, + suffix, + note: `reused ${targetPath} (marker version ${state.marker.version} matches)`, + } + } + + // Different version. Only overwrite if the caller opted in. + if (state.kind === "our-sample-different-version" && !opts.allowInPlaceUpgrade) { + return { + targetPath, + reused: true, + suffix, + note: `found existing sample at ${targetPath} version ${state.marker.version}, but current version is ${opts.sampleVersion}. Caller must prompt user before allowInPlaceUpgrade=true.`, + } + } + + // Atomic materialize: copy to a staging dir, write the marker there, then + // rename to the final target. If the process dies mid-copy the user is left + // with a `..tmp-` orphan (harmless — different name; swept below + // with an age guard so we never nuke a live sibling) instead of a + // partially-written targetPath that would look "unknown" to findSafeTarget + // on the next run and get shunted into `-2` while the original stays + // broken forever. + const stagingName = `.${preferredName}.tmp-${randomBytes(6).toString("hex")}` + const stagingPath = path.join(targetParent, stagingName) + // Best-effort cleanup of any prior tmp dirs older than ORPHAN_AGE_MS — + // Flock serializes same-preferredName runs, but a killed run with the + // same name could still have left a stale staging dir. We refuse to touch + // recently-mtimed entries defensively (in case a caller invoked + // materializeSample from a different process without going through the + // usual lock path). + sweepOrphanStaging(targetParent, preferredName) + try { + // Write to staging, but bake the FINAL target path into path-carrying + // files (target/manifest.json rehydration) — the atomic rename will + // land the tree at `targetPath`, so any path field written now with + // the staging path would be stale after the rename. + copySampleTree(sourcePath, stagingPath, targetPath) + writeMarker(stagingPath, { + kind: MARKER_KIND, + sampleName, + version: opts.sampleVersion, + materializedAt: new Date().toISOString(), + cliVersion: opts.cliVersion, + }) + + // Overwrite path: if there's an existing dir at targetPath, RE-VERIFY + // it still looks like the same version-mismatched sample we saw at + // findSafeTarget time before removing it. Even under the lock, another + // process could have written to that path between findSafeTarget and + // now (a manual `mv`, a second altimate-code install writing without + // the lock, etc.); a stale classification must not authorize a + // destructive remove. + if (fs.existsSync(targetPath)) { + const nowState = classifyTarget(targetPath, opts.sampleVersion, sampleName) + const stillEligible = + (state.kind === "our-sample-different-version" && nowState.kind === "our-sample-different-version") || + (state.kind === "empty" && nowState.kind === "empty") + if (!stillEligible) { + throw new Error( + `targetPath ${targetPath} was classified as ${state.kind} but is now ${nowState.kind} — refusing to remove. Rerun materializeSample to re-select a safe target.`, + ) + } + fs.rmSync(targetPath, { recursive: true, force: true }) + } + fs.renameSync(stagingPath, targetPath) + } catch (err) { + // Leave the staging dir on error so a debug pass can inspect it, but do + // not surface a raw ENOENT/EACCES to the caller — repackage. + throw new Error( + `materialize failed for ${targetPath} (staging left at ${stagingPath}): ${err instanceof Error ? err.message : String(err)}`, + ) + } + + return { + targetPath, + reused: false, + suffix, + note: buildNote(state, targetPath, suffix), + } +} + +/** + * Delete `..tmp-*` staging directories left over from a + * killed materialize — but ONLY those older than `ORPHAN_MAX_AGE_MS`. The + * age gate matters: a stale-classification-based sweep could otherwise + * delete a live staging dir a concurrent process (running outside our + * Flock, e.g. a different CLI version, or a script that bypassed the tool + * boundary) is still writing into. + * + * Best-effort — swallow errors so a permission-denied on one orphan + * doesn't block a fresh materialize. + */ +const ORPHAN_MAX_AGE_MS = 60 * 60 * 1000 // 1 hour — well past any real materialize wall-time + +function sweepOrphanStaging(targetParent: string, preferredName: string): void { + const prefix = `.${preferredName}.tmp-` + let entries: string[] + try { + entries = fs.readdirSync(targetParent) + } catch { + return + } + const now = Date.now() + for (const entry of entries) { + if (!entry.startsWith(prefix)) continue + const entryPath = path.join(targetParent, entry) + try { + // lstatSync (not statSync) so a symlinked entry has ITS OWN age + // checked, not the age of whatever it points at. If a user drops + // `..tmp-abc123 -> /some/frequently-touched/dir` into the + // parent, statSync would follow the link and return the target's + // mtime; the age guard would pass on a symlink that's actually + // ancient and we'd rmSync it (Node removes the symlink itself, + // not the target, but the classification is still wrong). Skip + // symlinks entirely — a symlink can't be a stale staging tree we + // wrote, so we have no business garbage-collecting it. Same + // class as finding 21 / codex NEW-5. + const stat = fs.lstatSync(entryPath) + if (stat.isSymbolicLink()) continue + const ageMs = now - Math.max(stat.mtimeMs, stat.birthtimeMs || 0) + if (ageMs < ORPHAN_MAX_AGE_MS) continue // young — could be a live sibling + fs.rmSync(entryPath, { recursive: true, force: true }) + } catch { + // orphan we can't stat/remove — skip, don't fail the fresh materialize + } + } +} + +/** + * Copy the shipped sample tree from `source` into `writeTo`. `finalTarget` + * is where the tree will END UP after atomic-rename (writeTo is the + * staging dir; finalTarget is the user-visible path). The distinction + * only matters for files that bake absolute paths into their bytes — + * currently just `target/manifest.json`, which carries {{SAMPLE_ROOT}} + * sentinels that must be rehydrated to `finalTarget`, not `writeTo`, + * or the rename will invalidate every path the manifest embeds. + */ +function copySampleTree(source: string, writeTo: string, finalTarget: string): void { + fs.mkdirSync(writeTo, { recursive: true }) + for (const entry of MATERIALIZE_ENTRIES) { + const from = path.join(source, entry.from) + const to = path.join(writeTo, entry.from) + if (!fs.existsSync(from)) { + // Optional entries (currently just .gitignore) skip quietly. Required + // entries mean the shipped package is broken — surface that as an + // error before writing a marker that would falsely mark this + // incomplete copy as reused-forever on subsequent runs. + if (entry.required) { + throw new Error( + `Sample source at ${source} is missing required entry '${entry.from}' — ` + + `the shipped package is incomplete. Reinstall with: npm i -g @altimateai/altimate-code@latest`, + ) + } + continue + } + if (entry.kind === "dir") { + fs.cpSync(from, to, { recursive: true, force: true }) + } else if (entry.from === "target/manifest.json") { + // Special-case the pre-compiled dbt manifest: the shipped file carries + // {{SAMPLE_ROOT}} / {{SAMPLE_ROOT_PARENT}} sentinels in every path + // field (root_path, patch_path, original_file_path, …) so the same + // committed artifact works for every user regardless of where they + // materialize the sample. Copy-by-bytes would ship those sentinels + // as-is and /discover + /review would choke on paths starting with + // "{{SAMPLE_ROOT}}/models/staging/stg_customers.sql". Route through + // loadShippedManifest which rehydrates the sentinels — REHYDRATED + // TO finalTarget (not writeTo) because atomic-rename will move + // this file to finalTarget after we return. + const rehydrated = loadShippedManifest(source, finalTarget) + fs.mkdirSync(path.dirname(to), { recursive: true }) + fs.writeFileSync(to, JSON.stringify(rehydrated, null, 2) + "\n", "utf8") + } else { + fs.mkdirSync(path.dirname(to), { recursive: true }) + fs.copyFileSync(from, to) + } + } +} + +function buildNote(state: TargetState, target: string, suffix: number | string): string { + if (state.kind === "empty" && suffix === 0) return `fresh materialize into ${target}` + if (state.kind === "empty" && typeof suffix === "number" && suffix > 0) + return `fresh materialize into ${target} (preferred name was taken by unrelated content — used numeric suffix -${suffix + 1})` + if (state.kind === "empty" && typeof suffix === "string") + return `fresh materialize into ${target} (all numeric slots were taken — used randomized suffix)` + if (state.kind === "our-sample-different-version") + return `in-place upgrade of ${target} from version ${state.marker.version} to current` + return `materialized into ${target}` +} diff --git a/packages/opencode/src/altimate/onboarding/sample-source-resolver.ts b/packages/opencode/src/altimate/onboarding/sample-source-resolver.ts new file mode 100644 index 0000000000..f2eb9800fc --- /dev/null +++ b/packages/opencode/src/altimate/onboarding/sample-source-resolver.ts @@ -0,0 +1,175 @@ +import fs from "node:fs" +import path from "node:path" + +/** + * Locate the read-only source tree of a starter sample project. + * + * The sample is shipped inside the wrapper npm package (see + * `script/publish.ts::copyAssets` — it copies `sample-projects/` into the + * wrapper alongside `bin/` and `skills/`). At runtime the resolver hunts + * across the layouts we ship in: + * + * 1. `ALTIMATE_STARTER_SAMPLE_DIR` env override — used by tests and by + * users pointing at a hand-curated fork of the sample. + * 2. Production: the compiled bun single-file exe lives at + * `/bin/altimate-code`, so the sample source is at + * `/sample-projects//`. + * 3. `bun run src/index.ts` (dev) or `bun test` — resolves relative to + * this file's own dirname, walking up to `packages/opencode/` then + * into `sample-projects/`. + * 4. Some install layouts (npx cache, pnpm content-addressable store) put + * the exe two hops away from the wrapper root — try one more level up. + * + * Returns the absolute path to the sample source directory, or `undefined` + * if no candidate contained a `dbt_project.yml`. Callers should surface an + * actionable error rather than crash — the sample is a nice-to-have on + * every activation, but the CLI stays usable without it. + */ + +export const DEFAULT_SAMPLE_NAME = "jaffle-shop-duckdb" + +/** Sentinel in `target/manifest.json` that stands in for the materialized + * target path. Substituted at load time by the sample-project consumer. */ +export const SAMPLE_ROOT_SENTINEL = "{{SAMPLE_ROOT}}" + +/** Sentinel for the parent of the materialized target — a couple of dbt + * manifest metadata fields carry the parent. */ +export const SAMPLE_ROOT_PARENT_SENTINEL = "{{SAMPLE_ROOT_PARENT}}" + +export interface SampleSourceLocation { + /** Absolute path to the sample source directory. */ + path: string + /** Which candidate matched — surfaced in logs for debugging install-layout issues. */ + origin: + | "env" + | "wrapper-bin-dir" + | "wrapper-bin-parent" + | "dev-source-tree" + | "wrapper-bin-grandparent" +} + +export function resolveSampleSource( + name: string = DEFAULT_SAMPLE_NAME, +): SampleSourceLocation | undefined { + const envOverride = process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + if (envOverride) { + const candidate = path.join(envOverride, name) + if (hasSampleShape(candidate)) return { path: path.resolve(candidate), origin: "env" } + } + + // The wrapper's bin script (`packages/opencode/bin/altimate-code:40`) sets + // ALTIMATE_BIN_DIR to its own scriptDir before spawning the platform + // binary. Use it as the FIRST post-env candidate: it survives every install + // scenario the exec-path chain doesn't — Windows (postinstall.mjs exits + // early on win32, so the hardlink from wrapper/bin/.altimate-code back to + // the platform binary never happens), `--ignore-scripts` installs (common + // in CI / corporate registries; same hardlink never runs), and + // ALTIMATE_CODE_BIN_PATH overrides (the wrapper honors this at + // bin/altimate-code:72 and runs the binary from an arbitrary location that + // has no relationship to the wrapper package). + const binDir = process.env["ALTIMATE_BIN_DIR"] + + // `process.execPath` is often a symlink or shim under package managers + // (npm global `/usr/local/bin/altimate-code -> .../lib/node_modules/...`, + // Homebrew `bin/altimate-code -> ../libexec/bin/altimate-code`, pnpm .bin + // shims). Resolve it so the candidate hunt walks from the real binary + // location, not the shim's directory. + let realExec: string + try { + realExec = fs.realpathSync(process.execPath) + } catch { + realExec = process.execPath + } + const execDir = path.dirname(realExec) + const selfDir = import.meta.dirname ?? (typeof __dirname === "string" ? __dirname : "") + + const candidates: Array<{ path: string; origin: SampleSourceLocation["origin"] }> = [] + if (binDir) { + candidates.push({ + path: path.join(binDir, "..", "sample-projects", name), + origin: "wrapper-bin-dir", + }) + } + candidates.push( + { path: path.join(execDir, "..", "sample-projects", name), origin: "wrapper-bin-parent" }, + // Dev / test: /packages/opencode/src/altimate/onboarding/*.ts + // → 3 hops up to packages/opencode/, then into sample-projects/. + // (../onboarding → ../altimate → ../src → packages/opencode) + { + path: path.join(selfDir, "..", "..", "..", "sample-projects", name), + origin: "dev-source-tree", + }, + // Some layouts (pnpm content-addressable, custom Homebrew brews) put the + // exe two levels beneath the wrapper root. + { + path: path.join(execDir, "..", "..", "sample-projects", name), + origin: "wrapper-bin-grandparent", + }, + ) + + for (const c of candidates) { + if (hasSampleShape(c.path)) return { path: path.resolve(c.path), origin: c.origin } + } + return undefined +} + +function hasSampleShape(dir: string): boolean { + try { + return fs.existsSync(path.join(dir, "dbt_project.yml")) + } catch { + return false + } +} + +/** + * Load the shipped `target/manifest.json` from the sample source, rehydrating + * the SAMPLE_ROOT sentinels with the user's materialized target path. Static + * workflows (/discover, /review) read this without needing dbt installed on + * the user's machine. + * + * The rehydration parses JSON FIRST and walks the tree replacing sentinels + * only inside string values. A prior naive text-level replace would corrupt + * the manifest if the user's target path contained JSON-significant + * characters like `"` or `\` (concrete failure: `/tmp/a"b` produces invalid + * JSON; Windows `C:\Users\...` produces invalid escape sequences). + * + * Throws if the sample source is missing or the manifest is malformed — + * callers should catch and fall back to an actionable message. + */ +export function loadShippedManifest( + sampleSource: string, + materializedTarget: string, +): Record { + const manifestPath = path.join(sampleSource, "target", "manifest.json") + const raw = fs.readFileSync(manifestPath, "utf8") + const parsed = JSON.parse(raw) as unknown + const parentTarget = path.dirname(materializedTarget) + return rehydrateSentinels(parsed, materializedTarget, parentTarget) as Record +} + +/** + * Walk a parsed JSON tree and replace sentinel occurrences inside STRING + * values only. Object keys, numbers, booleans, and nulls are untouched. + * Exported for the freshness test to exercise the round-trip directly. + */ +export function rehydrateSentinels(value: unknown, sampleRoot: string, sampleRootParent: string): unknown { + if (typeof value === "string") { + if (value.indexOf(SAMPLE_ROOT_SENTINEL) === -1 && value.indexOf(SAMPLE_ROOT_PARENT_SENTINEL) === -1) { + return value + } + // Replace both sentinels, longest-first so SAMPLE_ROOT never matches + // inside a preceding SAMPLE_ROOT_PARENT hit. + return value.split(SAMPLE_ROOT_PARENT_SENTINEL).join(sampleRootParent).split(SAMPLE_ROOT_SENTINEL).join(sampleRoot) + } + if (Array.isArray(value)) { + return value.map((item) => rehydrateSentinels(item, sampleRoot, sampleRootParent)) + } + if (value !== null && typeof value === "object") { + const out: Record = {} + for (const [k, v] of Object.entries(value)) { + out[k] = rehydrateSentinels(v, sampleRoot, sampleRootParent) + } + return out + } + return value +} diff --git a/packages/opencode/src/altimate/onboarding/tool-detection.ts b/packages/opencode/src/altimate/onboarding/tool-detection.ts new file mode 100644 index 0000000000..ee71f8cc91 --- /dev/null +++ b/packages/opencode/src/altimate/onboarding/tool-detection.ts @@ -0,0 +1,117 @@ +import { execFile } from "node:child_process" + +/** + * Probe the user's machine for the toolchain the sample project needs to + * materialize + run its dbt models. + * + * The starter sample's "look-first" workflows (/discover, /review) work with + * ZERO external tools — they read the shipped pre-compiled manifest. The + * "run" workflows (dbt build, live queries) need `dbt` on PATH AND the + * `dbt-duckdb` adapter installed against the same Python. We probe both so + * the post-materialize UX can hide options that would silently fail. + * + * Detection is intentionally lightweight — we do NOT invoke `dbt debug` + * against the materialized sample here, because this runs BEFORE + * materialization (to decide which workflow entries to show in the first + * place). `dbt --version` output includes the plugin list on dbt 1.x, so + * scan for the "duckdb" plugin there rather than shelling out again. + * + * If probing fails for any reason (timeout, ENOENT, garbage output), we + * treat that as "tool not usable" — the caller falls back to look-only + * workflows. Never throws; always returns a defined result. + */ + +export interface DbtRuntime { + /** `dbt --version` succeeded (dbt-core is on PATH). */ + hasDbt: boolean + /** `dbt --version` output mentions duckdb — best effort at "the adapter is + * installed against the same Python that owns this `dbt`". */ + hasDbtDuckdb: boolean + /** Raw dbt-core version string (e.g. `1.11.8`), when present. Surfaced to + * the user in the "install a newer dbt-duckdb" prompt. */ + dbtCoreVersion?: string +} + +/** Cache the probe result for the process lifetime — dbt install state + * can't change while the CLI is running, and the probe adds a subprocess + * fork we don't want to pay on every activation-dialog render. + * + * Callers that need up-to-date state MUST pass `{ force: true }`: + * - AFTER materialization (a user might have just run + * `pip install dbt-duckdb` in another terminal and then picked + * "sample project" — cache says `hasDbtDuckdb=false` but reality + * changed since the dialog first rendered). + * - BEFORE actually running any dbt-dependent workflow — even a + * force-refreshed probe here is cheap compared to a subprocess run + * that would silently fail. + * Cached path is used for the activation-dialog first-render only. */ +let cached: Promise | undefined + +export function detectDbtRuntime(opts?: { force?: boolean }): Promise { + if (!cached || opts?.force) cached = probe() + return cached +} + +/** Test helper: forget the cached probe so successive `detectDbtRuntime` + * calls re-probe. Not exported to production code paths. */ +export function _resetDbtRuntimeCacheForTests() { + cached = undefined +} + +async function probe(): Promise { + // Node's `execFile("dbt")` on Windows uses CreateProcess, which honours + // PATHEXT for `.exe`/`.com` but NOT `.cmd`/`.bat` (those need a shell). + // Some Windows dbt install layouts (older `pip install --user`, certain + // corporate distributions, WSL-bridge shims) drop a `dbt.cmd` wrapper + // on PATH instead of `dbt.exe`. Without a fallback we'd tell those + // users "dbt: missing" on the template's Build & query it branch even + // when dbt is right there. + // + // Fix: on Windows, if the direct probe misses, retry through + // `cmd.exe /c dbt --version` — cmd's own resolver honours the full + // PATHEXT (including `.cmd`/`.bat`) and finds any of the wrapper + // shapes. Args are constant strings so there's no injection surface. + // On macOS/Linux we skip the retry — one shell-less probe is enough. + let out = await tryExec("dbt", ["--version"], 5_000) + if (!out.ok && process.platform === "win32") { + out = await tryExec("cmd.exe", ["/c", "dbt", "--version"], 5_000) + } + if (!out.ok) return { hasDbt: false, hasDbtDuckdb: false } + + // dbt --version on 1.x prints something like: + // Core: + // - installed: 1.11.8 + // - latest: 1.12.0 - Update available! + // Plugins: + // - duckdb: 1.11.4 - Update available! + // We look for the plugin line specifically ("- duckdb:") rather than any + // "duckdb" substring so the presence of the word inside an upgrade hint + // ("Try dbt-duckdb...") doesn't false-positive. + const combined = `${out.stdout}\n${out.stderr}` + const hasDbtDuckdb = /^\s*-\s*duckdb:/m.test(combined) + + const versionMatch = combined.match(/-\s*installed:\s*([0-9]+\.[0-9]+\.[0-9]+)/) + const dbtCoreVersion = versionMatch?.[1] + + return { hasDbt: true, hasDbtDuckdb, dbtCoreVersion } +} + +interface ExecResult { + ok: boolean + stdout: string + stderr: string +} + +function tryExec(cmd: string, args: string[], timeoutMs: number): Promise { + return new Promise((resolve) => { + execFile(cmd, args, { timeout: timeoutMs }, (error, stdout, stderr) => { + if (error) { + // ENOENT = not on PATH; timeout, non-zero exit, other errors all + // resolve as "not usable". Never rejects. + resolve({ ok: false, stdout: stdout || "", stderr: stderr || String(error) }) + return + } + resolve({ ok: true, stdout: stdout || "", stderr: stderr || "" }) + }) + }) +} diff --git a/packages/opencode/src/altimate/plugin/altimate.ts b/packages/opencode/src/altimate/plugin/altimate.ts index 510f0e1de7..7052e2fe65 100644 --- a/packages/opencode/src/altimate/plugin/altimate.ts +++ b/packages/opencode/src/altimate/plugin/altimate.ts @@ -1,4 +1,178 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" +import { createServer } from "http" +import { randomBytes } from "crypto" +import open from "open" +import { AltimateApi } from "../api/client" +// altimate_change — onboarding telemetry for the gateway sign-in funnel +import * as OnboardingTelemetry from "../telemetry/onboarding" + +/** + * Why a failure reason is attached at the rejection site rather than inferred from the message: + * `gateway_auth_failed.reason` is a closed enum, and the callback's catch sees only an Error. + * Matching on message text would silently drift the moment any of these strings is reworded, and + * the `error` query param is attacker-influenced text we must not parse or forward. Tagging the + * error where the cause is known keeps the classification deterministic — and note that an + * unknown/invalid `state` never rejects a pending flow at all (the handler 400s without touching + * the map), so a CSRF mismatch legitimately surfaces later as `timeout`, not `denied`. + */ +type GatewayFailureReason = "timeout" | "denied" | "error" + +function markReason(err: Error, reason: GatewayFailureReason): Error { + return Object.assign(err, { altimateGatewayReason: reason }) +} + +function reasonOf(err: unknown): GatewayFailureReason { + const tagged = (err as { altimateGatewayReason?: GatewayFailureReason } | undefined)?.altimateGatewayReason + return tagged ?? "error" +} + +// Loopback port the CLI listens on for the browser to deliver the gateway +// credential after sign-in. Must match the redirect the web authorize page posts +// back to. 7317 is otherwise unused in this codebase. +const CALLBACK_PORT = 7317 + +// Web app that hosts the signup/login (authorize) page. Overridable for +// dev/staging via ALTIMATE_WEB_URL. +const DEFAULT_WEB_URL = "https://app.myaltimate.com" +// Fallback gateway API base if the callback omits one. +const DEFAULT_API_URL = "https://api.myaltimate.com" + +// Escape reflected values before interpolating them into the callback HTML — the +// error text originates from the URL query string, so it must not be trusted. +function escapeHtml(s: string): string { + return s.replace( + /[&<>"']/g, + (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c] as string, + ) +} + +// Neutral copy: the loopback returns this as soon as it RECEIVES the token, before +// the CLI has exchanged/persisted it — so don't claim "Signed in" (the terminal is +// the source of truth for actual success/failure). +const HTML_SUCCESS = `Altimate Code + +

Authorization received

Return to your terminal to finish connecting.

+` + +const HTML_ERROR = (msg: string) => `Altimate Code + +

Connection failed

${escapeHtml(msg)}

Please return to your terminal and try again.

` + +interface CallbackResult { + api_url: string + instance: string + // Short-lived, one-time login_token delivered by the browser. Exchanged for + // the gateway auth_token in callback() — the raw api_key never rides in the URL. + token: string +} + +interface Pending { + resolve: (creds: CallbackResult) => void + reject: (err: Error) => void +} + +let server: ReturnType | undefined +// Pending flows keyed by the unguessable `state`. Registered synchronously in +// authorize() BEFORE the browser opens, so an instant redirect (an already +// signed-in user) is matched instead of dropped; keying by state also lets two +// concurrent /auth flows coexist without clobbering each other. +const pending = new Map() + +async function startCallbackServer(): Promise { + if (server) return + server = createServer((req, res) => { + const url = new URL(req.url || "/", `http://localhost:${CALLBACK_PORT}`) + if (url.pathname !== "/callback") { + res.writeHead(404) + res.end("Not found") + return + } + + const html = (status: number, body: string) => { + res.writeHead(status, { "Content-Type": "text/html" }) + res.end(body) + } + + // Validate `state` FIRST — before honoring `error` — so a request without a + // known state can neither cancel an in-progress flow nor deliver anything. + const state = url.searchParams.get("state") + const entry = state ? pending.get(state) : undefined + if (!state || !entry) { + html(400, HTML_ERROR("Invalid or unknown sign-in state")) + return + } + pending.delete(state) + + const error = url.searchParams.get("error") + if (error) { + // altimate_change — the browser reported an explicit failure: the only true `denied` signal + entry.reject(markReason(new Error(error), "denied")) + html(200, HTML_ERROR(error)) + return + } + + const token = url.searchParams.get("token") + const instance = url.searchParams.get("instance") + const apiUrl = url.searchParams.get("url") || DEFAULT_API_URL + if (!token || !instance) { + const msg = "Missing credential in callback" + entry.reject(new Error(msg)) + html(400, HTML_ERROR(msg)) + return + } + + entry.resolve({ api_url: apiUrl, instance, token }) + html(200, HTML_SUCCESS) + }) + + try { + await new Promise((resolve, reject) => { + // Bind to loopback only — the credential/abort endpoints must not be reachable + // from the LAN. + server!.listen(CALLBACK_PORT, "127.0.0.1", () => resolve()) + server!.on("error", reject) + }) + } catch (err) { + // Reset so a retry isn't blocked by the `if (server) return` guard, and surface + // a clear reason (e.g. the port is already taken) instead of hanging. + server = undefined + const code = (err as NodeJS.ErrnoException)?.code + throw new Error( + code === "EADDRINUSE" + ? `Port ${CALLBACK_PORT} is already in use — close whatever is using it and try again.` + : `Could not start the sign-in server: ${err instanceof Error ? err.message : String(err)}`, + ) + } +} + +function stopCallbackServer() { + if (server) { + server.close() + server = undefined + } +} + +// Register a pending flow keyed by `state` and return its promise. Called +// synchronously in authorize() before the browser opens; the server handler +// resolves/rejects it by state, so a fast redirect is never lost. +function registerPending(state: string, timeoutMs = 5 * 60 * 1000): Promise { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + // altimate_change — tag as `timeout` for gateway_auth_failed classification + if (pending.delete(state)) reject(markReason(new Error("Timed out waiting for browser sign-in"), "timeout")) + }, timeoutMs) + pending.set(state, { + resolve: (creds) => { + clearTimeout(timeout) + resolve(creds) + }, + reject: (err) => { + clearTimeout(timeout) + reject(err) + }, + }) + }) +} export async function AltimateAuthPlugin(_input: PluginInput): Promise { return { @@ -6,8 +180,121 @@ export async function AltimateAuthPlugin(_input: PluginInput): Promise { provider: "altimate-backend", methods: [ { + type: "oauth", + label: "Altimate LLM Gateway", + async authorize() { + const state = randomBytes(16).toString("hex") + // altimate_change start — the attempt starts here, before the callback server and the + // browser open. startCallbackServer() throws when port 7317 is taken, which is a real + // and reasonably common gateway-auth failure that happens before any callback object + // exists — without this catch it would never appear in the funnel. + const startedAt = Date.now() + try { + await startCallbackServer() + } catch (err) { + void OnboardingTelemetry.emit({ type: "gateway_auth_failed", reason: "error" }) + throw err + } + // altimate_change end + // Register the pending flow BEFORE opening the browser so an instant + // redirect can be matched by state rather than dropped as CSRF. + const result = registerPending(state) + // If callback() is never awaited (e.g. the dialog is dismissed before it + // runs), the pending promise still rejects on timeout — swallow that here + // so it can't surface as an unhandled rejection. callback() awaits the + // same promise independently. + void result.catch(() => {}) + + const webUrl = (process.env.ALTIMATE_WEB_URL || DEFAULT_WEB_URL).replace(/\/+$/, "") + // Use 127.0.0.1 to match the loopback bind — a plain `localhost` redirect + // can resolve to ::1 first and hit a closed IPv6 port. + const redirect = `http://127.0.0.1:${CALLBACK_PORT}/callback` + // Land on the sign-up page and let the user choose how to authenticate + // (Google today, more providers later) rather than forcing Google. + const authorizeUrl = + `${webUrl}/register?client=altimate-code` + + `&redirect=${encodeURIComponent(redirect)}` + + `&state=${state}` + + await open(authorizeUrl).catch(() => undefined) + + // altimate_change start — onboarding funnel: gateway sign-in started. + // Spec name is `gateway_device_code_issued`; this flow is a browser loopback OAuth + // with no device code, so the event means "authorize URL built, browser open + // attempted". open() failures are swallowed above (the URL is also printed for the + // user to paste), so this fires even when no browser actually launched. + // The URL is never sent — it carries the CSRF `state`. + void OnboardingTelemetry.emit({ type: "gateway_device_code_issued" }) + + // One outcome per attempt. callback() closes over `result` and re-runs its whole body + // on every invocation, so a repeated call would otherwise re-emit completion/failure + // (and re-report a connect time measured from the original attempt). + let outcomeReported = false + // altimate_change end + + return { + url: authorizeUrl, + instructions: "Complete sign-in in your browser to connect Altimate LLM Gateway.", + method: "auto", + async callback() { + try { + const creds = await result + // The instance name comes from the browser callback; validate it + // before persisting (the manual paste path validates too). + if (!AltimateApi.isValidInstanceName(creds.instance)) { + throw new Error(`invalid instance name from callback: ${creds.instance}`) + } + // Exchange the short-lived, one-time login_token for the gateway + // auth_token server-side — the raw api_key never rides in the URL. + const authToken = await AltimateApi.exchangeSocialToken(creds.api_url, creds.instance, creds.token) + // Persist to ~/.altimate/altimate.json — the provider loader + // reads this first (it carries the instance/tenant + api_url + // the generic auth.json store can't). + await AltimateApi.saveCredentials({ + altimateUrl: creds.api_url, + altimateInstanceName: creds.instance, + altimateApiKey: authToken, + }) + // altimate_change start — onboarding funnel: auth succeeded and the instance + // is live. The instance name is the customer's tenant identifier and is never + // sent. time_to_connect_ms runs from the start of authorize() — before the + // callback server and browser open, both of which are part of the wait the user + // actually experiences — and lives in this attempt's closure, so a concurrent + // attempt cannot overwrite it. + if (!outcomeReported) { + outcomeReported = true + void OnboardingTelemetry.emit({ type: "gateway_auth_completed" }) + void OnboardingTelemetry.emit({ + type: "instance_connected", + time_to_connect_ms: Date.now() - startedAt, + }) + } + // altimate_change end + return { type: "success", key: authToken, provider: "altimate-backend" } + } catch (err) { + // Log the reason (CSRF / timeout / invalid instance / …). Runs in the + // server process, so this goes to the log, not the TUI display. + console.error("[altimate] gateway sign-in failed:", err instanceof Error ? err.message : err) + // altimate_change — onboarding funnel: only the classified enum is sent. The + // message can embed the instance name (see the invalid-instance throw above), + // so it never reaches telemetry. + if (!outcomeReported) { + outcomeReported = true + void OnboardingTelemetry.emit({ type: "gateway_auth_failed", reason: reasonOf(err) }) + } + return { type: "failed" } + } finally { + // Keep the shared server up while another flow is still waiting. + if (pending.size === 0) stopCallbackServer() + } + }, + } + }, + }, + { + // Fallback: paste an instance-name::api-key manually. type: "api", - label: "Connect to Altimate", + label: "Paste API key", }, ], }, diff --git a/packages/opencode/src/altimate/plugin/onboarding-telemetry.ts b/packages/opencode/src/altimate/plugin/onboarding-telemetry.ts new file mode 100644 index 0000000000..16e15fc131 --- /dev/null +++ b/packages/opencode/src/altimate/plugin/onboarding-telemetry.ts @@ -0,0 +1,134 @@ +// altimate_change start — activation-funnel telemetry, as a plugin. +// +// The three activation events are DERIVED. The activation menu is not UI: it is text the model +// writes from src/command/template/onboard-connect.txt, and the user picks a job by replying in +// free text. Nothing in the codebase observes either moment. What is deterministic is which +// command started the session and which tool ran next, so that is what this infers from — and +// the events are documented as lower bounds rather than exact counts. +// +// Implemented as a plugin rather than as edits inside session/prompt.ts because the hooks +// already exist (`command.execute.before`, `tool.execute.after`) and carry everything needed. +// This keeps the inference logic in one fork-owned file instead of scattering it across the +// session loop. +import type { Hooks, PluginInput } from "@opencode-ai/plugin" +import * as OnboardingTelemetry from "../telemetry/onboarding" + +const ONBOARD_CONNECT = "onboard-connect" + +/** Tool/skill → the spec's activation job enum. */ +type ActivationJob = "sample_duck_db" | "breaks_downstream" | "sql_review" | "cost" | "something_else" + +const JOB_BY_TOOL: Record = { + sample_setup: "sample_duck_db", +} + +const JOB_BY_SKILL: Record = { + "dbt-analyze": "breaks_downstream", + "sql-review": "sql_review", + "cost-report": "cost", +} + +/** + * Which activation job, if any, a tool call represents. Skills arrive as the generic `skill` + * tool with the skill name in `args.name`, so both shapes have to be checked. + * + * Returns undefined for the many tools that are not jobs (reads, writes, project_scan itself). + * Note that `something_else` — the "just let me chat" branch — has no tool signature at all and + * therefore can never be detected here; that branch is systematically missing from these counts. + */ +function jobForTool(tool: string, args: unknown): ActivationJob | undefined { + if (tool === "skill") { + const name = (args as { name?: unknown } | undefined)?.name + if (typeof name === "string") return JOB_BY_SKILL[name] + return undefined + } + return JOB_BY_TOOL[tool] +} + +/** + * Total warehouse connections a project_scan found, from its `metadata.connections` breakdown + * (`{existing, new_dbt, new_docker, new_env}` — there is no pre-summed total). All four count: + * a user whose only warehouse was discovered from a dbt profile, a docker compose file, or env + * vars still has a warehouse, and reporting `no_data` for them would send them down the + * sample-project branch of the activation menu. + */ +function countScanConnections(metadata: unknown): number { + const connections = (metadata as { connections?: Record } | undefined)?.connections + if (!connections) return 0 + return ["existing", "new_dbt", "new_docker", "new_env"].reduce((total, key) => { + const value = connections[key] + return total + (typeof value === "number" ? value : 0) + }, 0) +} + +/** + * Whether a completed tool call is evidence the JOB finished, not merely that a tool returned. + * + * This distinction is the whole reason `first_job_completed` is narrower than + * `activation_job_selected`. `sample_setup` genuinely does the work and reports `metadata.success`. + * The `skill` tool does not: it loads an instruction bundle and returns, after which the agent + * does the actual analysis with other tools. Treating a successful skill load as job completion + * would report "downstream impact analysis complete" the moment the instructions were read. + * + * There is no reliable signal for when a skill-driven job finishes, so those jobs are absent from + * `first_job_completed` rather than wrong in it. + */ +function isJobCompletion(tool: string, output: { metadata?: unknown }): boolean { + if (tool !== "sample_setup") return false + return (output.metadata as { success?: unknown } | undefined)?.success !== false +} + +export async function OnboardingTelemetryPlugin(_input: PluginInput): Promise { + return { + "command.execute.before": async (input) => { + // Any slash command means the next user message was not typed by the user — needed so + // `first_prompt_sent` measures a real first prompt rather than the scan gate's hidden + // `/onboard-connect` submission. + OnboardingTelemetry.noteCommandSubmission(input.sessionID) + + if (input.command !== ONBOARD_CONNECT) return + OnboardingTelemetry.markOnboardingSession(input.sessionID) + + // `skip` renders the menu immediately, with no scan to wait for, so the variant is known + // now. The `scan` branch cannot be resolved here — the menu follows the scan, and the + // variant depends on what the scan finds — so it is emitted from the scan result below. + if (input.arguments?.trim() === "skip" && OnboardingTelemetry.claimActivationMenu(input.sessionID)) { + void OnboardingTelemetry.emit({ type: "activation_menu_shown", variant: "no_data" }, input.sessionID) + } + }, + + "tool.execute.after": async (input, output) => { + if (!OnboardingTelemetry.isOnboardingSession(input.sessionID)) return + + // The scan branch: the menu is rendered right after project_scan returns, and the variant + // is whatever the scan found. This is the closest deterministic proxy for the menu + // actually being shown — closer than the command dispatch, which happens before the agent + // has done anything and would over-count sessions that error out first. + if (input.tool === "project_scan" && OnboardingTelemetry.claimActivationMenu(input.sessionID)) { + void OnboardingTelemetry.emit( + { + type: "activation_menu_shown", + // `no_data` tracks the template's own menu variant, which keys on whether a warehouse + // connection exists. A dbt project with no warehouse gets the no-data menu too. + variant: countScanConnections(output.metadata) > 0 ? "warehouse" : "no_data", + }, + input.sessionID, + ) + return + } + + const job = jobForTool(input.tool, input.args) + if (!job) return + + // Selection lands on the first job-shaped tool call. A job that starts and then fails still + // counts as selected — the user did choose it. + if (OnboardingTelemetry.claimActivationJobSelected(input.sessionID)) { + void OnboardingTelemetry.emit({ type: "activation_job_selected", job }, input.sessionID) + } + if (isJobCompletion(input.tool, output) && OnboardingTelemetry.claimFirstJobCompleted(input.sessionID)) { + void OnboardingTelemetry.emit({ type: "first_job_completed", job }, input.sessionID) + } + }, + } +} +// altimate_change end diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index d8f6e737e4..7d02fefd83 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -731,6 +731,147 @@ export namespace Telemetry { /** output tokens on the stop-without-tools generation — helps distinguish "refused" (low) from "wrote a long text plan" (high) */ tokens_output: number } + // altimate_change end + // altimate_change start — first-run onboarding funnel taxonomy. + // + // Event names and property names follow the product spec verbatim so the taxonomy is + // queryable under the names it was specified with. Two naming notes for whoever writes + // the queries: + // - `gateway_device_code_issued` is a spec name kept for fidelity. The gateway flow is + // actually a browser loopback OAuth (see altimate/plugin/altimate.ts) — there is no + // device code. The event means "authorize URL built and browser open attempted". + // - `environment_scan_completed` overlaps `environment_census` above; census stays the + // richer dbt/warehouse fingerprint, this one is the onboarding-shaped scan result. + // + // Events tagged "derived" are inferred from a proxy signal, not observed directly — the + // activation menu is model-rendered text (src/command/template/onboard-connect.txt), so + // there is no UI event to capture. Treat their counts as lower bounds. + | { + type: "onboarding_started" + timestamp: number + session_id: string + } + | { + type: "model_picker_shown" + timestamp: number + session_id: string + /** the picker mounts from several paths — without this the event over-counts first runs */ + trigger: "first_run" | "connect_command" | "big_pickle_back" | "prompt_gate" + } + | { + type: "provider_selected" + timestamp: number + session_id: string + provider: "altimate_gateway" | "anthropic" | "openai" | "google" | "big_pickle" | "search_all" + } + | { + type: "big_pickle_confirm_shown" + timestamp: number + session_id: string + origin: "welcome" | "model" + } + | { + type: "big_pickle_choice" + timestamp: number + session_id: string + choice: "accept" | "cancel" + } + | { + type: "gateway_device_code_issued" + timestamp: number + session_id: string + } + | { + type: "gateway_auth_completed" + timestamp: number + session_id: string + } + | { + type: "gateway_auth_failed" + timestamp: number + session_id: string + /** `denied` only from an explicit error callback; an unknown/invalid state never rejects + * the pending promise, so CSRF mismatches surface as `timeout`. Never carries error text. */ + reason: "timeout" | "denied" | "error" + } + | { + type: "instance_connected" + timestamp: number + session_id: string + /** measured from the authorize() call that opened the browser */ + time_to_connect_ms: number + } + | { + type: "onboarding_completed" + timestamp: number + session_id: string + } + | { + type: "scan_gate_shown" + timestamp: number + session_id: string + } + | { + type: "scan_gate_choice" + timestamp: number + session_id: string + choice: "scan" | "skip" + } + | { + type: "environment_scan_completed" + timestamp: number + session_id: string + has_dbt: boolean + has_warehouse: boolean + is_repo: boolean + connections_found: number + /** bounded list of short enum reasons — arrays are JSON.stringify'd into customDimensions */ + degraded: string[] + } + | { + type: "activation_menu_shown" + timestamp: number + session_id: string + variant: "warehouse" | "no_data" + } + | { + /** derived — inferred from the first job tool/skill after the menu. `something_else` + * has no tool anchor and is systematically under-counted. */ + type: "activation_job_selected" + timestamp: number + session_id: string + job: "sample_duck_db" | "breaks_downstream" | "sql_review" | "cost" | "something_else" + } + | { + /** derived — see activation_job_selected */ + type: "first_job_completed" + timestamp: number + session_id: string + job: "sample_duck_db" | "breaks_downstream" | "sql_review" | "cost" | "something_else" + } + | { + type: "sample_setup_completed" + timestamp: number + session_id: string + success: boolean + /** counts come from the shipped jaffle-shop manifest; the target path is never sent */ + models: number + tables: number + /** the tool is deliberately re-callable (reuse / reset / install-alongside) */ + reused: boolean + } + | { + type: "first_prompt_sent" + timestamp: number + session_id: string + } + | { + type: "onboarding_abandoned" + timestamp: number + session_id: string + /** last funnel stage observed before exit without a completion */ + last_stage: string + } // altimate_change end /** SHA256 hash a masked error message for anonymous grouping. */ @@ -1206,6 +1347,33 @@ export namespace Telemetry { let droppedEvents = 0 let initPromise: Promise | undefined let initDone = false + // altimate_change — in-flight shutdown, declared with the rest of the module state so init() + // above can consult it. See shutdown() for why concurrent shutdowns must be serialized. + let shutdownPromise: Promise | undefined + + // altimate_change start — per-launch correlation id, shared across threads via the environment. + // The TUI worker is spawned after the CLI middleware has already initialised telemetry on the + // main thread, and a Worker inherits a copy of process.env, so whichever thread runs first + // publishes the value and the other reads it. Lazy rather than module-init so importing the + // telemetry module (in tests, tooling) does not mint ids nobody uses. + // One id per process launch, shared with the TUI's server Worker through its environment. + // + // It has to be handed over explicitly at Worker construction (see cli/cmd/tui.ts). Two things + // that look like they would work do not, both confirmed end to end: + // - mutating process.env after startup: a Bun Worker does not observe it, so the worker mints + // its own id and the two halves of the funnel become unjoinable; + // - deriving it from the process (pid + start time): process.uptime() is per-THREAD in Bun, + // so the worker computes a different start time than the main thread. + const LAUNCH_ID_ENV = "ALTIMATE_LAUNCH_ID" + + export function launchId(): string { + const existing = process.env[LAUNCH_ID_ENV] + if (existing) return existing + const created = randomUUID() + process.env[LAUNCH_ID_ENV] = created + return created + } + // altimate_change end function parseConnectionString(cs: string): AppInsightsConfig | undefined { const parts: Record = {} @@ -1239,6 +1407,12 @@ export namespace Telemetry { source: clientSource, project_id: fields.project_id ?? projectId, ...(machineId && { machine_id: machineId }), + // altimate_change — groups every event from one process launch. The onboarding funnel + // spans the TUI main thread and the server worker, and most of it runs before any chat + // session exists, so `session_id` is empty for the first half and real for the second — + // leaving no key to join a single run on. This is not persisted, not derived from the + // machine or the user, and not reused across launches; it only says "same run". + launch_id: launchId(), } const measurements: Record = {} @@ -1289,7 +1463,13 @@ export namespace Telemetry { // won't race with await init() in session prompt. export function init(): Promise { if (!initPromise) { - initPromise = doInit() + // altimate_change start — never re-init across an in-flight shutdown. + // session/prompt.ts init()s at the start of every session loop and shutdown()s at the end, + // so a new session can begin while the previous shutdown is still awaiting flush(). Without + // this, init() returns immediately, the new session's events land in `buffer`, and the + // in-flight doShutdown() then clears that buffer — losing every event tracked in the gap. + initPromise = shutdownPromise ? shutdownPromise.catch(() => {}).then(doInit) : doInit() + // altimate_change end } return initPromise } @@ -1335,9 +1515,22 @@ export namespace Telemetry { try { machineId = fs.readFileSync(machineIdPath, "utf8").trim() } catch { - machineId = randomUUID() + // altimate_change start — create exclusively so two threads cannot mint different ids. + // The TUI main thread and the server worker each initialise their own copy of this + // module, and on a genuinely new install both can find the file missing at the same + // moment. With a plain write, the loser's value overwrites the winner's while both keep + // their own in memory, so a single first run reports two machine_ids — breaking the + // fallback identity exactly on the run that matters most. `wx` makes one of them fail, + // and the loser re-reads what the winner wrote. + const candidate = randomUUID() fs.mkdirSync(path.dirname(machineIdPath), { recursive: true }) - fs.writeFileSync(machineIdPath, machineId, "utf8") + try { + fs.writeFileSync(machineIdPath, candidate, { encoding: "utf8", flag: "wx" }) + machineId = candidate + } catch { + machineId = fs.readFileSync(machineIdPath, "utf8").trim() + } + // altimate_change end } } catch { // Machine ID unavailable — proceed without it @@ -1379,7 +1572,11 @@ export namespace Telemetry { } } - export async function flush() { + // altimate_change — `timeoutMs` lets exit paths bound the flush from the INSIDE. Racing + // flush() against an external timer does not cancel the fetch: the losing promise keeps + // running and can reset module state after the caller has moved on. Threading the deadline + // into the existing AbortController actually aborts the request. + export async function flush(timeoutMs: number = REQUEST_TIMEOUT_MS) { if (!enabled || buffer.length === 0 || !appInsights) return const events = buffer.splice(0, buffer.length) @@ -1397,7 +1594,7 @@ export namespace Telemetry { } const controller = new AbortController() - const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS) + const timeout = setTimeout(() => controller.abort(), timeoutMs) try { const response = await fetch(appInsights.endpoint, { method: "POST", @@ -1437,7 +1634,43 @@ export namespace Telemetry { } // altimate_change end - export async function shutdown() { + // altimate_change start — serialize concurrent shutdowns, and let callers bound the flush. + // + // shutdown() is called from several independent paths (session/prompt.ts at the end of each + // session loop, the CLI's outer finally, and — for onboarding telemetry — the TUI exit path + // and the TUI worker's rpc.shutdown). Two overlapping calls would both enter flush(), which + // splices the shared buffer, so one caller can post a half-empty batch while the other drops + // events. The in-flight promise is cleared on settle, so a later init/shutdown cycle (the + // per-session pattern in prompt.ts) still works. + // + // `timeoutMs` bounds the flush from the inside. Racing shutdown() against an external timer + // does NOT cancel it: the losing promise keeps running and resets module state after the + // caller has already moved on. Exit paths pass a budget so the fetch itself is aborted. + export async function shutdown(opts?: { timeoutMs?: number }) { + if (shutdownPromise) { + // An earlier caller is mid-flush, possibly on the default 10s budget. Returning its promise + // unchanged would silently ignore this caller's deadline and make the exit path wait past + // it — after which the worker is terminated anyway and the buffer is lost regardless. We + // cannot shorten the in-flight flush, but we can stop making the caller wait for it. + const budget = opts?.timeoutMs + if (budget === undefined) return shutdownPromise + let timer: ReturnType | undefined + await Promise.race([ + shutdownPromise, + new Promise((resolve) => { + timer = setTimeout(resolve, budget) + }), + ]).catch(() => {}) + if (timer) clearTimeout(timer) + return + } + shutdownPromise = doShutdown(opts?.timeoutMs).finally(() => { + shutdownPromise = undefined + }) + return shutdownPromise + } + + async function doShutdown(timeoutMs?: number) { // Wait for init to complete so we know whether telemetry is enabled // and have a valid endpoint to flush to. init() is fire-and-forget // in CLI middleware, so it may still be in-flight when shutdown runs. @@ -1452,7 +1685,7 @@ export namespace Telemetry { clearInterval(flushTimer) flushTimer = undefined } - await flush() + await flush(timeoutMs) enabled = false appInsights = undefined buffer = [] @@ -1463,4 +1696,5 @@ export namespace Telemetry { initPromise = undefined initDone = false } + // altimate_change end } diff --git a/packages/opencode/src/altimate/telemetry/onboarding.ts b/packages/opencode/src/altimate/telemetry/onboarding.ts new file mode 100644 index 0000000000..fab554c9ae --- /dev/null +++ b/packages/opencode/src/altimate/telemetry/onboarding.ts @@ -0,0 +1,257 @@ +// altimate_change start — first-run onboarding funnel emitter. +// +// Thin, typed wrapper over Telemetry.track for the onboarding taxonomy. It exists for two +// reasons that a bare track() call cannot cover: +// +// 1. INIT ORDERING. Telemetry.init() runs in the CLI middleware on the main thread +// (src/index.ts) but, in the TUI worker, only via session/prompt.ts when a prompt runs. +// Onboarding events fire before any prompt exists — a user who quits during gateway auth +// never reaches one — so those events would sit in the pre-init buffer and never ship. +// emit() awaits the idempotent init() first. +// +// 2. ABANDONMENT. `onboarding_abandoned` is defined by what did NOT happen, so something has +// to remember how far the user got. See the thread note below for where that state lives. +// +// THREAD NOTE — this matters and is easy to get wrong. `altimate-code tui` runs the TUI on the +// process main thread and the HTTP server in a Worker (src/cli/cmd/tui.ts), and each thread +// loads its own instance of the Telemetry module with its own buffer. TUI-side events +// (model picker, provider rows, scan gate) are tracked on the MAIN thread; server-side events +// (gateway auth, project scan, sample setup) on the WORKER. Neither can see the other's stage. +// +// Abandonment is therefore owned by the main thread: it is where the process exits, and where +// the user-visible funnel position is known. `last_stage` means "the furthest point the user +// reached in the TUI", which is the question the funnel is actually asking. Server-side stages +// are marked from their TUI-visible trigger (choosing the gateway provider marks `gateway_auth`) +// rather than from the worker, which cannot reach this state. +import { Telemetry } from "./index" + +/** Funnel positions, ordered. `last_stage` on abandonment reports the furthest one reached. */ +export const ONBOARDING_STAGES = [ + "started", + "model_picker", + "provider_setup", + "big_pickle_confirm", + "gateway_auth", + "connected", +] as const + +export type OnboardingStage = (typeof ONBOARDING_STAGES)[number] + +/** Every onboarding variant of Telemetry.Event, minus the envelope fields emit() fills in. */ +type OnboardingEventInput = Extract< + Telemetry.Event, + { + type: + | "onboarding_started" + | "model_picker_shown" + | "provider_selected" + | "big_pickle_confirm_shown" + | "big_pickle_choice" + | "gateway_device_code_issued" + | "gateway_auth_completed" + | "gateway_auth_failed" + | "instance_connected" + | "onboarding_completed" + | "scan_gate_shown" + | "scan_gate_choice" + | "environment_scan_completed" + | "activation_menu_shown" + | "activation_job_selected" + | "first_job_completed" + | "sample_setup_completed" + | "first_prompt_sent" + | "onboarding_abandoned" + } +> + +// Distributive: a bare Omit collapses the union to its shared keys, which would drop +// every per-event property (reason, choice, job, …) from the emit() signature. +type DistributiveOmit = T extends unknown ? Omit : never + +type EmitInput = DistributiveOmit + +// Module-global, per thread. Resets on every process launch, which is correct: a fresh launch +// is a fresh onboarding attempt. +let furthestStage: OnboardingStage | undefined +let completed = false +let abandonedEmitted = false +/** Set only when the first-run gate actually opened. Gates the whole funnel — see advance(). */ +let funnelStarted = false + +/** Stage implied by an event, where one is implied. Events not listed leave the stage alone. */ +const STAGE_FOR_EVENT: Partial> = { + onboarding_started: "started", + model_picker_shown: "model_picker", + // Any provider choice enters setup. Without this, a user who picks Anthropic and quits during + // key entry is reported as abandoning at `model_picker` — as if they never chose anything. + provider_selected: "provider_setup", + big_pickle_confirm_shown: "big_pickle_confirm", + gateway_device_code_issued: "gateway_auth", + instance_connected: "connected", + onboarding_completed: "connected", + // Deliberately no entry for scan_gate_shown / activation_menu_shown. Abandonment is defined as + // quitting BEFORE connecting, and both of those only happen after `onboarding_completed` has + // set `completed`, which suppresses abandonment entirely. Stages for them would be values that + // can never be reported. +} + +function advance(stage: OnboardingStage) { + // Only a real first run is in the funnel. Most of the UI that emits funnel events is also + // reachable outside onboarding — `/connect` opens the same picker, `/model` opens the same Big + // Pickle interstitial — and a returning user doing either would otherwise enter the funnel at + // `model_picker`, never emit `onboarding_completed`, and be reported as ABANDONED on exit. + // That would have made abandonment mostly a count of returning users opening `/connect`. + // + // `onboarding_started` is emitted only from the branch that actually opens the first-run gate, + // so it is the gate for everything downstream. + if (stage !== "started" && !funnelStarted) return + if (stage === "started") funnelStarted = true + + const next = ONBOARDING_STAGES.indexOf(stage) + const current = furthestStage ? ONBOARDING_STAGES.indexOf(furthestStage) : -1 + // Monotonic: re-opening the picker after reaching the scan gate must not walk the funnel back. + if (next > current) furthestStage = stage +} + +/** + * Emit an onboarding event. Fire-and-forget by design — onboarding must never wait on, or fail + * because of, telemetry. Awaiting the returned promise is only useful on an exit path. + */ +export async function emit(event: EmitInput, sessionID?: string): Promise { + try { + const stage = STAGE_FOR_EVENT[event.type] + if (stage) advance(stage) + if (event.type === "onboarding_completed") completed = true + + await Telemetry.init() + Telemetry.track({ + ...event, + timestamp: Date.now(), + // Prefer the caller's session over the ambient telemetry context. setContext() is + // process-global and set by the session loop, so a plugin hook firing for session A while + // the context still points at session B would otherwise misattribute the event — or stamp + // "" when no session has run yet, which is the normal case for the gateway events. + session_id: sessionID ?? Telemetry.getContext().sessionId, + } as Telemetry.Event) + } catch { + // Telemetry must never break onboarding. + } +} + +/** Mark the gateway provider path as entered. The browser flow itself runs in the worker, whose + * telemetry state this thread cannot see — see the thread note at the top of this file. */ +export function markStage(stage: OnboardingStage) { + advance(stage) +} + +/** True once `onboarding_completed` has been emitted on this thread. */ +export function isCompleted() { + return completed +} + +/** + * Emit `onboarding_abandoned` if the user got somewhere in the funnel and never completed. + * Call on the exit path, before the final flush. No-ops when the user never started (a + * returning user with credentials), already completed, or when it has already fired. + */ +export async function emitAbandonedIfIncomplete(): Promise { + if (completed || abandonedEmitted || !furthestStage) return + abandonedEmitted = true + await emit({ type: "onboarding_abandoned", last_stage: furthestStage }) +} + +// --------------------------------------------------------------------------- +// Session-scoped state (worker thread) +// +// The activation events are inferred, not observed: the menu is text the model writes from +// src/command/template/onboard-connect.txt, and the user answers in free text. What IS +// deterministic is which command started the session and which tool ran next, so that is what +// these track. Lives here rather than in the plugin so there is one home for onboarding state. +// +// Bounded: a `serve` process is long-lived and sees unboundedly many sessions, so these must not +// grow forever. Sets are capped and evict in insertion order. +// --------------------------------------------------------------------------- + +const MAX_TRACKED_SESSIONS = 256 + +/** + * One record per tracked session, rather than a set per flag. Separate sets can evict a session + * from some and not others, which produces the worst possible state: a session still considered + * "onboarding" but with its once-per-session claims forgotten, so it re-emits `first` events. A + * single map evicts a session's whole state atomically — it then simply stops being tracked. + */ +type SessionRecord = { + /** Next user message was submitted by a slash command, not typed. */ + commandSubmission: boolean + /** Session was started by `/onboard-connect`. */ + onboarding: boolean + menuShown: boolean + jobSelected: boolean + jobCompleted: boolean + firstPromptSent: boolean +} + +const sessions = new Map() + +function record(sessionID: string): SessionRecord { + const existing = sessions.get(sessionID) + if (existing) return existing + if (sessions.size >= MAX_TRACKED_SESSIONS) { + const oldest = sessions.keys().next() + if (!oldest.done) sessions.delete(oldest.value) + } + const created: SessionRecord = { + commandSubmission: false, + onboarding: false, + menuShown: false, + jobSelected: false, + jobCompleted: false, + firstPromptSent: false, + } + sessions.set(sessionID, created) + return created +} + +/** Claim a once-per-session flag. Returns false if it was already claimed. */ +function claim(sessionID: string, key: "menuShown" | "jobSelected" | "jobCompleted" | "firstPromptSent"): boolean { + const entry = record(sessionID) + if (entry[key]) return false + entry[key] = true + return true +} + +/** Record that the next user message in this session comes from a slash command. */ +export function noteCommandSubmission(sessionID: string) { + record(sessionID).commandSubmission = true +} + +/** True (once) if this session's pending user message was command-submitted. */ +export function consumeCommandSubmission(sessionID: string): boolean { + const entry = sessions.get(sessionID) + if (!entry?.commandSubmission) return false + entry.commandSubmission = false + return true +} + +export function markOnboardingSession(sessionID: string) { + record(sessionID).onboarding = true +} + +export function isOnboardingSession(sessionID: string): boolean { + return sessions.get(sessionID)?.onboarding === true +} + +export const claimActivationMenu = (sessionID: string) => claim(sessionID, "menuShown") +export const claimActivationJobSelected = (sessionID: string) => claim(sessionID, "jobSelected") +export const claimFirstJobCompleted = (sessionID: string) => claim(sessionID, "jobCompleted") +export const claimFirstPrompt = (sessionID: string) => claim(sessionID, "firstPromptSent") + +/** Test seam — module state is per-process by design, so tests need an explicit reset. */ +export function resetForTest() { + furthestStage = undefined + completed = false + abandonedEmitted = false + funnelStarted = false + sessions.clear() +} +// altimate_change end diff --git a/packages/opencode/src/altimate/tools/project-scan.ts b/packages/opencode/src/altimate/tools/project-scan.ts index 2c80ce7796..a636a06c8a 100644 --- a/packages/opencode/src/altimate/tools/project-scan.ts +++ b/packages/opencode/src/altimate/tools/project-scan.ts @@ -4,6 +4,8 @@ import { Dispatcher } from "../native" import { existsSync, readFileSync } from "fs" import path from "path" import { Telemetry } from "@/telemetry" +// altimate_change — onboarding funnel: environment_scan_completed +import * as OnboardingTelemetry from "@/altimate/telemetry/onboarding" import { Config } from "@/config/config" import { Skill } from "../../skill" @@ -930,6 +932,33 @@ export const ProjectScanTool = Tool.define("project_scan", { } } + // altimate_change start — onboarding funnel: the scan result, in the shape the funnel asks + // for. Deliberately separate from `environment_census` above, which stays the richer + // dbt/warehouse fingerprint; this one answers "did the scan find the user a working setup". + // + // Fires on every project_scan, not just the onboarding one — the tool is also reachable via + // /discover and any model-initiated call. + // + // has_warehouse and connections_found both come from totalConnections, NOT + // connections.alreadyConfigured: connections discovered from dbt profiles, docker, and env + // vars are counted separately, and a user whose only warehouse was auto-discovered would + // otherwise be recorded as having none. + // + // degraded[] is a sorted list of short detection-failure keys — no paths, hosts, or messages. + void OnboardingTelemetry.emit({ + type: "environment_scan_completed", + has_dbt: dbtProject.found, + has_warehouse: totalConnections > 0, + is_repo: git.isRepo, + connections_found: totalConnections, + degraded: degradedList, + // Explicit session: emit() otherwise falls back to the process-global telemetry context, + // which is set per prompt loop. Two concurrent sessions in `serve` or in the TUI worker + // overwrite each other's context, so a scan would be attributed to whichever session most + // recently started a turn. + }, ctx.sessionID) + // altimate_change end + // Build metadata const toolsFound = dataTools.filter((t) => t.installed).map((t) => t.name) diff --git a/packages/opencode/src/altimate/tools/sample-setup.ts b/packages/opencode/src/altimate/tools/sample-setup.ts new file mode 100644 index 0000000000..161879288f --- /dev/null +++ b/packages/opencode/src/altimate/tools/sample-setup.ts @@ -0,0 +1,267 @@ +import fs from "node:fs" +import path from "node:path" +import z from "zod" +import { InstallationVersion } from "@opencode-ai/core/installation/version" +import { Tool } from "../../tool/tool" +import { materializeSample } from "../onboarding/materialize" +import { DEFAULT_SAMPLE_NAME, resolveSampleSource, type SampleSourceLocation } from "../onboarding/sample-source-resolver" +import { detectDbtRuntime } from "../onboarding/tool-detection" +// altimate_change — onboarding funnel: sample_setup_completed +import * as OnboardingTelemetry from "../telemetry/onboarding" + +/** + * `sample_setup` — LLM-invoked tool that copies the shipped jaffle-shop + * DuckDB sample onto the user's filesystem and returns a structured + * summary the `/onboard-connect` template branches on. + * + * Contract with the template + * (`packages/opencode/src/command/template/onboard-connect.txt`, sample + * routing block): + * + * Success: `{title, metadata: {targetPath, reused, suffix, note}, output}` + * - reused=true, note contains "Caller must prompt" → template + * prompts before overwriting (different sample version on disk) + * - reused=true, no such note → "already set up at " + * - reused=false, suffix=0 → "Sample project created at " + * - reused=false, suffix>0 → preferred name was taken, used a + * suffixed variant (`-2`, `-3`, …) + * Failure: `{title, metadata: {error}, output}` — `output` carries a + * verbatim actionable message ("Target parent directory X is not + * writable", "HOME=/root but this process is not running as root", + * etc.) that the template passes through unchanged. + * + * The LLM invokes this with no arguments (the template says so). All + * schema parameters are optional and only exist for tests + advanced + * callers who want to override the target path. + * + * The sample version is read from the shipped + * `sample-projects//sample-manifest.json` — bumping the sample + * automatically bumps the version stamped into the marker without a + * code change here. + */ +export const SampleSetupTool = Tool.define("sample_setup", { + description: + "Materialize the shipped jaffle-shop DuckDB sample dbt project onto the user's disk. " + + "Called by the /onboard-connect activation menu when the user picks 'Try Altimate on a " + + "sample dbt project'. Idempotent — a second call reuses the existing materialized " + + "directory without re-copying. Never overwrites an unrelated user directory: if the " + + "preferred path holds unknown content, materializes into a suffixed variant " + + "(`-2`, `-3`, ...) instead.", + parameters: z.object({ + preferred_target_name: z + .string() + .trim() + .regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/, { + message: + "must be a plain directory name (letters/digits/dot/dash/underscore only, no path separators, no leading dot)", + }) + .optional() + .describe( + "Directory name (relative to the target parent) to materialize into. Defaults to " + + "`altimate-sample-dbt`. Rarely overridden — the default matches what the activation " + + "menu documents. Must be a single path segment: letters, digits, dot, dash, " + + "underscore only. Not a full path. Do not include `/` or `..`.", + ), + allow_in_place_upgrade: z + .boolean() + .optional() + .default(false) + .describe( + "When the target exists at a different sample version, overwrite in place instead of " + + "returning `reused: true` with a prompt hint. Only set true after the user has " + + "confirmed they want to upgrade AND does not care about local edits in the old copy.", + ), + install_alongside: z + .boolean() + .optional() + .default(false) + .describe( + "When the target exists at a different sample version, materialize the new version " + + "into `-2` (or the next free suffix) instead of touching the old copy. Use " + + "this after the user has picked the 'install alongside' option from the version-" + + "conflict prompt. Mutually exclusive with allow_in_place_upgrade; alongside wins.", + ), + }), + async execute(args, ctx) { + const sampleName = DEFAULT_SAMPLE_NAME + // Resolve the sample source ONCE per invocation and pass it forward. + // materializeSample() would otherwise call resolveSampleSource() again + // internally; on the wrapper-bin-parent / dev-source-tree candidates + // that means an extra fs.existsSync() sweep across the whole hunt + // chain — cheap in absolute terms, but a redundant expense the tool + // pays on every activation. Resolving once also guarantees that the + // manifest read and the materialize copy come from the SAME source + // directory (a mid-invocation env or filesystem change can't put them + // out of sync). + let sampleSource: SampleSourceLocation + let sampleVersion: string + try { + const resolved = resolveSampleSource(sampleName) + if (!resolved) { + throw new Error(`resolveSampleSource returned undefined for '${sampleName}'`) + } + sampleSource = resolved + sampleVersion = readSampleVersionAt(sampleSource.path) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + const guidance = + `Could not locate the shipped starter sample source. This usually means the CLI ` + + `was installed without its wrapper package assets. Reinstall with: ` + + `\`npm i -g @altimateai/altimate-code@latest\`\n\n` + + `Underlying error: ${message}` + // altimate_change — onboarding funnel: a broken install (sample assets missing) is a + // sample-setup failure too. Without this the failure count only covers materialization + // errors and silently under-reports the "CLI shipped without its assets" case. + void OnboardingTelemetry.emit({ + type: "sample_setup_completed", + success: false, + models: 0, + tables: 0, + reused: false, + }, ctx.sessionID) + return { + title: "Starter sample unavailable", + metadata: { success: false, error: message, targetPath: "", reused: false, suffix: 0, note: "" }, + output: `status: error\nreason: sample_source_missing\n\n${guidance}`, + } + } + + try { + const result = await materializeSample({ + sampleName, + preferredTargetName: args.preferred_target_name, + // NOTE: targetParent is deliberately NOT plumbed from tool args — it + // was removed from the LLM-facing schema to close a bypass of the + // rejectUnsafeHome guard (a caller-controlled parent skipped the + // check). Callers who need a specific parent use materializeSample + // directly with allowUnsafeParent for tests. + cliVersion: InstallationVersion, + sampleVersion, + allowInPlaceUpgrade: args.allow_in_place_upgrade, + installAlongside: args.install_alongside, + preResolvedSource: sampleSource, + }) + // Probe dbt-runtime state so the template's "Build & query it" branch + // can read it directly instead of shelling out to a duplicate probe. + // Force-refresh in case the user pip-installed dbt-duckdb during the + // session (cache from an earlier render would say hasDbtDuckdb=false). + const dbt = await detectDbtRuntime({ force: true }) + const dbtLine = dbt.hasDbt + ? `dbt: present (dbt-core ${dbt.dbtCoreVersion ?? "unknown"}, duckdb-adapter ${dbt.hasDbtDuckdb ? "present" : "missing"})` + : `dbt: missing (dbt-core not on PATH)` + + // Self-describing status prefix — the model only sees `output`, never + // `metadata` (packages/opencode/src/session/message-v2.ts:822). The + // template branches on `status: ok` vs `status: error` in this text. + const outputText = + `status: ok\n` + + `path: ${result.targetPath}\n` + + `reused: ${result.reused}\n` + + `suffix: ${result.suffix}\n` + + `${dbtLine}\n` + + `note: ${result.note}` + // altimate_change start — onboarding funnel. `reused` is carried because the tool is + // deliberately re-callable (reuse / reset / install-alongside / dbt re-probe), so this + // fires per invocation, not once per sample. targetPath is never sent — it is a filesystem + // path under the user's home. + const sampleContents = countSampleContents(sampleSource.path) + void OnboardingTelemetry.emit({ + type: "sample_setup_completed", + success: true, + models: sampleContents.models, + tables: sampleContents.tables, + reused: result.reused, + }, ctx.sessionID) + // altimate_change end + return { + title: result.reused ? `Reused starter sample at ${result.targetPath}` : `Materialized starter sample at ${result.targetPath}`, + metadata: { + success: true, + targetPath: result.targetPath, + reused: result.reused, + suffix: result.suffix, + note: result.note, + dbtRuntime: dbt, + }, + output: outputText, + } + } catch (err) { + // materializeSample throws with actionable messages for the three + // failure modes: unsafe HOME (rejectUnsafeHome), unwritable target + // parent (checkParentWritable), or missing sample source. Wrap the + // message with a status prefix so the template's failure branch can + // reliably detect it from `output` alone — metadata never reaches + // the model. + const message = err instanceof Error ? err.message : String(err) + // altimate_change — onboarding funnel: failed setup. The error message embeds filesystem + // paths (unsafe HOME, unwritable parent), so it is not sent — only the boolean. + void OnboardingTelemetry.emit({ + type: "sample_setup_completed", + success: false, + models: 0, + tables: 0, + reused: false, + }, ctx.sessionID) + return { + title: "Starter materialization failed", + metadata: { success: false, error: message, targetPath: "", reused: false, suffix: 0, note: "" }, + output: `status: error\nreason: materialize_failed\n\n${message}`, + } + } + }, +}) + +/** + * Read the sample's `sample-manifest.json` from an ALREADY-RESOLVED source + * directory and return its `version` field. Takes the resolved path (not the + * sample name) so the caller can resolve once and share the result with + * downstream materializeSample — see finding 25. + * + * The version stamps into the on-disk marker so a future run can detect + * whether the materialized copy is current or lags a CLI upgrade. + */ +// altimate_change start — onboarding funnel: model/seed counts for sample_setup_completed. +// +// Counted from the shipped source tree rather than read from a constant or from dbt's +// target/manifest.json. A constant silently drifts the first time someone adds a model; +// target/manifest.json is ~17k lines and parsing it to count two things is a waste on a path +// the user is actively waiting on. Counting files can't drift and costs one shallow walk. +// +// Best-effort by construction: telemetry must never fail a sample setup, so any fs error +// yields 0 rather than propagating. +function countFilesWithExtension(dir: string, extension: string): number { + let total = 0 + let entries: fs.Dirent[] + try { + entries = fs.readdirSync(dir, { withFileTypes: true }) + } catch { + return 0 + } + for (const entry of entries) { + if (entry.isDirectory()) { + total += countFilesWithExtension(path.join(dir, entry.name), extension) + } else if (entry.isFile() && entry.name.endsWith(extension)) { + total += 1 + } + } + return total +} + +/** dbt models (`models/**\/*.sql`) and seed tables (`seeds/*.csv`) in the shipped sample. */ +function countSampleContents(sampleSourcePath: string): { models: number; tables: number } { + return { + models: countFilesWithExtension(path.join(sampleSourcePath, "models"), ".sql"), + tables: countFilesWithExtension(path.join(sampleSourcePath, "seeds"), ".csv"), + } +} +// altimate_change end + +function readSampleVersionAt(sampleSourcePath: string): string { + const manifestPath = path.join(sampleSourcePath, "sample-manifest.json") + const raw = fs.readFileSync(manifestPath, "utf8") + const parsed = JSON.parse(raw) as { version?: unknown } + if (typeof parsed.version !== "string" || parsed.version.length === 0) { + throw new Error(`sample-manifest.json at ${manifestPath} is missing a string \`version\` field`) + } + return parsed.version +} diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index 8f8dfedb23..87c30f6d5c 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -371,6 +371,9 @@ export const ProvidersLoginCommand = effectCmd({ const hooks = yield* pluginSvc.list() const priority: Record = { + // altimate_change start — surface the Altimate LLM Gateway first + "altimate-backend": -1, + // altimate_change end opencode: 0, openai: 1, "github-copilot": 2, diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts index e93aa4f44a..403eaadc56 100644 --- a/packages/opencode/src/cli/cmd/tui.ts +++ b/packages/opencode/src/cli/cmd/tui.ts @@ -16,6 +16,10 @@ import type { EventSource } from "@opencode-ai/tui/context/sdk" import { writeHeapSnapshot } from "v8" import { validateSession } from "../tui/validate-session" import { win32InstallCtrlCGuard } from "@opencode-ai/tui/terminal-win32" +// altimate_change start — onboarding telemetry: main-thread flush on the TUI exit path +import { Telemetry } from "@/altimate/telemetry" +import * as OnboardingTelemetry from "@/altimate/telemetry/onboarding" +// altimate_change end declare global { const OPENCODE_WORKER_PATH: string @@ -131,7 +135,12 @@ export const TuiThreadCommand = cmd({ } const cwd = Filesystem.resolve(process.cwd()) - const worker = new Worker(file) + // altimate_change — hand the launch correlation id to the worker explicitly. A Bun Worker + // does not see runtime mutations to process.env, so without this the worker mints its own + // and the TUI-thread and worker-thread halves of the onboarding funnel cannot be joined. + const worker = new Worker(file, { + env: { ...process.env, ALTIMATE_LAUNCH_ID: Telemetry.launchId() }, + } as WorkerOptions) const client = Rpc.client(worker) const reload = () => { client.call("reload", undefined).catch(() => {}) @@ -203,6 +212,30 @@ export const TuiThreadCommand = cmd({ }, config, pluginHost: createLegacyTuiPluginHost(), + // altimate_change — onboarding funnel seam. Deliberately a single-line marker, not a + // start/end pair: this sits inside the "clean up TUI worker after failed --session + // validation" region, and a nested `altimate_change end` truncates the block that + // test/cli/tui/command.test.ts slices to assert cleanup ordering. + // + // The TUI renders on this thread, so + // this reaches the main-process Telemetry module directly (already initialized by the + // CLI middleware) — no HTTP, no worker round-trip. + // + // The `name` → `type` remap is the one untyped point in the chain: packages/tui + // cannot import the Telemetry event union, so it declares its own mirror in + // context/onboarding-telemetry.tsx. A test pins the two lists together. + // + // Selecting the gateway provider also marks the auth stage, because the browser flow + // itself runs in the worker and cannot reach this thread's abandonment state. + onTelemetry: (event) => { + const { name, ...props } = event + if (name === "provider_selected" && event.provider === "altimate_gateway") { + OnboardingTelemetry.markStage("gateway_auth") + } + void OnboardingTelemetry.emit({ type: name, ...props } as Parameters< + typeof OnboardingTelemetry.emit + >[0]) + }, directory: cwd, fetch: transport.fetch, events: transport.events, @@ -224,6 +257,23 @@ export const TuiThreadCommand = cmd({ try { unguard?.() } catch {} + // altimate_change start — flush main-thread telemetry before the explicit exit below. + // This handler ends with process.exit(0), which skips the outer `finally` in src/index.ts + // that normally calls Telemetry.shutdown(). Without this, every event tracked on the TUI + // thread since the last 5s interval flush is lost — including onboarding_abandoned, which + // by definition only fires here. Runs after stop() so the worker has already drained. + // + // Bounded from the INSIDE (shutdown → flush → AbortController), not by racing a timer: + // a lost race would leave the flush running and resetting module state after we resumed. + // flush() would otherwise block for REQUEST_TIMEOUT_MS (10s) on a blackholed network — a + // visible hang between the user quitting and the shell prompt returning. + try { + await OnboardingTelemetry.emitAbandonedIfIncomplete() + await Telemetry.shutdown({ timeoutMs: 2000 }) + } catch { + // Never let telemetry delay or break exit. + } + // altimate_change end } process.exit(0) }, diff --git a/packages/opencode/src/cli/tui/worker.ts b/packages/opencode/src/cli/tui/worker.ts index 29c7559de9..42f84ff838 100644 --- a/packages/opencode/src/cli/tui/worker.ts +++ b/packages/opencode/src/cli/tui/worker.ts @@ -24,6 +24,8 @@ import { disposeAllInstancesAndEmitGlobalDisposed } from "@/server/global-lifecy // does NOT receive this worker's in-process (Server.Default().fetch) session events. import { TraceConsumer } from "@/altimate/observability/trace-consumer" import { Instance } from "@/project/instance" +// altimate_change — onboarding telemetry: flush this thread's buffer in rpc.shutdown() +import { Telemetry } from "@/altimate/telemetry" Heap.start() @@ -111,6 +113,22 @@ export const rpc = { await Promise.race([traceTail, new Promise((r) => setTimeout(r, 2000))]).catch(() => {}) traceConsumer.flushSync() // altimate_change end + // altimate_change start — flush this thread's telemetry buffer before the worker dies. + // The worker loads its own instance of the Telemetry module (separate buffer from the main + // thread), and server-side events — gateway auth, project scan, sample setup, session + // events — land here. cli/cmd/tui.ts terminates the worker immediately after this RPC + // returns, so anything still buffered is lost. + // + // Bounded at 2s: the caller allows 5s total for this RPC and the trace drain above already + // claims up to 2s of it, so an unbounded flush (up to REQUEST_TIMEOUT_MS = 10s) would be + // cut off mid-request by worker.terminate() anyway. The bound is applied inside shutdown() + // rather than by racing a timer here — racing neither cancels the flush nor clears its timer. + try { + await Telemetry.shutdown({ timeoutMs: 2000 }) + } catch { + // Telemetry must never block worker shutdown. + } + // altimate_change end await InstanceRuntime.disposeAllInstances() if (server) await server.stop(true) }, diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 75bc2de9bc..ec435d535e 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -21,6 +21,10 @@ import PROMPT_CONFIGURE_CODEX from "./template/configure-codex.txt" import PROMPT_DISCOVER_MCPS from "./template/discover-and-add-mcps.txt" import PROMPT_FEEDBACK from "./template/feedback.txt" // altimate_change end +// altimate_change start — Part 2 onboarding: scan-gate orchestration (invokes the +// existing /discover flow on the found path; discover.txt is unchanged) +import PROMPT_ONBOARD_CONNECT from "./template/onboard-connect.txt" +// altimate_change end type State = { commands: Record @@ -77,6 +81,7 @@ export const Default = { CONFIGURE_CODEX: "configure-codex", DISCOVER_MCPS: "discover-and-add-mcps", MCPS: "mcps", + ONBOARD_CONNECT: "onboard-connect", // altimate_change end } as const @@ -121,6 +126,19 @@ export const layer = Layer.effect( hints: hints(PROMPT_DISCOVER), } // altimate_change end + // altimate_change start — Part 2 scan gate (invoked programmatically by the + // TUI gate with "scan"/"skip"; hidden from the slash menu in autocomplete) + commands[Default.ONBOARD_CONNECT] = { + name: Default.ONBOARD_CONNECT, + description: "onboarding: scan environment and connect (scan|skip)", + source: "command", + subtask: false, + get template() { + return PROMPT_ONBOARD_CONNECT + }, + hints: hints(PROMPT_ONBOARD_CONNECT), + } + // altimate_change end commands[Default.REVIEW] = { name: Default.REVIEW, description: "review changes [commit|branch|pr], defaults to uncommitted", diff --git a/packages/opencode/src/command/template/onboard-connect.txt b/packages/opencode/src/command/template/onboard-connect.txt new file mode 100644 index 0000000000..7ddf623dda --- /dev/null +++ b/packages/opencode/src/command/template/onboard-connect.txt @@ -0,0 +1,229 @@ +You are guiding a data engineer immediately after they finished setting up an AI +model (Part 1 of onboarding). Chat is now live. The user has just answered a +Yes/No "Scan your environment?" gate; the argument below is their answer. Act on +it. Never end any branch on a bare report of absence — every outcome converts +into a connection, a question, or a concrete next action. + +Present every menu and question as plain text in your reply, then wait for the +user to type their answer. Do NOT use the `question` tool (or any interactive +picker) for any step in this flow. + +Argument: $ARGUMENTS + +── If the argument is "skip" (the user declined the scan) ── +Do NOT scan. Respond with the opener AND the activation menu in the same +reply — the menu is the primary next action, the opener frames it: + + No problem. What are you working on — a dbt project, a specific warehouse, or + just exploring? I'll help set it up when you're ready. + +Then immediately append the "What would you like to do?" activation menu +described below (no-data variant). Do not wait for a free-text answer to +the "what are you working on" question before showing the menu — the two +together give the user both an open door and a concrete numbered choice. + +If the user replies with a free-text answer instead of picking a number, +handle it directly (a named warehouse → `warehouse_add`; a dbt project → +offer /discover; "just exploring" → invite them to pick a menu number). +If they pick a numbered option, follow the routing table below. + +── If the argument is "scan" ── +Call the `project_scan` tool exactly once (read-only local inspection; nothing +leaves the machine). From its result, compute: +- hasDbt = a dbt project was found +- hasWarehouse = at least one warehouse connection was found from ANY source + (already-configured, dbt profile, Docker container, or environment variable) +- isRepo = the current directory is a git repository + +Then take the FIRST matching branch: + +1. hasWarehouse is true (a warehouse exists, with or without dbt) — FOUND: + The environment is workable. Continue exactly as the /discover command does + from here — you ALREADY have the scan results, so do not scan again. Present + what was found in a friendly summary; for each NEW connection discovered, offer + to add it with `warehouse_add` and verify with `warehouse_test`; offer + `schema_index` for any connected-but-unindexed warehouse; then show concrete + next steps. (This is the repo's standard discovery flow.) + +2. hasDbt is true AND hasWarehouse is false — dbt project, no warehouse: + A very common state (the project is here; the warehouse credentials live + elsewhere). Do not treat it as "nothing found." Say: + + Found your dbt project, but no warehouse connection yet. Which warehouse does + it run against — Snowflake, BigQuery, Databricks, something else? I'll walk + you through connecting it. + + When they name one, call `warehouse_add` for that type and guide them through it. + +3. hasDbt is false AND hasWarehouse is false AND isRepo is true — not in a dbt project: + Most likely they launched one directory up. Say: + + I didn't find a dbt project in this folder. /discover looks up from where you + launched — if your project is elsewhere, cd into it and run /discover again, + or tell me the path. + +4. hasDbt is false AND hasWarehouse is false AND isRepo is false — genuinely nothing yet: + Say: + + Nothing to connect here yet. When you've got a dbt project or warehouse handy, + run /discover and I'll pick it up. Meanwhile I can explain a concept, review + SQL you paste, or scaffold a project — want to try one? + +── Activation menu (ALWAYS — after every branch above, both scan and skip) ── +End your reply with a short numbered "What would you like to do?" menu in JOB +language (never slash commands in the labels). Compose it for what the CURRENT +environment can actually do: + +If a REAL warehouse is connected (found by the scan, or just added): + Personalize the lead-in from the scan results (e.g. "You've got 12 dbt models + and a Snowflake connection. Want to:"), then offer: + 1. See what breaks downstream before you change a model + 2. Review a SQL PR with every finding explained + 3. Find what's driving warehouse cost + 4. Something else — describe it + +If NOTHING usable was found, or the user declined the scan: + Lead with the sample, then the stack-agnostic jobs: + 1. Try Altimate on a sample dbt project (spins up a small jaffle-shop DuckDB — + real data, real dbt models, nothing touches your warehouse) + 2. See what breaks downstream before you change a model + 3. Review a SQL PR with every finding explained + 4. Something else — describe it + +If a dbt project exists but no warehouse yet (branch 2): ask the warehouse +question first as instructed above, then append this same no-data menu (the +sample option included) so declining the warehouse still leaves a next step. + +Routing — selecting a job STARTS the job (this is the user's first activation +moment, not another menu): +- "Try Altimate on a sample dbt project" → call the `sample_setup` tool + (with no arguments — defaults are correct). The tool's `output` starts + with a `status:` line — read it. The full shape is: + + status: ok + path: + reused: true|false + suffix: 0 | | + note: + + or, on failure: + + status: error + reason: + + + + Branch on that text in this order: + + a. First line is `status: error` → Show the message after the blank + line verbatim to the user. That message names the cause and the + next command (unwritable HOME, unsafe HOME, missing shipped + assets). Do not present the sample menu; do not retry silently. + + b. `status: ok` AND `reused: true` AND `note:` contains "Caller must + prompt" → An older version of the sample already exists at `path`. + Ask the user: "You have a sample at from an earlier CLI + version. Reset it in place (any local edits lost), keep it as-is, + or install the new version alongside (your old copy stays where + it is)?" Wait for a clear answer. + - Reset → call `sample_setup` again with + `allow_in_place_upgrade: true` + - Keep as-is → do NOT call sample_setup again; present the + SAMPLE menu below (they'll be working against + the existing older-version sample) + - Install alongside → call `sample_setup` again with + `install_alongside: true`. The tool will + materialize the new version into `-2` + (or the next free suffix). Do NOT re-route + through branch (d) — its "existing directory + wasn't ours" wording is wrong here, because the + existing directory IS ours (an older version) + and the user explicitly kept it. Instead say: + "Installed the new sample alongside your + existing copy at . Your older copy at + is untouched." Then present the + SAMPLE menu below. + + c. `status: ok` AND `reused: true` (any other note) → Existing sample + reused. Say "Your sample is already set up at ." Then + present the SAMPLE menu below. + + d. `status: ok` AND `reused: false` AND `suffix:` is anything other + than `0` (a number like `1` or a hex string like `a1b2c3`) → The + preferred name was taken by unrelated content; the sample landed + at the suffixed path (see `path:`). Say: "Materialized the sample + at (your existing directory wasn't ours, so I put it + alongside)." Then present the SAMPLE menu. + + e. `status: ok` AND `reused: false` AND `suffix: 0` → Clean fresh + materialize. Say: "Sample project created at ." Then + present the SAMPLE menu. + + SAMPLE menu (only reached in branches c/d/e above — jobs this + environment can actually satisfy): + 1. See what breaks downstream before you change a model (try customers or orders) + 2. Review the SQL in this project with every finding explained + 3. Build & query it — run the models and tests, then ask questions of the data + 4. Something else — describe it + Do NOT offer warehouse cost analysis on the sample — the DuckDB has no cost data. +- "See what breaks downstream…" → invoke the `dbt-analyze` skill on the current + project; if the user hasn't named a model, suggest one from the scan/sample. +- "Review a SQL PR…" → invoke the `sql-review` skill on the changes or files the + user points at (on the sample: review the mart models). +- "Find what's driving warehouse cost" → invoke the `cost-report` skill. Real + warehouses only — never on the sample. +- "Build & query it" (sample) → read the `dbt:` line from the earlier + `sample_setup` output (it looked like `dbt: present (dbt-core 1.11.8, + duckdb-adapter present)` or `dbt: missing (dbt-core not on PATH)` or + `duckdb-adapter missing`). If that line says the adapter is missing or + dbt itself is missing, do NOT try to install anything on the user's + behalf. Say exactly: + + Building the sample needs the dbt CLI + the DuckDB adapter. Two options: + 1. If you already have dbt installed somewhere, paste the path to the + `dbt` binary (e.g. `/Users/you/venvs/dbt/bin/dbt`) and I'll use it + from there — no reinstall needed. + 2. Install fresh: run `pip install dbt-duckdb` (grabs both dbt-core and + the DuckDB adapter), then say "ready" and I'll continue. + + If the user pastes a path (option 1), do NOT interpolate the pasted + string into a shell pipeline verbatim — a paste like `; rm -rf ~` would + execute. Validate first, then always run through a single-quoted argv: + 1. Refuse if the path contains any of `; & | > < $ ` ( ) { } newline` + or any single-quote. Ask the user to paste again. (Matched-pair + single quotes still break the single-quote wrap below — `'a'b'c'` + leaves `b` unquoted — so ANY single-quote is refused, not just + unmatched ones.) + 2. Refuse if the file at that path is not executable + (`test -x ''` — quote the path). + 3. Verify it's a dbt-duckdb binary by running the SINGLE-QUOTED + command `'' --version 2>&1` via bash and checking that its + stdout contains a line starting with `- duckdb:`. Do NOT chain + with `grep -q` on user-controlled strings. + 4. If verified, remember the path and use `'' build` (single- + quoted) for the build step below. Never pass the path unquoted. + If they install fresh (option 2) and say "ready", call `sample_setup` + again — the fresh call re-probes with `{ force: true }` and its `dbt:` + line will now say `present`. Only shell out if you have to; the tool's + probe is the source of truth. + + Once dbt is available, run `dbt build` (or the validated `'' build` + from above, with the path single-quoted) in the sample project dir via + bash and report the PASS/FAIL counts truthfully. Before + running any queries, call the `dbt-profiles` tool with `projectDir` + pointed at the sample dir to discover the DuckDB profile. The profile + reports the DuckDB path as `target/jaffle.duckdb` — a RELATIVE path + (dbt-duckdb resolves it against `dbt build`'s working directory, so it + landed at `/target/jaffle.duckdb`). If you pass that + relative string straight to `warehouse_add`, `sql_execute` will resolve + it against ITS own cwd and open (or create) an empty duckdb file + wherever the CLI happens to be running from — which is exactly the + bug that surfaced. Absolutize first: join the sample path from the + earlier `sample_setup` output with the profile's `path:` value, then + pass the ABSOLUTE path (`/target/jaffle.duckdb`) to + `warehouse_add` so `sql_execute` connects to the database dbt just + built. Then offer a first query and run it with `sql_execute`. +- "Something else — describe it" → just ask what they're working on; free chat. + +Keep the tone calm and honest: the scan only reads local files the user already +has; the real credential ask comes later, only when connecting a specific warehouse. diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index fcb2dff677..fe1cb9abc6 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -30,6 +30,9 @@ import { DatabricksAuthPlugin } from "../altimate/plugin/databricks" // altimate_change start — altimate backend auth plugin import { AltimateAuthPlugin } from "../altimate/plugin/altimate" // altimate_change end +// altimate_change start — onboarding activation-funnel telemetry plugin +import { OnboardingTelemetryPlugin } from "../altimate/plugin/onboarding-telemetry" +// altimate_change end // altimate_change start — wire plugin experimental_workspace.register into the // control-plane adapter registry consumed by control-plane/workspace.ts import { registerAdapter } from "../control-plane/adapters" @@ -60,6 +63,7 @@ export namespace Plugin { SnowflakeCortexAuthPlugin, DatabricksAuthPlugin, AltimateAuthPlugin, + OnboardingTelemetryPlugin, ] // altimate_change end diff --git a/packages/opencode/src/plugin/tui/altimate/provider-credentials.tsx b/packages/opencode/src/plugin/tui/altimate/provider-credentials.tsx index a4bbeb14a4..9dc4741945 100644 --- a/packages/opencode/src/plugin/tui/altimate/provider-credentials.tsx +++ b/packages/opencode/src/plugin/tui/altimate/provider-credentials.tsx @@ -81,6 +81,20 @@ function show(api: TuiPluginApi) { api.ui.dialog.replace(() => ) } +// altimate_change start — /logout: clear the stored gateway credential. Self-contained +// (dispatched from the packages/tui slash command in app.tsx) since AltimateApi is +// opencode-side and unreachable from packages/tui. +async function logout(api: TuiPluginApi) { + try { + await AltimateApi.clearCredentials() + await api.client.instance.dispose() + api.ui.toast({ variant: "success", message: "Signed out of Altimate LLM Gateway" }) + } catch (err) { + api.ui.toast({ variant: "error", message: err instanceof Error ? err.message : "Sign-out failed" }) + } +} +// altimate_change end + const tui: TuiPlugin = async (api) => { api.keymap.registerLayer({ commands: [ @@ -93,8 +107,22 @@ const tui: TuiPlugin = async (api) => { show(api) }, }, + // altimate_change start — /logout entry point, dispatched by packages/tui/src/app.tsx + { + name: "altimate.provider.logout", + title: "Sign out of Altimate LLM Gateway", + category: "Altimate", + namespace: "palette", + run() { + void logout(api) + }, + }, + // altimate_change end ], - bindings: api.tuiConfig.keybinds.gather("altimate.palette", ["altimate.provider.connect"]), + bindings: api.tuiConfig.keybinds.gather("altimate.palette", [ + "altimate.provider.connect", + "altimate.provider.logout", + ]), }) } diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 289bd8a748..da3bed08da 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -75,6 +75,7 @@ import { Tracer } from "../altimate/observability/tracing" import { stampRegistryToolSource, describeMcpTool } from "../altimate/tool-source" // altimate_change end import { Telemetry } from "@/telemetry" // altimate_change — session telemetry +import * as OnboardingTelemetry from "@/altimate/telemetry/onboarding" // altimate_change — onboarding funnel // @ts-ignore globalThis.AI_SDK_LOG_WARNINGS = false @@ -1010,6 +1011,26 @@ export namespace SessionPrompt { } } // altimate_change end — task intent classification + // altimate_change start — onboarding funnel: first free-text prompt. + // + // Three guards, each load-bearing: + // - `step === 1` (the enclosing block) is NOT "first message of the session". `step` + // is declared inside loop() and loop() runs once per user turn, so this block runs + // on every turn. `claimFirstPrompt` is what makes this once-per-session. + // - `isOnboardingSession` scopes it to the funnel. Without it, an onboarding-taxonomy + // event would fire for every session in the product — TUI, `run`, GitHub, API. + // - `consumeCommandSubmission` excludes slash commands, because the scan gate submits + // a hidden `/onboard-connect` as an ordinary user message: it would otherwise be + // recorded as the user's first typed prompt in every fresh onboarding. + const fromCommand = OnboardingTelemetry.consumeCommandSubmission(sessionID) + if ( + !fromCommand && + OnboardingTelemetry.isOnboardingSession(sessionID) && + OnboardingTelemetry.claimFirstPrompt(sessionID) + ) { + void OnboardingTelemetry.emit({ type: "first_prompt_sent" }, sessionID) + } + // altimate_change end // altimate_change end — session start telemetry } diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 17236cb398..c006cd406a 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -128,6 +128,7 @@ import { ToolLookupTool } from "../altimate/tools/tool-lookup" import { ProjectScanTool } from "../altimate/tools/project-scan" import { DatamateManagerTool } from "../altimate/tools/datamate" import { FeedbackSubmitTool } from "../altimate/tools/feedback-submit" +import { SampleSetupTool } from "../altimate/tools/sample-setup" // altimate_change end // altimate_change start - import altimate persistent memory tools @@ -460,6 +461,7 @@ export namespace ToolRegistry { ProjectScanTool, DatamateManagerTool, FeedbackSubmitTool, + SampleSetupTool, // altimate_change end // altimate_change start - register altimate persistent memory tools ...(!Flag.ALTIMATE_DISABLE_MEMORY diff --git a/packages/opencode/test/altimate/onboarding/marker.test.ts b/packages/opencode/test/altimate/onboarding/marker.test.ts new file mode 100644 index 0000000000..6297d813da --- /dev/null +++ b/packages/opencode/test/altimate/onboarding/marker.test.ts @@ -0,0 +1,262 @@ +/** + * marker.ts — the on-disk `.altimate-sample.json` sentinel that decides + * whether the starter-sample materializer can reuse / upgrade / suffix / + * refuse a candidate target directory. + * + * Test surface targets the four `classifyTarget()` verdicts + the + * `findSafeTarget()` suffix-hunt (numeric loop → randomized fallback), + * plus the `checkParentWritable()` pre-check that codex flagged as needing + * its own contract. + */ + +import { describe, expect, test } from "bun:test" +import fs from "node:fs" +import os from "node:os" +import path from "node:path" +import { + MARKER_FILE_NAME, + MARKER_KIND, + checkParentWritable, + classifyTarget, + findSafeTarget, + readMarker, + writeMarker, + type SampleMarker, +} from "../../../src/altimate/onboarding/marker" + +function makeTmp(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)) +} + +function makeMarker(overrides: Partial = {}): SampleMarker { + return { + kind: MARKER_KIND, + sampleName: "jaffle-shop-duckdb", + version: "1.0.0", + materializedAt: "2026-07-24T12:00:00.000Z", + cliVersion: "0.9.4", + ...overrides, + } +} + +describe("readMarker + writeMarker round-trip", () => { + test("write then read returns the same marker shape", () => { + const dir = makeTmp("marker-rt-") + const marker = makeMarker() + writeMarker(dir, marker) + const readBack = readMarker(dir) + expect(readBack).toEqual(marker) + }) + + test("readMarker returns undefined when the file is missing", () => { + const dir = makeTmp("marker-missing-") + expect(readMarker(dir)).toBeUndefined() + }) + + test("readMarker returns undefined on unparseable JSON", () => { + const dir = makeTmp("marker-badjson-") + fs.writeFileSync(path.join(dir, MARKER_FILE_NAME), "{not-json") + expect(readMarker(dir)).toBeUndefined() + }) + + test("readMarker rejects a payload with wrong `kind` (guards against a user's ordinary .json in the dir being mistaken for our marker)", () => { + const dir = makeTmp("marker-wrongkind-") + fs.writeFileSync( + path.join(dir, MARKER_FILE_NAME), + JSON.stringify({ kind: "some-other-tool", sampleName: "x", version: "1", materializedAt: "", cliVersion: "" }), + ) + expect(readMarker(dir)).toBeUndefined() + }) + + test("readMarker rejects a payload missing required string fields", () => { + const dir = makeTmp("marker-shortfield-") + fs.writeFileSync( + path.join(dir, MARKER_FILE_NAME), + JSON.stringify({ kind: MARKER_KIND, sampleName: "x" }), // missing version, materializedAt, cliVersion + ) + expect(readMarker(dir)).toBeUndefined() + }) +}) + +describe("classifyTarget — the four decision-table branches", () => { + test("branch: dir does not exist → empty", () => { + const parent = makeTmp("classify-notexist-") + const target = path.join(parent, "does-not-exist") + expect(classifyTarget(target, "1.0.0", "jaffle-shop-duckdb")).toEqual({ kind: "empty" }) + }) + + test("branch: dir exists but is empty → empty", () => { + const target = makeTmp("classify-emptydir-") + expect(classifyTarget(target, "1.0.0", "jaffle-shop-duckdb")).toEqual({ kind: "empty" }) + }) + + test("branch: target is a file, not a directory → unknown-dir", () => { + const parent = makeTmp("classify-filepath-") + const target = path.join(parent, "some-file") + fs.writeFileSync(target, "hello") + const result = classifyTarget(target, "1.0.0", "jaffle-shop-duckdb") + expect(result.kind).toBe("unknown-dir") + }) + + test("branch: our marker at requested version → our-sample-current", () => { + const dir = makeTmp("classify-current-") + writeMarker(dir, makeMarker({ version: "1.0.0" })) + const result = classifyTarget(dir, "1.0.0", "jaffle-shop-duckdb") + expect(result.kind).toBe("our-sample-current") + if (result.kind === "our-sample-current") { + expect(result.marker.version).toBe("1.0.0") + expect(result.path).toBe(dir) + } + }) + + test("branch: our marker at different version → our-sample-different-version", () => { + const dir = makeTmp("classify-diffver-") + writeMarker(dir, makeMarker({ version: "1.0.0" })) + const result = classifyTarget(dir, "1.0.1", "jaffle-shop-duckdb") + expect(result.kind).toBe("our-sample-different-version") + if (result.kind === "our-sample-different-version") { + expect(result.marker.version).toBe("1.0.0") + } + }) + + test("branch: non-empty dir with NO marker → unknown-dir (never overwrite)", () => { + const dir = makeTmp("classify-unknown-") + fs.writeFileSync(path.join(dir, "unrelated.txt"), "something the user had") + const result = classifyTarget(dir, "1.0.0", "jaffle-shop-duckdb") + expect(result.kind).toBe("unknown-dir") + if (result.kind === "unknown-dir") { + expect(result.reason).toContain("no altimate-code marker") + } + }) + + test("branch: non-empty dir with wrong-kind marker → unknown-dir", () => { + const dir = makeTmp("classify-wrongkind-") + fs.writeFileSync( + path.join(dir, MARKER_FILE_NAME), + JSON.stringify({ kind: "other-tool", sampleName: "x", version: "1", materializedAt: "", cliVersion: "" }), + ) + const result = classifyTarget(dir, "1.0.0", "jaffle-shop-duckdb") + expect(result.kind).toBe("unknown-dir") + }) + + test("branch: our marker but DIFFERENT sampleName → unknown-dir (cubic P1: don't reuse a different sample) (cubic P1 #1)", () => { + // The marker was written by an altimate-code CLI for sample-A. We're + // asking about sample-B. Even if the version happens to match, this + // is not "ours" for THIS request — must fall into the suffix + // escalation path, not silently reuse or in-place-upgrade. + const dir = makeTmp("classify-diff-sample-") + writeMarker(dir, makeMarker({ sampleName: "other-sample", version: "1.0.0" })) + const result = classifyTarget(dir, "1.0.0", "jaffle-shop-duckdb") + expect(result.kind).toBe("unknown-dir") + if (result.kind === "unknown-dir") { + expect(result.reason).toContain("belongs to sample 'other-sample'") + } + }) + + test("branch: symlinked directory → unknown-dir (codex NEW-21 — lstat, don't follow)", () => { + // Pre-seed a symlink pointing at a REAL dir with a valid marker. + // If classifyTarget follows the link, it would return + // our-sample-current and (in the outer flow) authorize a + // destructive overwrite of the linked-to content. lstat should catch + // it as a symlink and classify unknown-dir. + const linkTarget = makeTmp("classify-symlink-target-") + writeMarker(linkTarget, makeMarker({ version: "1.0.0" })) + const parent = makeTmp("classify-symlink-parent-") + const link = path.join(parent, "our-sample") + fs.symlinkSync(linkTarget, link) + const result = classifyTarget(link, "1.0.0", "jaffle-shop-duckdb") + expect(result.kind).toBe("unknown-dir") + if (result.kind === "unknown-dir") { + expect(result.reason).toContain("symlink") + } + }) + + test("branch: unreadable directory (chmod 000) → unknown-dir with EACCES-flavored reason", () => { + // Skip on root — chmod restrictions don't apply. + if (typeof process.getuid !== "function" || process.getuid() === 0) return + const dir = makeTmp("classify-unreadable-") + fs.writeFileSync(path.join(dir, "some-content"), "x") + fs.chmodSync(dir, 0o000) + try { + const result = classifyTarget(dir, "1.0.0", "jaffle-shop-duckdb") + expect(result.kind).toBe("unknown-dir") + if (result.kind === "unknown-dir") { + expect(result.reason.toLowerCase()).toMatch(/unreadable|permission|eacces/) + } + } finally { + // Restore so tmp cleanup can traverse it. + try { fs.chmodSync(dir, 0o755) } catch { /* ignore */ } + } + }) +}) + +describe("findSafeTarget — suffix hunt + randomized fallback", () => { + test("preferred slot empty → returns suffix 0 at the preferred path", () => { + const parent = makeTmp("safe-fresh-") + const result = findSafeTarget(parent, "altimate-sample-dbt", "1.0.0", "jaffle-shop-duckdb") + expect(result.suffix).toBe(0) + expect(result.path).toBe(path.join(parent, "altimate-sample-dbt")) + expect(result.state.kind).toBe("empty") + }) + + test("preferred slot holds unrelated content → returns -2 suffix", () => { + const parent = makeTmp("safe-collide-") + const preferredPath = path.join(parent, "altimate-sample-dbt") + fs.mkdirSync(preferredPath) + fs.writeFileSync(path.join(preferredPath, "unrelated.txt"), "user's stuff") + const result = findSafeTarget(parent, "altimate-sample-dbt", "1.0.0", "jaffle-shop-duckdb") + expect(result.suffix).toBe(1) + expect(result.path).toBe(path.join(parent, "altimate-sample-dbt-2")) + }) + + test("preferred slot holds OUR sample at same version → returns suffix 0 with 'our-sample-current' state", () => { + const parent = makeTmp("safe-reuse-") + const preferredPath = path.join(parent, "altimate-sample-dbt") + fs.mkdirSync(preferredPath) + writeMarker(preferredPath, makeMarker({ version: "1.0.0" })) + const result = findSafeTarget(parent, "altimate-sample-dbt", "1.0.0", "jaffle-shop-duckdb") + expect(result.suffix).toBe(0) + expect(result.state.kind).toBe("our-sample-current") + }) + + test("preferred slot holds OUR sample at different version → returns suffix 0 with 'different-version' state (caller decides upgrade vs new slot)", () => { + const parent = makeTmp("safe-diffver-") + const preferredPath = path.join(parent, "altimate-sample-dbt") + fs.mkdirSync(preferredPath) + writeMarker(preferredPath, makeMarker({ version: "0.9.0" })) + const result = findSafeTarget(parent, "altimate-sample-dbt", "1.0.0", "jaffle-shop-duckdb") + expect(result.suffix).toBe(0) + expect(result.state.kind).toBe("our-sample-different-version") + }) + + test("all N numeric slots taken → randomized fallback returns a string suffix", () => { + const parent = makeTmp("safe-random-") + // Poison the first 3 candidate slots with unrelated content so the + // numeric loop cannot land, forcing the randomized fallback path. + for (const suffix of ["", "-2", "-3"]) { + const dir = path.join(parent, `altimate-sample-dbt${suffix}`) + fs.mkdirSync(dir) + fs.writeFileSync(path.join(dir, "unrelated.txt"), "user's stuff") + } + const result = findSafeTarget(parent, "altimate-sample-dbt", "1.0.0", "jaffle-shop-duckdb", 3) + expect(typeof result.suffix).toBe("string") + // Random suffix is 6 hex chars per the impl. + expect(result.suffix).toMatch(/^[0-9a-f]{6}$/) + expect(result.state.kind).toBe("empty") + expect(result.path).toBe(path.join(parent, `altimate-sample-dbt-${result.suffix}`)) + }) +}) + +describe("checkParentWritable — the pre-check codex asked for", () => { + test("writable parent returns undefined", () => { + const parent = makeTmp("writable-") + expect(checkParentWritable(parent)).toBeUndefined() + }) + + test("nonexistent parent returns a specific error message", () => { + const parent = "/definitely/does/not/exist/on/this/machine" + const err = checkParentWritable(parent) + expect(err).toBeDefined() + expect(err).toContain("not writable") + }) +}) diff --git a/packages/opencode/test/altimate/onboarding/materialize.test.ts b/packages/opencode/test/altimate/onboarding/materialize.test.ts new file mode 100644 index 0000000000..901569b824 --- /dev/null +++ b/packages/opencode/test/altimate/onboarding/materialize.test.ts @@ -0,0 +1,623 @@ +/** + * materialize.ts — copies the shipped starter sample onto the user's + * filesystem with a marker-based conflict policy and unsafe-HOME guard. + * + * These tests exercise the real materializer against the real shipped + * sample source at packages/opencode/sample-projects/jaffle-shop-duckdb/ + * — verifies whitelisted files land, marker is written, DuckDB profile + * is intact, reuse is correctly detected on second call, unsafe HOME + * paths are refused with actionable messages. + */ + +import { describe, expect, test } from "bun:test" +import fs from "node:fs" +import os from "node:os" +import path from "node:path" +import { materializeSample, rejectUnsafeHome } from "../../../src/altimate/onboarding/materialize" +import { MARKER_FILE_NAME, MARKER_KIND, readMarker } from "../../../src/altimate/onboarding/marker" + +const SAMPLE_VERSION = "1.0.0" +const CLI_VERSION = "0.9.4-test" + +function makeTmpParent(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)) +} + +describe("rejectUnsafeHome — codex-flagged HOME hygiene guard", () => { + test("undefined HOME → refused", () => { + expect(rejectUnsafeHome(undefined)).toContain("not set") + }) + + test("empty string HOME → refused", () => { + expect(rejectUnsafeHome("")).toContain("not set") + }) + + test("HOME='/' → refused", () => { + expect(rejectUnsafeHome("/")).toContain("not a usable") + }) + + test("HOME='/tmp/something' → refused (ephemeral)", () => { + expect(rejectUnsafeHome("/tmp/xyz")).toContain("ephemeral") + }) + + test("normal HOME → allowed (returns undefined)", () => { + expect(rejectUnsafeHome("/Users/somebody")).toBeUndefined() + expect(rejectUnsafeHome("/home/somebody")).toBeUndefined() + }) + + // /root is safe when the process IS running as root; only refused when + // uid != 0. Skip on macOS where getuid() behavior is CI-dependent. + test("HOME='/root' with non-root uid → refused (guards against sudo npm install)", () => { + if (typeof process.getuid !== "function" || process.getuid() === 0) return + const err = rejectUnsafeHome("/root") + expect(err).toBeDefined() + expect(err).toContain("sudo") + }) + + // Canonicalization bypass — codex sweep NEW-1/NEW-4. On macOS, `/tmp` + // is a symlink to `/private/tmp`, so a caller passing `/private/tmp/foo` + // would slip past the raw `startsWith('/tmp/')` check. Assert we + // realpath first and STILL reject. + test("canonicalized-tmp path (macOS /private/tmp) → refused (bypass of raw string prefix check)", () => { + if (process.platform !== "darwin") return // /private/tmp is macOS-specific + // Sanity: /tmp really is a symlink to /private/tmp on this box; if not, + // the assertion below wouldn't prove anything. + let target = "" + try { target = fs.realpathSync("/tmp") } catch { return } + if (target !== "/private/tmp") return + const err = rejectUnsafeHome("/private/tmp/some-scratch-dir") + expect(err, "canonicalized /private/tmp path bypassed rejectUnsafeHome — realpath check missing").toBeDefined() + expect(err).toMatch(/tmp/) + }) +}) + +describe("materializeSample — happy path", () => { + test("fresh materialize copies the sample files and writes a marker", async () => { + const parent = makeTmpParent("materialize-fresh-") + const result = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + + expect(result.reused).toBe(false) + expect(result.suffix).toBe(0) + expect(result.targetPath).toBe(path.join(parent, "starter")) + + // Whitelisted files present. + const expectedFiles = [ + "README.md", + "dbt_project.yml", + "profiles.yml", + "sample-manifest.json", + "models/staging/stg_customers.sql", + "models/staging/schema.yml", + "models/marts/customers.sql", + "models/marts/schema.yml", + "seeds/raw_customers.csv", + "seeds/raw_orders.csv", + "target/manifest.json", + ] + for (const rel of expectedFiles) { + expect(fs.existsSync(path.join(result.targetPath, rel))).toBe(true) + } + + // Marker was written and reads back correctly. + const marker = readMarker(result.targetPath) + expect(marker).toBeDefined() + expect(marker!.kind).toBe(MARKER_KIND) + expect(marker!.sampleName).toBe("jaffle-shop-duckdb") + expect(marker!.version).toBe(SAMPLE_VERSION) + expect(marker!.cliVersion).toBe(CLI_VERSION) + + // profiles.yml still declares the DuckDB target — codex fix #3 asserts + // the shipped-source-copy did not silently drop this critical file. + const profiles = fs.readFileSync(path.join(result.targetPath, "profiles.yml"), "utf8") + expect(profiles).toContain("type: duckdb") + expect(profiles).toContain("target/jaffle.duckdb") + + // target/manifest.json must be REHYDRATED at copy time — the shipped + // artifact carries {{SAMPLE_ROOT}} / {{SAMPLE_ROOT_PARENT}} sentinels + // in every path field so a single committed manifest works for every + // materialization target. If copySampleTree ships it byte-for-byte, + // /discover and /review get paths like + // "{{SAMPLE_ROOT}}/models/staging/stg_customers.sql" and choke. This + // asserts the sentinels were replaced with the real target path + // BEFORE the file landed in the user's home. + const manifest = fs.readFileSync(path.join(result.targetPath, "target/manifest.json"), "utf8") + expect(manifest, "materialized manifest.json contains {{SAMPLE_ROOT}} — rehydration in copySampleTree is not running").not.toContain("{{SAMPLE_ROOT}}") + expect(manifest, "materialized manifest.json contains {{SAMPLE_ROOT_PARENT}} — rehydration is missing the parent sentinel").not.toContain("{{SAMPLE_ROOT_PARENT}}") + // Positive assertion: the materialized target path appears at least + // once (in root_path or original_file_path fields). + expect(manifest).toContain(result.targetPath) + }) +}) + +describe("materializeSample — conflict policy", () => { + test("second call to same target reuses existing sample (no re-copy, no marker rewrite)", async () => { + const parent = makeTmpParent("materialize-reuse-") + const first = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + const originalMaterializedAt = readMarker(first.targetPath)!.materializedAt + // Small sleep so we can distinguish materializedAt values if a rewrite + // happens — reuse must NOT rewrite the marker. + await new Promise((r) => setTimeout(r, 20)) + const second = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + expect(second.reused).toBe(true) + expect(second.targetPath).toBe(first.targetPath) + expect(readMarker(second.targetPath)!.materializedAt).toBe(originalMaterializedAt) + }) + + test("preferred target holds unrelated content → suffix -2 slot used, unrelated content untouched", async () => { + const parent = makeTmpParent("materialize-collide-") + const preferred = path.join(parent, "starter") + fs.mkdirSync(preferred) + fs.writeFileSync(path.join(preferred, "user-file.txt"), "important, do not touch") + const result = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + expect(result.suffix).toBe(1) + expect(result.targetPath).toBe(path.join(parent, "starter-2")) + // User's original file still there, untouched. + expect(fs.readFileSync(path.join(preferred, "user-file.txt"), "utf8")).toBe("important, do not touch") + }) + + test("second call after in-place upgrade (bumped sampleVersion) refuses in-place unless allowInPlaceUpgrade=true", async () => { + const parent = makeTmpParent("materialize-upgrade-") + const first = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: "1.0.0", + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + const second = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: "1.0.1", + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + // allowInPlaceUpgrade NOT set — impl should return reused-with-note. + }) + // Same path, "reused" reported so caller sees the state and prompts. + expect(second.targetPath).toBe(first.targetPath) + expect(second.reused).toBe(true) + expect(second.note).toContain("Caller must prompt") + }) + + test("in-place upgrade path rewrites files + updates marker version", async () => { + const parent = makeTmpParent("materialize-upgrade-ok-") + await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: "1.0.0", + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + const upgraded = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: "1.0.1", + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + allowInPlaceUpgrade: true, + }) + expect(upgraded.reused).toBe(false) + expect(readMarker(upgraded.targetPath)!.version).toBe("1.0.1") + }) + + test("installAlongside path materializes new version into starter-2, leaves old starter intact (codex #16)", async () => { + const parent = makeTmpParent("materialize-alongside-") + // Prior run: version 1.0.0 in slot 0. + const first = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: "1.0.0", + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + const oldPath = first.targetPath + const oldMarker = readMarker(oldPath)! + expect(oldMarker.version).toBe("1.0.0") + // Install 1.0.1 ALONGSIDE — should skip slot 0 (version mismatch), + // materialize into starter-2, leave starter/ untouched. + const alongside = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: "1.0.1", + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + installAlongside: true, + }) + expect(alongside.reused).toBe(false) + expect(alongside.suffix).toBe(1) // slot 1 = -2 + expect(alongside.targetPath).toBe(path.join(parent, "starter-2")) + // Old marker/dir untouched. + expect(fs.existsSync(oldPath)).toBe(true) + expect(readMarker(oldPath)!.version).toBe("1.0.0") + expect(readMarker(oldPath)!.materializedAt).toBe(oldMarker.materializedAt) + // New marker at the alongside path. + expect(readMarker(alongside.targetPath)!.version).toBe("1.0.1") + }) +}) + +describe("materializeSample — failure modes", () => { + test("unsafe HOME (unset targetParent + HOME=/tmp) → refuses with actionable error", async () => { + // Simulate the unsafe-HOME path by pointing targetParent at /tmp/x + // directly (bypasses the opts.targetParent short-circuit? Actually + // opts.targetParent set → skips rejectUnsafeHome. To exercise the + // guard we need to omit targetParent and control os.homedir(). We + // spy on os.homedir instead. + const origHomedir = os.homedir + Object.defineProperty(os, "homedir", { value: () => "/tmp/xyz-unsafe", configurable: true }) + try { + // NO allowUnsafeParent — this test EXISTS to prove the guard fires + // when the defaulted targetParent falls on an ephemeral path. + await expect( + materializeSample({ + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + }), + ).rejects.toThrow(/ephemeral/) + } finally { + Object.defineProperty(os, "homedir", { value: origHomedir, configurable: true }) + } + }) + + test("unwritable target parent → refuses with actionable error (codex #3)", async () => { + // Point at a nonexistent path that fs.accessSync will reject with + // ENOENT (unwritable-in-the-sense-that-we-cannot-write-there). + await expect( + materializeSample({ + targetParent: "/definitely/not/writable/anywhere", + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }), + ).rejects.toThrow(/not writable/) + }) +}) + +/** + * Path-traversal / adversarial-input guards for `preferredTargetName`. + * `sample_setup` accepts this from the LLM; a prompt-injected model turn + * (or a compromised template) could try to steer materialization outside + * `targetParent`. The name-regex + post-resolve containment check should + * refuse before any fs write happens. + */ +describe("materializeSample — preferredTargetName input hardening", () => { + // Split by which guard is expected to fire — regex vs containment check. + // The alternation `regex|containment` previously masked *which* layer + // caught the input; a review flagged that a regex regression could silently + // shift catches to the containment layer without any test failing (the test + // still passes because the second alternative matches). Asserting the + // exact message per name proves the regex is doing the work it claims to. + const REJECTED = [ + "../escape", + "..", + "../../etc/passwd", + "a/b", + "/absolute", + ".hidden", + "with space", + "with\ttab", + "with\nnewline", + "quote'char", + "back\\slash", + "", // empty — no valid segment + ] + for (const name of REJECTED) { + test(`refuses preferredTargetName ${JSON.stringify(name)} at the regex layer, before any fs write`, async () => { + const parent = makeTmpParent("materialize-traversal-") + await expect( + materializeSample({ + targetParent: parent, + preferredTargetName: name, + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }), + ).rejects.toThrow(/not a plain directory name/) + // Parent still exists, but nothing was materialized inside it. + expect(fs.readdirSync(parent)).toEqual([]) + }) + } + + const ACCEPTED = ["starter", "altimate-sample-dbt", "a", "A1", "with.dot", "with-dash", "with_underscore"] + for (const name of ACCEPTED) { + test(`accepts preferredTargetName ${JSON.stringify(name)}`, async () => { + const parent = makeTmpParent("materialize-accept-") + const result = await materializeSample({ + targetParent: parent, + preferredTargetName: name, + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + expect(result.targetPath).toBe(path.join(parent, name)) + }) + } +}) + +/** + * Default-targetParent behavior: when the caller omits targetParent, the + * materializer must fall back to `os.homedir()` — and that call must run + * through the same rejectUnsafeHome + writability guards as an explicit + * targetParent. A review flagged that no test exercised the default path. + */ +describe("materializeSample — default targetParent", () => { + test("omitted targetParent defaults to os.homedir() and materializes there", async () => { + // Two things being tested together: + // 1. When targetParent is omitted, the code falls back to os.homedir() + // (opts.targetParent ?? os.homedir()) — we mock homedir to a + // scratch dir and assert the result lands under it. + // 2. The materializer still runs to completion — proves no other + // code path assumed targetParent was always set. + // allowUnsafeParent is set so the tmp-shaped scratch home doesn't + // trigger rejectUnsafeHome; the guard itself has its own dedicated + // "unsafe HOME → refuses" test above that verifies it fires on the + // defaulted path. + const scratchParent = makeTmpParent("materialize-default-home-") + const origHomedir = os.homedir + Object.defineProperty(os, "homedir", { value: () => scratchParent, configurable: true }) + try { + const result = await materializeSample({ + preferredTargetName: "altimate-sample-default", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + expect(result.targetPath).toBe(path.join(scratchParent, "altimate-sample-default")) + expect(fs.existsSync(path.join(result.targetPath, MARKER_FILE_NAME))).toBe(true) + } finally { + Object.defineProperty(os, "homedir", { value: origHomedir, configurable: true }) + } + }) +}) + +/** + * Symlink hardening (codex #21). `classifyTarget` uses lstatSync so a + * symlinked target is classified `unknown-dir` and forwarded to a suffix, + * rather than being followed (which would (a) place the materialize outside + * the parent our containment check validated, or (b) let an "empty" + * classification silently unlink the symlink when the overwrite path fires). + */ +describe("materializeSample — symlink target", () => { + test("symlinked preferred slot is classified unknown-dir → suffixed to -2, symlink untouched", async () => { + const parent = makeTmpParent("materialize-symlink-") + // Real dir the symlink points at — outside the parent, so if + // classifyTarget followed the symlink and treated its target as our + // slot, materialization would land wherever the symlink went and would + // trip either the containment check or clobber unrelated content. + const linkTarget = makeTmpParent("materialize-symlink-target-") + fs.writeFileSync(path.join(linkTarget, "user-file.txt"), "please do not touch") + const symlinkPath = path.join(parent, "starter") + fs.symlinkSync(linkTarget, symlinkPath) + + const result = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + + // Escalated to slot 1 — symlink was classified as unknown-dir. + expect(result.suffix).toBe(1) + expect(result.targetPath).toBe(path.join(parent, "starter-2")) + // Symlink itself still exists (not unlinked) and still points where it did. + const stat = fs.lstatSync(symlinkPath) + expect(stat.isSymbolicLink()).toBe(true) + // What the link points at is intact. + expect(fs.readFileSync(path.join(linkTarget, "user-file.txt"), "utf8")).toBe("please do not touch") + }) +}) + +/** + * findSafeTarget bail-early behavior (codex #26). Scanning a hostile + * parent with 10+ consecutive unrelated dirs should short-circuit to the + * hex fallback rather than burning ~100 stat syscalls to arrive at the + * same answer. + */ +describe("materializeSample — findSafeTarget bail-early on crowded parent", () => { + test("11 consecutive unrelated dirs under preferred name → materializes into hex-suffixed slot", async () => { + const parent = makeTmpParent("materialize-crowded-") + // Seed slot 0 through slot 10 (starter, starter-2, …, starter-11) with + // unrelated content — CONSECUTIVE_UNKNOWN_LIMIT is 10, so 11 unknowns + // guarantees the short-circuit fires. + fs.mkdirSync(path.join(parent, "starter")) + fs.writeFileSync(path.join(parent, "starter", "unrelated.txt"), "x") + for (let i = 2; i <= 11; i++) { + const dir = path.join(parent, `starter-${i}`) + fs.mkdirSync(dir) + fs.writeFileSync(path.join(dir, "unrelated.txt"), "x") + } + + const result = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + + // The scan bailed to hex — suffix is a string, not a number. + expect(typeof result.suffix).toBe("string") + expect(result.targetPath).toMatch(new RegExp(`starter-[0-9a-f]{6}$`)) + // Marker written to the hex-suffixed slot. + expect(fs.existsSync(path.join(result.targetPath, MARKER_FILE_NAME))).toBe(true) + // Unrelated content untouched. + expect(fs.readFileSync(path.join(parent, "starter", "unrelated.txt"), "utf8")).toBe("x") + expect(fs.readFileSync(path.join(parent, "starter-11", "unrelated.txt"), "utf8")).toBe("x") + }) +}) + +/** + * Interrupt-safety: a prior killed materialize leaves a `..tmp-` + * staging dir. The next run must (a) not classify it as unknown-dir and + * escalate to a suffix, and (b) sweep the orphan. + */ +describe("materializeSample — orphan staging cleanup", () => { + test("OLD .starter.tmp-* orphan (past age guard) → swept + starter/ materialized cleanly", async () => { + const parent = makeTmpParent("materialize-orphan-") + const orphan1 = path.join(parent, ".starter.tmp-deadbeef") + const orphan2 = path.join(parent, ".starter.tmp-cafebabe") + fs.mkdirSync(orphan1, { recursive: true }) + fs.writeFileSync(path.join(orphan1, "partial.txt"), "leftover from crash") + fs.mkdirSync(orphan2, { recursive: true }) + // Backdate the orphans past the sweep age guard (default 1h). Young + // orphans are DELIBERATELY kept to avoid nuking a live sibling's + // staging tree — see sweepOrphanStaging comment in materialize.ts. + const twoHoursAgo = (Date.now() - 2 * 60 * 60 * 1000) / 1000 + fs.utimesSync(orphan1, twoHoursAgo, twoHoursAgo) + fs.utimesSync(orphan2, twoHoursAgo, twoHoursAgo) + + const result = await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + + // Fresh materialize into starter/ (not starter-2/). + expect(result.suffix).toBe(0) + expect(result.targetPath).toBe(path.join(parent, "starter")) + // Marker present → fully atomic. + expect(fs.existsSync(path.join(result.targetPath, MARKER_FILE_NAME))).toBe(true) + // Both orphans gone. + expect(fs.existsSync(orphan1)).toBe(false) + expect(fs.existsSync(orphan2)).toBe(false) + // No stray staging dir for THIS run. + const staging = fs.readdirSync(parent).filter((n) => n.startsWith(".starter.tmp-")) + expect(staging).toEqual([]) + }) + + test("YOUNG .starter.tmp-* orphan (recent — could be a live sibling) is LEFT ALONE (codex #17)", async () => { + const parent = makeTmpParent("materialize-orphan-young-") + const youngOrphan = path.join(parent, ".starter.tmp-freshxxxx") + fs.mkdirSync(youngOrphan, { recursive: true }) + // No utimes backdating — modified just now, well under the 1h guard. + + await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + + // Young orphan MUST still exist — the sweep is age-guarded to prevent + // deleting a concurrent process's live staging tree. + expect(fs.existsSync(youngOrphan)).toBe(true) + }) + + test("SYMLINKED orphan .starter.tmp-* is skipped (codex NEW-5 — age check would follow the link)", async () => { + const parent = makeTmpParent("materialize-orphan-symlink-") + // Real dir the symlink points at. If sweepOrphanStaging follows the + // symlink for its age check, the orphan classification uses that + // dir's fresh mtime — which would either KEEP an ancient link (age + // guard misfires) or DELETE a link over live content depending on + // whose mtime wins. The safe answer: skip symlinks entirely. + const realDir = makeTmpParent("materialize-orphan-symlink-target-") + fs.writeFileSync(path.join(realDir, "keep-me.txt"), "do not touch") + const symlinkOrphan = path.join(parent, ".starter.tmp-abcdef") + fs.symlinkSync(realDir, symlinkOrphan) + // Backdate the SYMLINK itself past the age guard. If sweep uses + // statSync (buggy) it would see the target's fresh mtime and skip; + // if it uses lstatSync (correct) it sees the symlink's own ancient + // mtime — but should still skip because of the isSymbolicLink guard. + const twoHoursAgo = (Date.now() - 2 * 60 * 60 * 1000) / 1000 + try { fs.lutimesSync(symlinkOrphan, twoHoursAgo, twoHoursAgo) } catch { /* fallback: some fs lack lutimes */ } + + await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + + // Symlink still exists (skipped, not rm'd). + const stat = fs.lstatSync(symlinkOrphan) + expect(stat.isSymbolicLink(), "symlinked orphan was unlinked; sweep should skip symlinks entirely").toBe(true) + // What the link points at is intact. + expect(fs.readFileSync(path.join(realDir, "keep-me.txt"), "utf8")).toBe("do not touch") + }) + + test("orphan for a DIFFERENT preferredName is left alone (different sweep prefix)", async () => { + const parent = makeTmpParent("materialize-orphan-scoped-") + const otherOrphan = path.join(parent, ".other-sample.tmp-abcdef") + fs.mkdirSync(otherOrphan, { recursive: true }) + const twoHoursAgo = (Date.now() - 2 * 60 * 60 * 1000) / 1000 + fs.utimesSync(otherOrphan, twoHoursAgo, twoHoursAgo) + + await materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }) + + // Only starter's orphans get swept; another sample's staging is not our + // business — even when it's old enough that the age guard would allow + // deletion. + expect(fs.existsSync(otherOrphan)).toBe(true) + }) + + test("two concurrent materializeSample calls with the same preferredName → serialize under Flock, no corruption (codex #17)", async () => { + const parent = makeTmpParent("materialize-concurrent-") + // Kick off two concurrent materializes into the same slot. Without a + // lock, findSafeTarget in both would see slot 0 as empty, both would + // build staging dirs, and the second's rename would either fail with + // ENOTEMPTY or silently clobber. With Flock, they serialize: one gets + // slot 0 as fresh, the other sees "our-sample-current" and reuses. + const [r1, r2] = await Promise.all([ + materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }), + materializeSample({ + targetParent: parent, + preferredTargetName: "starter", + sampleVersion: SAMPLE_VERSION, + cliVersion: CLI_VERSION, + allowUnsafeParent: true, + }), + ]) + // Both landed at the SAME path — no suffix escalation, no split. + expect(r1.targetPath).toBe(path.join(parent, "starter")) + expect(r2.targetPath).toBe(path.join(parent, "starter")) + // Exactly one wrote fresh (reused=false); the other found the sample + // and reused it (reused=true). Order is undefined; XOR the flags. + expect(r1.reused !== r2.reused).toBe(true) + // The materialized dir has the marker. + expect(fs.existsSync(path.join(r1.targetPath, MARKER_FILE_NAME))).toBe(true) + // No stray staging left behind by either run. + const staging = fs.readdirSync(parent).filter((n) => n.startsWith(".starter.tmp-")) + expect(staging).toEqual([]) + }) +}) diff --git a/packages/opencode/test/altimate/onboarding/publish-parity.test.ts b/packages/opencode/test/altimate/onboarding/publish-parity.test.ts new file mode 100644 index 0000000000..199a0d585c --- /dev/null +++ b/packages/opencode/test/altimate/onboarding/publish-parity.test.ts @@ -0,0 +1,70 @@ +/** + * Publish-parity guard: script/publish.ts must copy every file that + * MATERIALIZE_ENTRIES in materialize.ts declares. If a maintainer adds a + * new file to the runtime whitelist but forgets to add it to the publish + * copy step, dev + local tests still pass (they resolve to the source + * tree via the dev-source-tree candidate) but prod installs ship + * without the file — silently producing a broken materialize. + * + * The test reads publish.ts as text and asserts that every entry from + * MATERIALIZE_ENTRIES appears as a path in the copy commands. A + * reasonably tolerant match: we look for the literal `./sample-projects/ + * /` substring, which is how publish.ts writes them + * today. If publish.ts refactors the copy shape substantially the test + * fails loudly and forces this file to be updated in lockstep — that's + * the point. + */ + +import { describe, expect, test } from "bun:test" +import fs from "node:fs" +import path from "node:path" + +// Keep this list in sync with MATERIALIZE_ENTRIES in +// packages/opencode/src/altimate/onboarding/materialize.ts. We inline the +// list here (rather than import it) so the test would fail even if the +// import chain re-exported it — a re-export shadow that always agrees +// with itself is not a real cross-check. The lint is against the shape +// publish.ts actually writes on disk. +const MATERIALIZE_ENTRIES = [ + "README.md", + "dbt_project.yml", + "profiles.yml", + "sample-manifest.json", + ".gitignore", + "models", + "seeds", + "target/manifest.json", +] + +describe("publish.ts ships every file the materializer expects", () => { + test("every MATERIALIZE_ENTRIES path appears in publish.ts's sample-projects copy list", () => { + const publishPath = path.resolve(__dirname, "../../../script/publish.ts") + const src = fs.readFileSync(publishPath, "utf8") + // The copy commands reference paths like + // `./sample-projects/jaffle-shop-duckdb/` — split on any + // whitespace and lint each entry. Fuzzy substring is intentional + // (we want to survive `\\` line-continuations, path stitching, etc.); + // if publish.ts refactors away from that shape entirely, the test + // fails and the maintainer updates both files together. + const missing: string[] = [] + for (const entry of MATERIALIZE_ENTRIES) { + const needle = `sample-projects/jaffle-shop-duckdb/${entry}` + if (!src.includes(needle)) missing.push(entry) + } + expect( + missing, + `publish.ts is missing copy commands for these materialize entries — dev works but prod installs ship broken: ${missing.join(", ")}`, + ).toEqual([]) + }) + + test("if publish.ts's sample-projects block is removed entirely, the test fails loudly", () => { + // Sanity: our substring search MUST find something in publish.ts today. + // A zero-match result would silently pass every entry check above if + // publish.ts were entirely rewritten to not mention sample-projects, + // which would be a much bigger regression than the parity check alone + // is meant to catch. + const publishPath = path.resolve(__dirname, "../../../script/publish.ts") + const src = fs.readFileSync(publishPath, "utf8") + expect(src).toContain("sample-projects/jaffle-shop-duckdb/") + }) +}) diff --git a/packages/opencode/test/altimate/onboarding/sample-source-resolver.test.ts b/packages/opencode/test/altimate/onboarding/sample-source-resolver.test.ts new file mode 100644 index 0000000000..0916a41f20 --- /dev/null +++ b/packages/opencode/test/altimate/onboarding/sample-source-resolver.test.ts @@ -0,0 +1,227 @@ +/** + * sample-source-resolver.ts — locate the shipped starter sample source + * across dev / test / prod install layouts, and rehydrate the sentinel- + * bearing pre-compiled manifest into a usable-anywhere manifest. + * + * The rehydration test is the load-bearing one — codex flagged a real + * JSON-corruption failure mode (naive text-level replace breaking on + * paths with quotes or Windows backslashes). The Phase 3 refinement + * moved to a tree-walking replace that only touches string leaves; + * these tests pin that behavior with adversarial inputs. + */ + +import { describe, expect, test } from "bun:test" +import fs from "node:fs" +import os from "node:os" +import path from "node:path" +import { + DEFAULT_SAMPLE_NAME, + SAMPLE_ROOT_PARENT_SENTINEL, + SAMPLE_ROOT_SENTINEL, + loadShippedManifest, + rehydrateSentinels, + resolveSampleSource, +} from "../../../src/altimate/onboarding/sample-source-resolver" + +describe("resolveSampleSource — ALTIMATE_BIN_DIR (real install layouts codex #10)", () => { + test("wrapper layout without postinstall hardlink (Windows, --ignore-scripts) → resolves via ALTIMATE_BIN_DIR", () => { + // Simulate the layout that ships in production BEFORE the postinstall + // hardlink from wrapper/bin/.altimate-code back to the platform binary: + // wrapper-root/ + // bin/altimate-code (the Node wrapper script) + // sample-projects//dbt_project.yml + // node_modules//bin/altimate-code (the actual exe) + // Without ALTIMATE_BIN_DIR, resolveSampleSource walks from execDir + // (the platform-package bin) and lands 2 hops away from sample-projects — + // the very failure the finding was written to fix. + const wrapperRoot = fs.mkdtempSync(path.join(os.tmpdir(), "resolver-bindir-")) + const wrapperBinDir = path.join(wrapperRoot, "bin") + const sampleDir = path.join(wrapperRoot, "sample-projects", DEFAULT_SAMPLE_NAME) + fs.mkdirSync(wrapperBinDir, { recursive: true }) + fs.mkdirSync(sampleDir, { recursive: true }) + fs.writeFileSync(path.join(sampleDir, "dbt_project.yml"), "name: fake\n") + + const origBinDir = process.env["ALTIMATE_BIN_DIR"] + const origEnv = process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + delete process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + process.env["ALTIMATE_BIN_DIR"] = wrapperBinDir + try { + const location = resolveSampleSource() + expect(location).toBeDefined() + expect(location!.origin).toBe("wrapper-bin-dir") + expect(location!.path).toBe(path.resolve(sampleDir)) + } finally { + if (origBinDir === undefined) delete process.env["ALTIMATE_BIN_DIR"] + else process.env["ALTIMATE_BIN_DIR"] = origBinDir + if (origEnv === undefined) delete process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + else process.env["ALTIMATE_STARTER_SAMPLE_DIR"] = origEnv + } + }) + + test("ALTIMATE_STARTER_SAMPLE_DIR still wins over ALTIMATE_BIN_DIR (override precedence)", () => { + // Both set → env override is more specific, takes precedence. + const envDir = fs.mkdtempSync(path.join(os.tmpdir(), "resolver-env-precedence-")) + const envSample = path.join(envDir, DEFAULT_SAMPLE_NAME) + fs.mkdirSync(envSample, { recursive: true }) + fs.writeFileSync(path.join(envSample, "dbt_project.yml"), "# env-dir\n") + + const binWrapper = fs.mkdtempSync(path.join(os.tmpdir(), "resolver-bindir-precedence-")) + const binDirSample = path.join(binWrapper, "sample-projects", DEFAULT_SAMPLE_NAME) + fs.mkdirSync(binDirSample, { recursive: true }) + fs.writeFileSync(path.join(binDirSample, "dbt_project.yml"), "# bindir\n") + + const origEnv = process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + const origBinDir = process.env["ALTIMATE_BIN_DIR"] + process.env["ALTIMATE_STARTER_SAMPLE_DIR"] = envDir + process.env["ALTIMATE_BIN_DIR"] = path.join(binWrapper, "bin") + try { + const location = resolveSampleSource() + expect(location).toBeDefined() + expect(location!.origin).toBe("env") + expect(location!.path).toBe(path.resolve(envSample)) + } finally { + if (origEnv === undefined) delete process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + else process.env["ALTIMATE_STARTER_SAMPLE_DIR"] = origEnv + if (origBinDir === undefined) delete process.env["ALTIMATE_BIN_DIR"] + else process.env["ALTIMATE_BIN_DIR"] = origBinDir + } + }) +}) + +describe("resolveSampleSource — env override", () => { + test("ALTIMATE_STARTER_SAMPLE_DIR points at a valid sample → returns it with origin=env", () => { + // Stage a fake sample dir under a tempdir so the override resolves. + const stageParent = fs.mkdtempSync(path.join(os.tmpdir(), "resolver-env-")) + const sampleDir = path.join(stageParent, DEFAULT_SAMPLE_NAME) + fs.mkdirSync(sampleDir, { recursive: true }) + fs.writeFileSync(path.join(sampleDir, "dbt_project.yml"), "name: fake\n") + + const orig = process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + process.env["ALTIMATE_STARTER_SAMPLE_DIR"] = stageParent + try { + const location = resolveSampleSource() + expect(location).toBeDefined() + expect(location!.origin).toBe("env") + expect(location!.path).toBe(path.resolve(sampleDir)) + } finally { + if (orig === undefined) delete process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + else process.env["ALTIMATE_STARTER_SAMPLE_DIR"] = orig + } + }) + + test("no override + shipped sample present → returns via dev-source-tree candidate in this repo", () => { + // Ensure the env override isn't leaking from another test. + const origEnv = process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + delete process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + try { + const location = resolveSampleSource() + // This test asserts against the real repo layout — expects the + // dev-source-tree candidate to hit because + // packages/opencode/sample-projects/jaffle-shop-duckdb/dbt_project.yml + // exists in the branch that landed Phase 3. + expect(location).toBeDefined() + expect(location!.path).toContain("packages/opencode/sample-projects/jaffle-shop-duckdb") + expect(location!.origin).toBe("dev-source-tree") + } finally { + if (origEnv !== undefined) process.env["ALTIMATE_STARTER_SAMPLE_DIR"] = origEnv + } + }) +}) + +describe("rehydrateSentinels — JSON-safe tree walk (codex fix #2)", () => { + test("plain string with sentinels → single expansion", () => { + const input = `${SAMPLE_ROOT_SENTINEL}/models/foo.sql` + const out = rehydrateSentinels(input, "/home/alice/altimate-sample-dbt", "/home/alice") as string + expect(out).toBe("/home/alice/altimate-sample-dbt/models/foo.sql") + }) + + test("target path with double-quotes gets substituted verbatim (naive text replace would break JSON)", () => { + const input = { p: `${SAMPLE_ROOT_SENTINEL}/models/foo.sql` } + const trickyPath = `/tmp/a"b` + const out = rehydrateSentinels(input, trickyPath, "/tmp") as { p: string } + // The value contains the quote — this is fine because we're walking + // parsed JSON, not text. A round-trip through JSON.stringify would + // re-escape the quote correctly. + expect(out.p).toBe(`/tmp/a"b/models/foo.sql`) + // Sanity check: JSON.stringify works on the result (no invalid state). + const roundTrip = JSON.parse(JSON.stringify(out)) + expect(roundTrip.p).toBe(out.p) + }) + + test("target path with Windows-style backslashes gets substituted without producing invalid escape sequences", () => { + const input = { p: `${SAMPLE_ROOT_SENTINEL}/models/foo.sql` } + const winPath = String.raw`C:\Users\alice\altimate-sample-dbt` + const out = rehydrateSentinels(input, winPath, String.raw`C:\Users\alice`) as { p: string } + expect(out.p).toBe(String.raw`C:\Users\alice\altimate-sample-dbt/models/foo.sql`) + // Should JSON-round-trip cleanly (naive text-replace failed here). + const roundTrip = JSON.parse(JSON.stringify(out)) + expect(roundTrip.p).toBe(out.p) + }) + + test("object keys are NOT rehydrated — only string values (guards against a sentinel accidentally appearing in a key)", () => { + // Synthesize a manifest fragment where the key contains the sentinel + // — walking should leave the key untouched. Object-key rehydration + // would corrupt the schema. + const input: Record = {} + input[SAMPLE_ROOT_SENTINEL] = "value" + const out = rehydrateSentinels(input, "/target", "/parent") as Record + // Key preserved literally. + expect(Object.keys(out)).toContain(SAMPLE_ROOT_SENTINEL) + }) + + test("PARENT sentinel is replaced before ROOT so the shorter one can't shadow the longer one", () => { + // If order were reversed, {{SAMPLE_ROOT}} would match inside + // {{SAMPLE_ROOT_PARENT}} first and leave dangling tokens. + const input = `${SAMPLE_ROOT_PARENT_SENTINEL}/other-project` + const out = rehydrateSentinels(input, "/user/sample", "/user") as string + expect(out).toBe("/user/other-project") + // The ROOT sentinel is a substring of the PARENT sentinel token — a + // faulty impl would produce "{{}}/other-project" or similar. Guard. + expect(out).not.toContain("SAMPLE_ROOT") + expect(out).not.toContain("{{") + }) + + test("array of strings is walked", () => { + const input = [ + `${SAMPLE_ROOT_SENTINEL}/a`, + `${SAMPLE_ROOT_SENTINEL}/b`, + { nested: `${SAMPLE_ROOT_SENTINEL}/c` }, + ] + const out = rehydrateSentinels(input, "/x", "/") as any[] + expect(out[0]).toBe("/x/a") + expect(out[1]).toBe("/x/b") + expect(out[2].nested).toBe("/x/c") + }) + + test("numbers, booleans, nulls are untouched", () => { + const input = { n: 42, b: true, z: null, s: `${SAMPLE_ROOT_SENTINEL}/x` } + const out = rehydrateSentinels(input, "/t", "/") as Record + expect(out.n).toBe(42) + expect(out.b).toBe(true) + expect(out.z).toBeNull() + expect(out.s).toBe("/t/x") + }) +}) + +describe("loadShippedManifest — end-to-end against the real shipped manifest", () => { + test("loading the shipped sample's manifest.json with a target substitution yields no dangling sentinels", () => { + delete process.env["ALTIMATE_STARTER_SAMPLE_DIR"] + const location = resolveSampleSource() + // Every branch of this test suite runs from within the worktree checkout + // where the shipped sample tree lives at + // packages/opencode/sample-projects/jaffle-shop-duckdb/. If the resolver + // returns undefined here, the fallback candidate list is broken — that + // IS the failure mode this test exists to catch. Do not silently skip. + expect(location, "resolveSampleSource() returned undefined — the resolver's candidate-path list can no longer find the shipped sample tree in a dev checkout").toBeDefined() + const materializedTarget = "/tmp/materialized-target" + const manifest = loadShippedManifest(location!.path, materializedTarget) + // The rehydrated manifest MUST NOT contain the sentinel strings + // anywhere — the whole point of the tree walk was to substitute + // them all. + const serialized = JSON.stringify(manifest) + expect(serialized).not.toContain(SAMPLE_ROOT_SENTINEL) + expect(serialized).not.toContain(SAMPLE_ROOT_PARENT_SENTINEL) + // And the materializedTarget path should appear (that's the substitution). + expect(serialized).toContain(materializedTarget) + }) +}) diff --git a/packages/opencode/test/altimate/onboarding/tool-detection.test.ts b/packages/opencode/test/altimate/onboarding/tool-detection.test.ts new file mode 100644 index 0000000000..3fe40da6de --- /dev/null +++ b/packages/opencode/test/altimate/onboarding/tool-detection.test.ts @@ -0,0 +1,180 @@ +/** + * tool-detection.ts — probes `dbt --version` output to decide whether the + * user's local toolchain can run the sample's dbt build workflow. + * + * These tests exercise the REAL `detectDbtRuntime()` end-to-end by putting + * a fake `dbt` script first on PATH. Each scenario drops a shell stub that + * emits a scripted stdout/stderr + exit code, then asserts on what the + * real parser inside probe() extracts. If the impl regex or shape changes, + * these tests will catch it — unlike an earlier version that duplicated + * the regex constants into the test file and asserted against those. + */ + +import { afterEach, describe, expect, test } from "bun:test" +import fs from "node:fs" +import os from "node:os" +import path from "node:path" +import { _resetDbtRuntimeCacheForTests, detectDbtRuntime } from "../../../src/altimate/onboarding/tool-detection" + +const ORIG_PATH = process.env.PATH ?? "" + +afterEach(() => { + process.env.PATH = ORIG_PATH + _resetDbtRuntimeCacheForTests() +}) + +/** + * Drop a fake `dbt` executable in a fresh tmpdir and prepend it to PATH. + * The script echoes the given stdout on stderr-vs-stdout per real dbt + * (which prints its `--version` output on stderr with color codes on + * some versions, stdout on others — probe() reads both). + */ +function stubDbt(opts: { stdout?: string; stderr?: string; exitCode?: number }): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "tool-detection-stub-")) + const script = path.join(dir, "dbt") + const stdout = opts.stdout ?? "" + const stderr = opts.stderr ?? "" + const exit = opts.exitCode ?? 0 + // Bash-quote the payload so newlines + special chars round-trip. + const payload = `#!/usr/bin/env bash +cat <<'STDOUT' +${stdout} +STDOUT +cat <<'STDERR' 1>&2 +${stderr} +STDERR +exit ${exit} +` + fs.writeFileSync(script, payload, { mode: 0o755 }) + process.env.PATH = `${dir}:${ORIG_PATH}` + return dir +} + +/** Stub that isn't executable (models a `dbt` file that exists but can't run). + * Uses ONLY the broken dir on PATH — no fallthrough to the real system dbt. */ +function stubBrokenDbt(): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "tool-detection-broken-")) + fs.writeFileSync(path.join(dir, "dbt"), "not-a-script", { mode: 0o644 }) + process.env.PATH = dir + return dir +} + +/** Point PATH at an empty dir so `dbt` genuinely isn't found. */ +function stubNoDbt(): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "tool-detection-nodbt-")) + process.env.PATH = dir + return dir +} + +describe("detectDbtRuntime — real subprocess invocation via PATH override", () => { + test("dbt 1.11 with duckdb plugin → hasDbt=true, hasDbtDuckdb=true, correct version", async () => { + stubDbt({ + stdout: `Core: + - installed: 1.11.8 + - latest: 1.12.0 - Update available! + +Plugins: + - duckdb: 1.11.4 - Update available! +`, + }) + const runtime = await detectDbtRuntime({ force: true }) + expect(runtime.hasDbt).toBe(true) + expect(runtime.hasDbtDuckdb).toBe(true) + expect(runtime.dbtCoreVersion).toBe("1.11.8") + }) + + test("dbt with only non-duckdb plugins → hasDbt=true, hasDbtDuckdb=false", async () => { + stubDbt({ + stdout: `Core: + - installed: 1.11.8 + +Plugins: + - snowflake: 1.11.0 + - bigquery: 1.11.1 +`, + }) + const runtime = await detectDbtRuntime({ force: true }) + expect(runtime.hasDbt).toBe(true) + expect(runtime.hasDbtDuckdb).toBe(false) + expect(runtime.dbtCoreVersion).toBe("1.11.8") + }) + + test("'duckdb' as substring in a prose line → NOT detected as adapter", async () => { + stubDbt({ + stdout: `Core: + - installed: 1.11.8 + - latest: 1.12.0 + +Try installing dbt-duckdb for a local warehouse. +`, + }) + const runtime = await detectDbtRuntime({ force: true }) + expect(runtime.hasDbtDuckdb).toBe(false) + expect(runtime.dbtCoreVersion).toBe("1.11.8") + }) + + test("dbt writes its version on STDERR (some 1.x versions do this) → still parsed", async () => { + stubDbt({ + stderr: `Core: + - installed: 1.10.2 + +Plugins: + - duckdb: 1.10.0 +`, + }) + const runtime = await detectDbtRuntime({ force: true }) + expect(runtime.hasDbt).toBe(true) + expect(runtime.hasDbtDuckdb).toBe(true) + expect(runtime.dbtCoreVersion).toBe("1.10.2") + }) + + test("dbt exits non-zero → treated as not usable (hasDbt=false)", async () => { + stubDbt({ stdout: "some noise", exitCode: 2 }) + const runtime = await detectDbtRuntime({ force: true }) + expect(runtime.hasDbt).toBe(false) + expect(runtime.hasDbtDuckdb).toBe(false) + expect(runtime.dbtCoreVersion).toBeUndefined() + }) + + test("dbt not on PATH at all → hasDbt=false (never throws)", async () => { + stubNoDbt() + const runtime = await detectDbtRuntime({ force: true }) + expect(runtime.hasDbt).toBe(false) + expect(runtime.hasDbtDuckdb).toBe(false) + }) + + test("dbt file exists but is not executable → hasDbt=false", async () => { + stubBrokenDbt() + const runtime = await detectDbtRuntime({ force: true }) + expect(runtime.hasDbt).toBe(false) + expect(runtime.hasDbtDuckdb).toBe(false) + }) + + test("cached call returns same result without re-invoking (perf contract)", async () => { + stubDbt({ + stdout: `Core: + - installed: 1.11.8 + +Plugins: + - duckdb: 1.11.4 +`, + }) + const first = await detectDbtRuntime({ force: true }) + // Change the stub to return DIFFERENT output — if the cache isn't + // honored, the second call would see the new content. + stubDbt({ + stdout: `Core: + - installed: 9.9.9 + +Plugins: + - snowflake: 9.9.9 +`, + }) + const second = await detectDbtRuntime() // NO force → must use cache + expect(second).toEqual(first) + // And with force → re-probes. + const third = await detectDbtRuntime({ force: true }) + expect(third.dbtCoreVersion).toBe("9.9.9") + expect(third.hasDbtDuckdb).toBe(false) + }) +}) diff --git a/packages/opencode/test/altimate/telemetry/onboarding.test.ts b/packages/opencode/test/altimate/telemetry/onboarding.test.ts new file mode 100644 index 0000000000..804304d9cc --- /dev/null +++ b/packages/opencode/test/altimate/telemetry/onboarding.test.ts @@ -0,0 +1,345 @@ +// altimate_change — coverage for the onboarding funnel taxonomy. +// +// The question these answer is "does user action X emit event Y, with the right properties". +// Emission is verified by spying on Telemetry.track, so the assertions are about what would be +// sent, not about the transport. +// +// Expected values are written out literally from the product spec rather than derived from the +// implementation — a test that computes its expectation the same way the code does would pass +// even when both are wrong. +import { describe, expect, test, beforeEach, afterEach, spyOn, mock } from "bun:test" +import { Telemetry } from "@/altimate/telemetry" +import * as Onboarding from "@/altimate/telemetry/onboarding" +import { OnboardingTelemetryPlugin } from "@/altimate/plugin/onboarding-telemetry" + +type Tracked = Telemetry.Event + +function captureEvents() { + const events: Tracked[] = [] + // init() reads config and touches the filesystem; the funnel logic under test does not care. + spyOn(Telemetry, "init").mockImplementation(async () => {}) + spyOn(Telemetry, "track").mockImplementation((event: Tracked) => { + events.push(event) + }) + return events +} + +/** Wait for the fire-and-forget `void emit(...)` promises to settle. */ +const settle = () => Bun.sleep(0) + +beforeEach(() => { + Onboarding.resetForTest() +}) + +afterEach(() => { + mock.restore() +}) + +// --------------------------------------------------------------------------- +// Abandonment — the funnel must only contain people who were actually in it +// --------------------------------------------------------------------------- +describe("onboarding abandonment", () => { + test("a returning user who opens the picker is not in the funnel", async () => { + const events = captureEvents() + + // No onboarding_started: this is /connect from an established user, which mounts the very + // same picker. Reaching a stage must not enrol them. + Onboarding.markStage("model_picker") + await Onboarding.emitAbandonedIfIncomplete() + await settle() + + expect(events).toEqual([]) + }) + + test("quitting mid first-run reports the furthest stage reached", async () => { + const events = captureEvents() + + await Onboarding.emit({ type: "onboarding_started" }) + await Onboarding.emit({ type: "model_picker_shown", trigger: "first_run" }) + await Onboarding.emit({ type: "provider_selected", provider: "anthropic" }) + await Onboarding.emitAbandonedIfIncomplete() + await settle() + + const abandoned = events.filter((e) => e.type === "onboarding_abandoned") + expect(abandoned).toHaveLength(1) + // Picking a provider and quitting during key entry is "got as far as setting up a provider", + // not "only ever saw the picker". + expect((abandoned[0] as any).last_stage).toBe("provider_setup") + }) + + test("a completed onboarding is never reported as abandoned", async () => { + const events = captureEvents() + + await Onboarding.emit({ type: "onboarding_started" }) + await Onboarding.emit({ type: "onboarding_completed" }) + await Onboarding.emitAbandonedIfIncomplete() + await settle() + + expect(events.some((e) => e.type === "onboarding_abandoned")).toBe(false) + }) + + test("re-opening the picker later does not walk the stage backwards", async () => { + const events = captureEvents() + + await Onboarding.emit({ type: "onboarding_started" }) + await Onboarding.emit({ type: "gateway_device_code_issued" }) + await Onboarding.emit({ type: "model_picker_shown", trigger: "big_pickle_back" }) + await Onboarding.emitAbandonedIfIncomplete() + await settle() + + const abandoned = events.find((e) => e.type === "onboarding_abandoned") + expect((abandoned as any).last_stage).toBe("gateway_auth") + }) + + test("abandonment fires at most once", async () => { + const events = captureEvents() + + await Onboarding.emit({ type: "onboarding_started" }) + await Onboarding.emitAbandonedIfIncomplete() + await Onboarding.emitAbandonedIfIncomplete() + await settle() + + expect(events.filter((e) => e.type === "onboarding_abandoned")).toHaveLength(1) + }) +}) + +// --------------------------------------------------------------------------- +// Activation — inferred from the plugin hooks +// --------------------------------------------------------------------------- +describe("activation events", () => { + const SESSION = "ses_test" + + async function plugin() { + return OnboardingTelemetryPlugin({} as any) + } + + async function startOnboarding(hooks: any, args: "scan" | "skip") { + await hooks["command.execute.before"]!({ command: "onboard-connect", sessionID: SESSION, arguments: args }, { + parts: [], + }) + } + + test("skipping the scan shows the no-data menu immediately", async () => { + const events = captureEvents() + const hooks = await plugin() + + await startOnboarding(hooks, "skip") + await settle() + + const menu = events.filter((e) => e.type === "activation_menu_shown") + expect(menu).toHaveLength(1) + expect((menu[0] as any).variant).toBe("no_data") + }) + + test("a warehouse discovered from dbt profiles still counts as having a warehouse", async () => { + const events = captureEvents() + const hooks = await plugin() + await startOnboarding(hooks, "scan") + + // The adversarial case: nothing is already configured, but the scan discovered a connection + // from a dbt profile. This user HAS a warehouse and must get the warehouse menu — reading + // only `existing` would send them down the sample-project branch. + await hooks["tool.execute.after"]!( + { tool: "project_scan", sessionID: SESSION, callID: "c1", args: {} }, + { title: "", output: "", metadata: { connections: { existing: 0, new_dbt: 1, new_docker: 0, new_env: 0 } } }, + ) + await settle() + + const menu = events.filter((e) => e.type === "activation_menu_shown") + expect(menu).toHaveLength(1) + expect((menu[0] as any).variant).toBe("warehouse") + }) + + test("a scan that finds nothing shows the no-data menu", async () => { + const events = captureEvents() + const hooks = await plugin() + await startOnboarding(hooks, "scan") + + await hooks["tool.execute.after"]!( + { tool: "project_scan", sessionID: SESSION, callID: "c1", args: {} }, + { title: "", output: "", metadata: { connections: { existing: 0, new_dbt: 0, new_docker: 0, new_env: 0 } } }, + ) + await settle() + + expect((events.find((e) => e.type === "activation_menu_shown") as any).variant).toBe("no_data") + }) + + test("loading a skill selects a job but does not complete one", async () => { + const events = captureEvents() + const hooks = await plugin() + await startOnboarding(hooks, "skip") + + // The skill tool loads an instruction bundle; the analysis itself happens afterwards through + // other tools. Reporting completion here would claim the job finished the moment the + // instructions were read. + await hooks["tool.execute.after"]!( + { tool: "skill", sessionID: SESSION, callID: "c2", args: { name: "dbt-analyze" } }, + { title: "", output: "", metadata: { name: "dbt-analyze" } }, + ) + await settle() + + const selected = events.filter((e) => e.type === "activation_job_selected") + expect(selected).toHaveLength(1) + expect((selected[0] as any).job).toBe("breaks_downstream") + expect(events.some((e) => e.type === "first_job_completed")).toBe(false) + }) + + test("the sample project both selects and completes a job", async () => { + const events = captureEvents() + const hooks = await plugin() + await startOnboarding(hooks, "skip") + + await hooks["tool.execute.after"]!( + { tool: "sample_setup", sessionID: SESSION, callID: "c3", args: {} }, + { title: "", output: "", metadata: { success: true } }, + ) + await settle() + + expect((events.find((e) => e.type === "activation_job_selected") as any).job).toBe("sample_duck_db") + expect((events.find((e) => e.type === "first_job_completed") as any).job).toBe("sample_duck_db") + }) + + test("a failed sample setup counts as selected but not completed", async () => { + const events = captureEvents() + const hooks = await plugin() + await startOnboarding(hooks, "skip") + + await hooks["tool.execute.after"]!( + { tool: "sample_setup", sessionID: SESSION, callID: "c4", args: {} }, + { title: "", output: "", metadata: { success: false } }, + ) + await settle() + + expect(events.some((e) => e.type === "activation_job_selected")).toBe(true) + expect(events.some((e) => e.type === "first_job_completed")).toBe(false) + }) + + test("only the first job counts as the activation job", async () => { + const events = captureEvents() + const hooks = await plugin() + await startOnboarding(hooks, "skip") + + await hooks["tool.execute.after"]!( + { tool: "sample_setup", sessionID: SESSION, callID: "c5", args: {} }, + { title: "", output: "", metadata: { success: true } }, + ) + await hooks["tool.execute.after"]!( + { tool: "skill", sessionID: SESSION, callID: "c6", args: { name: "sql-review" } }, + { title: "", output: "", metadata: {} }, + ) + await settle() + + expect(events.filter((e) => e.type === "activation_job_selected")).toHaveLength(1) + }) + + test("tools run outside an onboarding session emit nothing", async () => { + const events = captureEvents() + const hooks = await plugin() + + // No /onboard-connect for this session — an ordinary chat where someone happens to use a + // reviewable skill must not look like onboarding activation. + await hooks["tool.execute.after"]!( + { tool: "skill", sessionID: "ses_other", callID: "c7", args: { name: "sql-review" } }, + { title: "", output: "", metadata: {} }, + ) + await settle() + + expect(events).toEqual([]) + }) + + test("helper tools are not mistaken for activation jobs", async () => { + const events = captureEvents() + const hooks = await plugin() + await startOnboarding(hooks, "skip") + + for (const tool of ["read", "bash", "warehouse_add"]) { + await hooks["tool.execute.after"]!( + { tool, sessionID: SESSION, callID: tool, args: {} }, + { title: "", output: "", metadata: {} }, + ) + } + await settle() + + expect(events.some((e) => e.type === "activation_job_selected")).toBe(false) + }) +}) + +// --------------------------------------------------------------------------- +// Slash-command suppression, which first_prompt_sent depends on +// --------------------------------------------------------------------------- +describe("command submission tracking", () => { + test("a command-submitted message is flagged, once", () => { + Onboarding.noteCommandSubmission("ses_a") + expect(Onboarding.consumeCommandSubmission("ses_a")).toBe(true) + expect(Onboarding.consumeCommandSubmission("ses_a")).toBe(false) + }) + + test("a session that never ran a command is not flagged", () => { + expect(Onboarding.consumeCommandSubmission("ses_never")).toBe(false) + }) + + test("the flag does not leak between sessions", () => { + Onboarding.noteCommandSubmission("ses_a") + expect(Onboarding.consumeCommandSubmission("ses_b")).toBe(false) + expect(Onboarding.consumeCommandSubmission("ses_a")).toBe(true) + }) + + test("every slash command is flagged, not just /onboard-connect", async () => { + const hooks = await OnboardingTelemetryPlugin({} as any) + await hooks["command.execute.before"]!( + { command: "discover", sessionID: "ses_c", arguments: "" }, + { parts: [] } as any, + ) + expect(Onboarding.consumeCommandSubmission("ses_c")).toBe(true) + }) +}) + +// --------------------------------------------------------------------------- +// launch_id — added during envelope conversion, so a track() spy cannot see it +// --------------------------------------------------------------------------- +describe("launch correlation id", () => { + afterEach(async () => { + await Telemetry.shutdown() + mock.restore() + }) + + test("every event in a run carries the same launch_id", async () => { + const origDisabled = process.env.ALTIMATE_TELEMETRY_DISABLED + const origCs = process.env.APPLICATIONINSIGHTS_CONNECTION_STRING + const bodies: string[] = [] + const fetchMock = spyOn(global, "fetch").mockImplementation((async (_input: any, init: any) => { + bodies.push(String(init?.body ?? "")) + return new Response("", { status: 200 }) + }) as unknown as typeof fetch) + + try { + delete process.env.ALTIMATE_TELEMETRY_DISABLED + process.env.APPLICATIONINSIGHTS_CONNECTION_STRING = + "InstrumentationKey=k;IngestionEndpoint=https://example.invalid" + await Telemetry.init() + + Telemetry.track({ type: "onboarding_started", timestamp: 1, session_id: "" }) + Telemetry.track({ type: "scan_gate_choice", timestamp: 2, session_id: "ses_1", choice: "scan" }) + await Telemetry.flush() + + // Select by name rather than by position: the telemetry buffer is module-global, so a + // sibling test file can leave events in it and they flush alongside these. + const envelopes = JSON.parse(bodies[0]) as any[] + const byName = (name: string) => envelopes.find((e) => e.data.baseData.name === name) + const preSession = byName("onboarding_started") + const withSession = byName("scan_gate_choice") + expect(preSession).toBeDefined() + expect(withSession).toBeDefined() + + // The whole point: an event emitted before any session exists and one emitted with a real + // session must still be joinable to the same run. + expect(preSession.data.baseData.properties.launch_id).toBeTruthy() + expect(preSession.data.baseData.properties.launch_id).toBe(withSession.data.baseData.properties.launch_id) + } finally { + process.env.ALTIMATE_TELEMETRY_DISABLED = origDisabled + if (origCs !== undefined) process.env.APPLICATIONINSIGHTS_CONNECTION_STRING = origCs + else delete process.env.APPLICATIONINSIGHTS_CONNECTION_STRING + fetchMock.mockRestore() + } + }) +}) diff --git a/packages/opencode/test/altimate/tools/sample-setup.test.ts b/packages/opencode/test/altimate/tools/sample-setup.test.ts new file mode 100644 index 0000000000..5d08230248 --- /dev/null +++ b/packages/opencode/test/altimate/tools/sample-setup.test.ts @@ -0,0 +1,210 @@ +/** + * sample_setup tool — LLM-invoked wrapper around materializeSample(). + * + * The template at packages/opencode/src/command/template/onboard-connect.txt + * branches on the tool's `output` (never `metadata` — the model only sees + * `output`, per packages/opencode/src/session/message-v2.ts:822). The + * `output` starts with a `status:` line that identifies success vs error; + * these tests pin that contract shape end-to-end through `tool.execute`. + */ + +import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test" +import fs from "node:fs" +import os from "node:os" +import path from "node:path" +import { SampleSetupTool } from "../../../src/altimate/tools/sample-setup" +import { MARKER_KIND, readMarker, writeMarker } from "../../../src/altimate/onboarding/marker" +import { initTool, type TestTool } from "../tool-fixture" + +let tool: TestTool +beforeAll(async () => { + tool = await initTool(SampleSetupTool) +}) + +const CTX: any = { sessionID: "test-session" } + +const ORIG_HOMEDIR = os.homedir +function pinHomedirTo(dir: string) { + Object.defineProperty(os, "homedir", { value: () => dir, configurable: true }) +} +afterEach(() => { + Object.defineProperty(os, "homedir", { value: ORIG_HOMEDIR, configurable: true }) +}) + +// Scratch dirs carved out of the REAL home directory. The sample_setup tool +// intentionally does NOT expose allowUnsafeParent on its LLM-facing schema +// (that would be a bypass of rejectUnsafeHome), so a fake home used to +// exercise the tool must survive rejectUnsafeHome on its own — which means +// NOT under os.tmpdir() (rejected as ephemeral), NOT /tmp/* (same), NOT +// /root as non-root. The user's real home is the one reliably-safe parent +// available across macOS + Linux CI; scoping into a UUID-suffixed subdir +// under it keeps the tests hermetic while still exercising the guard end- +// to-end. +const CREATED_SCRATCH_HOMES: string[] = [] +function makeTmpHome(prefix: string): string { + const realHome = os.homedir() + const scratchHome = path.join(realHome, `.altimate-sample-setup-test-${prefix}-${Date.now().toString(36)}-${process.pid}`) + fs.mkdirSync(scratchHome, { recursive: true }) + CREATED_SCRATCH_HOMES.push(scratchHome) + return scratchHome +} +afterAll(() => { + // Best-effort cleanup of every scratch home the tests created under the + // user's real HOME. Individual test failures should not leak scratch + // dirs; wrap each in try/catch so one stubborn dir doesn't block the + // rest. + for (const dir of CREATED_SCRATCH_HOMES) { + try { fs.rmSync(dir, { recursive: true, force: true }) } catch { /* ignore */ } + } +}) + +describe("sample_setup tool — LLM-facing contract", () => { + test("fresh materialize → output starts with 'status: ok', includes path/reused/suffix", async () => { + const home = makeTmpHome("fresh") + pinHomedirTo(home) + const result = await tool.execute({ preferred_target_name: "sample", allow_in_place_upgrade: false }, CTX) + // Template branches on the FIRST LINE of output — assert that first. + const firstLine = result.output.split("\n")[0] + expect(firstLine).toBe("status: ok") + // Body must carry the fields the template's routing table reads. + expect(result.output).toContain(`path: ${path.join(home, "sample")}`) + expect(result.output).toContain("reused: false") + expect(result.output).toContain("suffix: 0") + // Metadata carries the same info + the success flag (telemetry contract). + expect(result.metadata.success).toBe(true) + expect(result.metadata.targetPath).toBe(path.join(home, "sample")) + // Sanity: the materialized dir has the marker. + expect(readMarker(result.metadata.targetPath as string)?.kind).toBe(MARKER_KIND) + }) + + test("second call to same target → 'status: ok' + 'reused: true' (template branch c)", async () => { + const home = makeTmpHome("reuse") + pinHomedirTo(home) + await tool.execute({ preferred_target_name: "sample", allow_in_place_upgrade: false }, CTX) + const second = await tool.execute({ preferred_target_name: "sample", allow_in_place_upgrade: false }, CTX) + expect(second.output).toContain("status: ok") + expect(second.output).toContain("reused: true") + expect(second.metadata.success).toBe(true) + }) + + test("preferred name taken by unrelated content → suffix carries a non-zero value (template branch d)", async () => { + const home = makeTmpHome("collide") + pinHomedirTo(home) + const preferred = path.join(home, "sample") + fs.mkdirSync(preferred, { recursive: true }) + fs.writeFileSync(path.join(preferred, "user-file.txt"), "important") + const result = await tool.execute({ preferred_target_name: "sample", allow_in_place_upgrade: false }, CTX) + expect(result.output).toContain("status: ok") + expect(result.output).toContain("reused: false") + expect(result.output).toContain("suffix: 1") + expect(result.output).toContain(`path: ${path.join(home, "sample-2")}`) + // User's file untouched. + expect(fs.readFileSync(path.join(preferred, "user-file.txt"), "utf8")).toBe("important") + }) + + test("existing our-sample at different version → 'reused: true' + 'Caller must prompt' note (template branch b)", async () => { + const home = makeTmpHome("diffver") + pinHomedirTo(home) + const preferred = path.join(home, "sample") + fs.mkdirSync(preferred, { recursive: true }) + writeMarker(preferred, { + kind: MARKER_KIND, + sampleName: "jaffle-shop-duckdb", + version: "0.9.0", + materializedAt: "2020-01-01T00:00:00.000Z", + cliVersion: "0.9.0-old", + }) + const result = await tool.execute({ preferred_target_name: "sample", allow_in_place_upgrade: false }, CTX) + expect(result.output).toContain("status: ok") + expect(result.output).toContain("reused: true") + expect(result.output).toContain("Caller must prompt") + }) + + test("unsafe HOME → 'status: error' + verbatim actionable message in output (template branch a)", async () => { + // Simulate the /root-as-non-root case rejectUnsafeHome catches: pin + // os.homedir() to /tmp/xyz, since /tmp/* is universally refused. + pinHomedirTo("/tmp/xyz-unsafe") + const result = await tool.execute({ preferred_target_name: "sample", allow_in_place_upgrade: false }, CTX) + // FIRST line — that's what the template reads before deciding to show + // the sample menu vs surface the message. + const firstLine = result.output.split("\n")[0] + expect(firstLine).toBe("status: error") + expect(result.output).toContain("reason: materialize_failed") + // Body carries the guard's actionable text. + expect(result.output).toContain("ephemeral") + // Metadata records the failure for telemetry. + expect(result.metadata.success).toBe(false) + expect(result.metadata.error).toBeDefined() + }) + + test("install_alongside=true skips version-mismatched slot 0 → lands at sample-2 with fresh marker (template branch b→install-alongside routing)", async () => { + const home = makeTmpHome("alongside") + pinHomedirTo(home) + // Seed slot 0 with an older-version sample so install_alongside has + // something to route around. + const old = path.join(home, "sample") + fs.mkdirSync(old, { recursive: true }) + writeMarker(old, { + kind: MARKER_KIND, + sampleName: "jaffle-shop-duckdb", + version: "0.5.0", + materializedAt: "2020-01-01T00:00:00.000Z", + cliVersion: "0.5.0-old", + }) + // Also seed a canary file in the old slot to prove install_alongside + // leaves it untouched. + fs.writeFileSync(path.join(old, "user-note.md"), "keep me") + + const result = await tool.execute( + { preferred_target_name: "sample", allow_in_place_upgrade: false, install_alongside: true }, + CTX, + ) + expect(result.output).toContain("status: ok") + expect(result.output).toContain("reused: false") + // Suffix carries a non-zero value; the template reads `path:` for the + // canonical location so we assert on that. + expect(result.output).toContain(`path: ${path.join(home, "sample-2")}`) + // Old slot 0 untouched — its marker still says 0.5.0 and the canary is there. + expect(readMarker(old)?.version).toBe("0.5.0") + expect(fs.existsSync(path.join(old, "user-note.md"))).toBe(true) + // New slot has the current-version marker. + expect(readMarker(path.join(home, "sample-2"))?.kind).toBe(MARKER_KIND) + }) + + test("output includes a `dbt:` line the template's 'Build & query it' branch reads (finding 18)", async () => { + // The template routes "Build & query it" by reading a `dbt:` line from + // the sample_setup output — if that line disappears, the template's + // build branch has to shell out again and duplicates work the tool + // already did. Assert both the presence and the "present"/"missing" + // dichotomy so a regression that dropped the field is caught. + const home = makeTmpHome("dbtline") + pinHomedirTo(home) + const result = await tool.execute({ preferred_target_name: "sample", allow_in_place_upgrade: false }, CTX) + expect(result.output).toContain("status: ok") + const dbtLine = result.output.split("\n").find((l) => l.startsWith("dbt:")) + expect(dbtLine, "output missing `dbt:` line — template's Build & query it branch would have to shell out").toBeDefined() + // Exactly one of the two documented shapes: + // dbt: present (dbt-core X, duckdb-adapter present|missing) + // dbt: missing (dbt-core not on PATH) + expect(dbtLine).toMatch(/^dbt: (present \(dbt-core .*, duckdb-adapter (present|missing)\)|missing \(dbt-core not on PATH\))$/) + }) + + test("tool boundary rejects bad preferred_target_name via the Zod schema (finding 29)", async () => { + // preferred_target_name with a path separator should be refused at the + // schema level (Zod regex on the tool argument), BEFORE materializeSample + // even runs. tool.execute surfaces this as InvalidArgumentsError, so + // the outer test guards against BOTH the throw AND the "no write + // happened" contract — a regression that silently accepted "../foo" + // would land on disk and this test would catch it. + const home = makeTmpHome("zod-guard") + pinHomedirTo(home) + await expect( + tool.execute( + { preferred_target_name: "../escape", allow_in_place_upgrade: false } as any, + CTX, + ), + ).rejects.toThrow(/invalid arguments|SchemaError|Expected/i) + // No fs write happened — schema stopped it. + expect(fs.readdirSync(home)).toEqual([]) + }) +}) diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index abb2a2d641..55bccbe7c7 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -9,6 +9,12 @@ import { UPGRADE_KV_KEY } from "./component/upgrade-indicator-utils" // altimate_change end import { ClipboardProvider, useClipboard } from "./context/clipboard" import { ExitProvider, useExit } from "./context/exit" +// altimate_change — onboarding funnel telemetry seam +import { + OnboardingTelemetryProvider, + useOnboardingTelemetry, + type TrackOnboarding, +} from "./context/onboarding-telemetry" import { EpilogueProvider } from "./context/epilogue" import * as Selection from "./util/selection" import { createCliRenderer, MouseButton, type CliRenderer } from "@opentui/core" @@ -28,7 +34,13 @@ import { } from "solid-js" import { TuiPathsProvider, TuiStartupProvider, TuiTerminalEnvironmentProvider, useTuiStartup } from "./context/runtime" import { DialogProvider, useDialog } from "./ui/dialog" -import { DialogProvider as DialogProviderList } from "./component/dialog-provider" +// altimate_change start — /auth (gateway sign-in) + /connect (curated welcome picker) +// + /logout commands +import { DialogAltimateAuth } from "./component/dialog-provider" +import { DialogModelWelcome, useReady, resetSetupComplete } from "./component/altimate-onboarding" +// altimate_change end +// altimate_change — Part 2 scan gate (fires once when Part 1 first completes) +import { DialogScanGate } from "./component/dialog-scan-gate" import { ErrorComponent } from "./component/error-component" import { PluginRouteMissing } from "./component/plugin-route-missing" import { ProjectProvider, useProject } from "./context/project" @@ -147,6 +159,9 @@ export type TuiInput = { headers?: RequestInit["headers"] events?: EventSource pluginHost: TuiPluginHost + // altimate_change — onboarding funnel telemetry, injected by the host (packages/tui cannot + // reach the Telemetry module). Optional: absent means no tracking, not an error. + onTelemetry?: TrackOnboarding } function errorMessage(error: unknown) { @@ -250,6 +265,11 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { destroyRenderer(renderer) }} > + {/* altimate_change start — onboarding telemetry seam. Mounted here, above + DialogProvider, because dialog contents render as a sibling of DialogProvider's + children and cannot see anything provided inside it. Defaults to a no-op so + hosts that do not supply a tracker (tests, embedders) work unchanged. */} + {})}> (exit.epilogue = value)}> }> + + {/* altimate_change end */} ) }, renderer) @@ -535,18 +557,76 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi }) }) + // altimate_change start — connection + onboarding readiness. `connected` tracks a + // paid/BYOK provider; `onboardingReady` also counts a completed first-run setup pick + // (e.g. Big Pickle) and gates first-run chat/tips (see component/altimate-onboarding.tsx). + // Distinct from the plugin-host `ready` signal above (line ~408), which tracks TUI + // plugin startup, not onboarding state. + const connected = useConnected() + const onboardingReady = useReady() + // altimate_change — onboarding funnel tracker (no-op when the host injected none) + const trackOnboarding = useOnboardingTelemetry() + // altimate_change end + + // altimate_change start — AI-7774: first-run onboarding gate. On a fresh launch + // with no usable model, open the curated provider picker as the entry point (chat + // input stays visible; submit is gated in the prompt until setup completes). Fire + // EXACTLY once, and only after startup has settled (`ready()` = plugin host + + // sync bootstrap done), so a returning user with valid credentials never sees it. + let firstRunPickerHandled = false + createEffect(() => { + if (firstRunPickerHandled) return + if (!ready()) return // wait until providers are loaded before deciding + firstRunPickerHandled = true + if (onboardingReady()) return // already set up — no gate + // altimate_change — funnel: top of the first-run flow. Emitted only on the branch that + // actually opens the gate, so returning users never enter the funnel. + trackOnboarding({ name: "onboarding_started" }) + dialog.replace(() => ) + }) + // altimate_change end + + // altimate_change start — Part 2 scan gate: fire EXACTLY once, on the first time + // this session reaches "ready" from a not-ready start (i.e. the user just finished + // Part 1 in a fresh onboarding). `prev === false` guarantees a genuine false→true + // transition, so a returning user (ready from launch, prev === undefined) never + // sees it, and a later /model change (ready stays true, no transition) never + // re-triggers it. We do NOT auto-scan — the gate just asks. + let scanGateShown = false createEffect( - on( - () => sync.status === "complete" && sync.data.provider.length === 0, - (isEmpty, wasEmpty) => { - // only trigger when we transition into an empty-provider state - if (!isEmpty || wasEmpty) return - dialog.replace(() => ) - }, - ), + on(onboardingReady, (isReady, prev) => { + if (scanGateShown) return + if (isReady && prev === false) { + scanGateShown = true + // altimate_change — funnel: Part 1 finished (a model is ready) and the gate is opening. + // This false→true transition is exactly the ticket's "onboarding_completed" definition, + // so both events are emitted from it. + trackOnboarding({ name: "onboarding_completed" }) + trackOnboarding({ name: "scan_gate_shown" }) + dialog.replace(() => ( + { + // altimate_change — funnel: emitted here rather than inside the gate because the + // dialog overlay renders outside the provider tree; `onChoose` is already the + // established way to hand the gate something it cannot reach itself. + trackOnboarding({ name: "scan_gate_choice", choice: arg }) + // Yes → /onboard-connect scan; No → /onboard-connect skip. + // Both branches now have a real follow-up: `scan` runs + // project_scan and branches into the discovery UX; `skip` + // asks what the user is working on and offers the activation + // menu (sample dbt, downstream impact, SQL PR, or free chat). + // Template lives at packages/opencode/src/command/template/onboard-connect.txt. + const ref = promptRef.current + if (!ref) return + ref.set({ input: `/onboard-connect ${arg}`, parts: [] }) + ref.submit() + }} + /> + )) + } + }), ) - - const connected = useConnected() + // altimate_change end const currentWorktreeWorkspace = createMemo(() => { const workspaceID = project.workspace.current() if (!workspaceID) return @@ -733,16 +813,46 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi local.agent.move(-1) }, }, + // altimate_change start — /connect opens the curated welcome picker (Gateway + top + // BYOK providers + Big Pickle) instead of the full provider list; "Search all + // providers…" still hands off to the full DialogModel catalog. { name: "provider.connect", - title: "Connect provider", + title: "Connect to your AI model provider", suggested: !connected(), slashName: "connect", run: () => { - dialog.replace(() => ) + dialog.replace(() => ) + }, + category: "Provider", + }, + // altimate_change end + // altimate_change start — /auth: sign in to the Altimate LLM Gateway directly; + // /logout: clear the stored gateway credential and disconnect. + { + name: "altimate.auth", + title: "Sign in to Altimate LLM Gateway", + suggested: !connected(), + slashName: "auth", + run: () => { + dialog.replace(() => ) }, category: "Provider", }, + { + name: "altimate.logout", + title: "Sign out of Altimate LLM Gateway", + slashName: "logout", + run: () => { + // altimate_change — the credential clear + instance dispose + toast happen + // opencode-side (AltimateApi is unreachable from packages/tui); see + // packages/opencode/src/plugin/tui/altimate/provider-credentials.tsx. + keymap.dispatchCommand("altimate.provider.logout") + resetSetupComplete() + }, + category: "Provider", + }, + // altimate_change end ...(sync.data.console_state.switchableOrgCount > 1 ? [ { @@ -1015,6 +1125,10 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi // altimate_change start — upstream_fix: persist update availability for footer indicator kv.set(UPGRADE_KV_KEY, version) // altimate_change end + // altimate_change start — don't cover the first-run welcome panel with the update + // confirm dialog; the footer upgrade indicator still surfaces it. Prompt once ready. + if (!onboardingReady()) return + // altimate_change end const skipped = kv.get("skipped_version") if (skipped && !isVersionGreater(version, skipped)) return diff --git a/packages/tui/src/component/altimate-onboarding.tsx b/packages/tui/src/component/altimate-onboarding.tsx new file mode 100644 index 0000000000..7055a752ea --- /dev/null +++ b/packages/tui/src/component/altimate-onboarding.tsx @@ -0,0 +1,379 @@ +// Altimate onboarding layer — kept in a dedicated, altimate-owned file so it does +// NOT enlarge the rebase surface of the upstream `dialog-model.tsx`. Holds the +// first-run readiness state, the curated welcome/provider picker, and the Big +// Pickle interstitial. Imports back into dialog-model are runtime-only (used inside +// callbacks/JSX), so the circular reference is safe. +import { createMemo, createSignal, For, Show, onMount } from "solid-js" +import { useLocal } from "../context/local" +import { useDialog } from "../ui/dialog" +import { useTheme, selectedForeground } from "../context/theme" +import { TextAttributes, RGBA } from "@opentui/core" +import { useKeyboard } from "@opentui/solid" +import { createDialogProviderOptions } from "./dialog-provider" +import { DialogModel } from "./dialog-model" +import { useConnected } from "./use-connected" +// altimate_change — onboarding funnel telemetry seam +import { useOnboardingTelemetry } from "../context/onboarding-telemetry" + +// Session-scoped "setup complete" flag. Set when the user picks a ready model, +// chooses the free Big Pickle option, or finishes the gateway flow. Combined with +// useConnected() (real credentials) via useReady(), it gates the first-run chat +// lock. Module-global so it is shared across the app and resets on every process +// launch (so a fresh relaunch is a clean fresh-user state). +const [setupComplete, setSetupComplete] = createSignal(false) +export function markSetupComplete() { + setSetupComplete(true) +} +// Cleared on /logout so first-run tips don't keep showing "you're all set" after +// the credential is gone. +export function resetSetupComplete() { + setSetupComplete(false) +} +export function useReady() { + const connected = useConnected() + return createMemo(() => connected() || setupComplete()) +} + +// First-run welcome picker (presentation only; reuses the same action handlers as +// DialogModel/createDialogProviderOptions). A curated six: five recommended +// providers + a "Search all providers…" row that hands off to the full DialogModel +// picker. The long tail stays behind search. +const NAME_W = 24 +type WelcomeTone = "success" | "warning" | "muted" + +interface WelcomeRow { + name: string + note: string + tone: WelcomeTone + activate: () => void + // altimate_change — funnel: the row's identity in the spec's provider enum. Kept as its own + // field rather than derived from providerID/modelID so the enum cannot drift from the row list + // (the "search all" row has no provider at all, and Big Pickle is a model, not a provider). + analyticsProvider: "altimate_gateway" | "anthropic" | "openai" | "google" | "big_pickle" | "search_all" + // Identifies the row for the "currently selected" tick. providerID alone matches + // any model of that provider; add modelID to match a specific model (Big Pickle). + providerID?: string + modelID?: string +} + +export function DialogModelWelcome(props: { + intro?: string + // altimate_change — funnel: which path opened the picker. It also opens from /connect, from + // declining Big Pickle, and from the prompt gate, so without this every impression would read + // as a fresh first run. Defaults to the /connect case since that is the only caller that does + // not pass one explicitly. + trigger?: "first_run" | "connect_command" | "big_pickle_back" | "prompt_gate" +}) { + const { theme } = useTheme() + const dialog = useDialog() + const local = useLocal() + const providers = createDialogProviderOptions() + const [selected, setSelected] = createSignal(0) + // altimate_change start — funnel: picker impression + provider choice + const trackOnboarding = useOnboardingTelemetry() + onMount(() => trackOnboarding({ name: "model_picker_shown", trigger: props.trigger ?? "connect_command" })) + // altimate_change end + + onMount(() => dialog.setSize("large")) + + function connectProvider(id: string) { + // Reuse the exact provider onSelect (gateway flow for altimate-backend, + // auth-method screens for the BYOK providers). + providers() + .find((o) => o.value === id) + ?.onSelect?.() + } + + function chooseBigPickle() { + dialog.replace(() => ) + } + + function openFullCatalog() { + dialog.replace(() => ) + } + + const rows = createMemo(() => [ + { + name: "Altimate LLM Gateway", + note: "Recommended · best tool-calling · 10M free tokens", + tone: "success", + providerID: "altimate-backend", + activate: () => connectProvider("altimate-backend"), + analyticsProvider: "altimate_gateway", + }, + { + name: "Anthropic (Claude)", + note: "bring your own API key", + tone: "muted", + providerID: "anthropic", + activate: () => connectProvider("anthropic"), + analyticsProvider: "anthropic", + }, + { + name: "OpenAI (GPT)", + note: "bring your own API key", + tone: "muted", + providerID: "openai", + activate: () => connectProvider("openai"), + analyticsProvider: "openai", + }, + { + name: "Google (Gemini)", + note: "bring your own API key", + tone: "muted", + providerID: "google", + activate: () => connectProvider("google"), + analyticsProvider: "google", + }, + { + name: "Big Pickle", + note: "free, no signup — slower, unreliable tool-calling", + tone: "warning", + providerID: "opencode", + modelID: "big-pickle", + activate: chooseBigPickle, + analyticsProvider: "big_pickle", + }, + { + name: "Search all providers…", + note: "/", + tone: "muted", + activate: openFullCatalog, + analyticsProvider: "search_all", + }, + ]) + + // The currently active model → drives the green "selected" tick. + const current = createMemo(() => local.model.current()) + const isCurrent = (row: WelcomeRow) => { + const c = current() + if (!row.providerID || !c || c.providerID !== row.providerID) return false + return row.modelID ? c.modelID === row.modelID : true + } + + // altimate_change — funnel: single choke point for row activation so keyboard and mouse + // cannot diverge. Fires on selection, before auth resolves: a cancelled or failed sign-in + // still counts as a provider having been chosen, which is what the funnel step means. + // Guarded: keyboard return and mouse-up both reach here, and nothing stops two firing before + // the dialog unmounts — a fast double input would both double-count and start the provider + // flow twice. Per instance, so re-opening the picker is a genuinely new selection. + let activated = false + function activateRow(row: WelcomeRow) { + if (activated) return + activated = true + trackOnboarding({ name: "provider_selected", provider: row.analyticsProvider }) + row.activate() + } + + // Indices 0-4 are providers, 5 is the search row (rendered below a divider). + const COUNT = 6 + function move(direction: number) { + setSelected((prev) => (prev + direction + COUNT) % COUNT) + } + + useKeyboard((evt) => { + if (evt.name === "up" || (evt.ctrl && evt.name === "p")) return move(-1) + if (evt.name === "down" || (evt.ctrl && evt.name === "n")) return move(1) + if (evt.name === "return") { + evt.preventDefault() + evt.stopPropagation() + activateRow(rows()[selected()]) + return + } + // "/", ctrl+a, or any letter/number reveals the full searchable catalog. + if (evt.name === "/" || (evt.ctrl && evt.name === "a") || /^[a-z0-9]$/i.test(evt.name ?? "")) { + evt.preventDefault() + // altimate_change — the "/" shortcut is the same intent as the "Search all providers…" + // row, so it routes through the same guarded path. + activateRow(rows()[5]) + } + }) + + const selFg = selectedForeground(theme) + const transparent = RGBA.fromInts(0, 0, 0, 0) + const noteColor = (tone: WelcomeTone) => + tone === "success" ? theme.success : tone === "warning" ? theme.warning : theme.textMuted + + const Row = (props: { row: WelcomeRow; index: number; onActivate: (row: WelcomeRow) => void }) => { + const active = createMemo(() => selected() === props.index) + return ( + setSelected(props.index)} + onMouseUp={() => props.onActivate(props.row)} + > + + {active() ? "›" : " "} + + + + {props.row.name} + + + {/* bright green so it reads clearly even where ANSI green renders dim */} + + {isCurrent(props.row) ? "✓" : " "} + + + {isCurrent(props.row) ? `${props.row.note} · selected` : props.row.note} + + + ) + } + + return ( + + + + {props.intro} + + + + + + Select a provider + + — you can change this anytime with /model + + + {(row, i) => } + + + + + + ) +} + +// Big Pickle interstitial — one confirm, default No. Custom component (not +// DialogSelect) so the full warning wraps instead of clipping; y/n keys work, +// enter accepts the highlighted row (No by default). +export function DialogBigPickleConfirm(props: { origin: "welcome" | "model" }) { + const { theme } = useTheme() + const dialog = useDialog() + const local = useLocal() + const [selected, setSelected] = createSignal(0) // 0 = No (default) + // altimate_change start — funnel: interstitial impression + decision. + // `decided` guards against a double-submit: keyboard and mouse handlers both call yes()/no() + // directly, and nothing prevents two firing before the dialog unmounts. + const trackOnboarding = useOnboardingTelemetry() + let decided = false + onMount(() => trackOnboarding({ name: "big_pickle_confirm_shown", origin: props.origin })) + // altimate_change end + + function no() { + // altimate_change start + if (decided) return + decided = true + trackOnboarding({ name: "big_pickle_choice", choice: "cancel" }) + // altimate_change end + dialog.replace(() => + props.origin === "welcome" ? : , + ) + } + function yes() { + // altimate_change start + if (decided) return + decided = true + trackOnboarding({ name: "big_pickle_choice", choice: "accept" }) + // altimate_change end + dialog.clear() + local.model.set({ providerID: "opencode", modelID: "big-pickle" }, { recent: true }) + markSetupComplete() + } + const options = [ + { label: "No — pick something else", hint: "(default)", run: no }, + { label: "Yes — continue with Big Pickle", hint: "", run: yes }, + ] + + useKeyboard((evt) => { + if (evt.name === "up" || evt.name === "down") { + setSelected((prev) => (prev + 1) % 2) + evt.preventDefault() + return + } + if (evt.name === "return") { + evt.preventDefault() + evt.stopPropagation() + options[selected()].run() + return + } + if (evt.name === "y" && !evt.ctrl && !evt.meta) { + evt.preventDefault() + yes() + return + } + if (evt.name === "n" && !evt.ctrl && !evt.meta) { + evt.preventDefault() + no() + } + }) + + const selFg = selectedForeground(theme) + const transparent = RGBA.fromInts(0, 0, 0, 0) + + return ( + + + + Use Big Pickle? + + dialog.clear()}> + esc + + + + Big Pickle works for chat but often fails at data tasks. The Gateway is free to start (10M tokens). Continue? + [y/N] + + + + {(option, index) => ( + setSelected(index())} onMouseUp={() => option.run()}> + + {selected() === index() ? "›" : " "} + + + + {option.label} + + + + {option.hint} + + + )} + + + + ) +} diff --git a/packages/tui/src/component/command-palette.tsx b/packages/tui/src/component/command-palette.tsx index 3dd6829c54..67df2d07a3 100644 --- a/packages/tui/src/component/command-palette.tsx +++ b/packages/tui/src/component/command-palette.tsx @@ -9,6 +9,8 @@ import { useOpencodeKeymap, } from "../keymap" import { useTuiConfig } from "../config" +// altimate_change — first-run command filtering +import { useReady } from "./altimate-onboarding" type PaletteCommandEntry = ReturnType[number] @@ -45,8 +47,13 @@ export function CommandPaletteDialog() { bindings: registeredBindings.get(entry.command.name) ?? entry.bindings, })) }) - const options = createMemo(() => - entries().map((entry) => ({ + // altimate_change start — first run: until a model is ready, the command palette + // surfaces only commands that work without a provider (connect first, then a few + // utilities). Full palette returns once a provider is set up. + const FIRST_RUN_COMMANDS = ["provider.connect", "help.show", "theme.switch", "opencode.status", "app.exit"] + const ready = useReady() + const options = createMemo(() => { + const all = entries().map((entry) => ({ title: typeof entry.command.title === "string" ? entry.command.title : entry.command.name, description: typeof entry.command.desc === "string" ? entry.command.desc : undefined, category: typeof entry.command.category === "string" ? entry.command.category : undefined, @@ -57,8 +64,15 @@ export function CommandPaletteDialog() { dialog.clear() keymap.dispatchCommand(entry.command.name) }, - })), - ) + })) + if (!ready()) + return FIRST_RUN_COMMANDS.flatMap((name) => { + const match = all.find((option) => option.value === name) + return match ? [match] : [] + }) + return all + }) + // altimate_change end let ref: DialogSelectRef const list = () => { diff --git a/packages/tui/src/component/dialog-model.tsx b/packages/tui/src/component/dialog-model.tsx index ff8715ef85..9b5027ff41 100644 --- a/packages/tui/src/component/dialog-model.tsx +++ b/packages/tui/src/component/dialog-model.tsx @@ -1,14 +1,23 @@ import { createMemo, createSignal } from "solid-js" import { useLocal } from "../context/local" -import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda" +import { useSync } from "../context/sync" +import { map, pipe, flatMap, entries, filter, sortBy } from "remeda" import { DialogSelect } from "../ui/dialog-select" import { useDialog } from "../ui/dialog" -import { createDialogProviderOptions, DialogProvider } from "./dialog-provider" +import { createDialogProviderOptions, DialogProvider, WARNLIST, PROVIDER_PRIORITY } from "./dialog-provider" import { DialogVariant } from "./dialog-variant" import * as fuzzysort from "fuzzysort" import { useConnected } from "./use-connected" -import { useSync } from "../context/sync" +// altimate_change — onboarding helpers (readiness state, welcome picker, Big Pickle +// interstitial) live in the altimate-owned ./altimate-onboarding to keep this +// upstream file's rebase surface small. markSetupComplete / DialogBigPickleConfirm +// are used by the restructured DialogModel below. +import { markSetupComplete, DialogBigPickleConfirm } from "./altimate-onboarding" +// altimate_change start — DialogModel restructured from the upstream flat +// favorites/recent/provider list into READY / NEEDS-SETUP sections with a Big Pickle +// fallback. This is an in-place rewrite of the upstream component; on an upstream +// merge, expect a conflict here and re-apply the READY/NEEDS-SETUP shaping. export function DialogModel(props: { providerID?: string }) { const local = useLocal() const sync = useSync() @@ -18,112 +27,98 @@ export function DialogModel(props: { providerID?: string }) { const connected = useConnected() const providers = createDialogProviderOptions() - const showExtra = createMemo(() => connected() && !props.providerID) + // A provider is "ready" (usable now) when it has valid credentials: it is present + // in the live provider list with at least one model — and, for the free OpenCode + // provider, with at least one paid model (a Zen key entered). + function providerReady(id: string) { + const p = sync.data.provider.find((x) => x.id === id) + if (!p) return false + if (id === "opencode") return Object.values(p.models).some((m) => m.cost?.input != null && m.cost.input !== 0) + return Object.keys(p.models).length > 0 + } const options = createMemo(() => { const needle = query().trim() - const showSections = showExtra() && needle.length === 0 - const favorites = connected() ? local.model.favorite() : [] - const recents = local.model.recent() - - function toOptions(items: typeof favorites, category: string) { - if (!showSections) return [] - return items.flatMap((item) => { - const provider = sync.data.provider.find((provider) => provider.id === item.providerID) - if (!provider) return [] - const model = provider.models[item.modelID] - if (!model) return [] - return [ - { - key: item, - value: { providerID: provider.id, modelID: model.id }, - title: model.name ?? item.modelID, - description: provider.name, - category, - disabled: provider.id === "opencode" && model.id.includes("-nano"), - footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined, - onSelect: () => { - onSelect(provider.id, model.id) - }, - }, - ] - }) - } + const favorites = local.model.favorite() - const favoriteOptions = toOptions(favorites, "Favorites") - const recentOptions = toOptions( - recents.filter( - (item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID), - ), - "Recent", - ) - - const providerOptions = pipe( + // READY — models from providers that already have valid credentials. Selecting + // one switches instantly. + const readyOptions = pipe( sync.data.provider, - sortBy( - (provider) => provider.id !== "opencode", - (provider) => provider.name, - ), + filter((provider) => providerReady(provider.id)), + // altimate_change — order ready providers by the same PROVIDER_PRIORITY as the + // welcome/NEEDS-SETUP lists so the Altimate LLM Gateway leads the full list too. + sortBy((provider) => PROVIDER_PRIORITY[provider.id] ?? 99), flatMap((provider) => pipe( provider.models, entries(), filter(([_, info]) => info.status !== "deprecated"), filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)), - map(([model, info]) => ({ - value: { providerID: provider.id, modelID: model }, - title: info.name ?? model, - releaseDate: info.release_date, - description: favorites.some((item) => item.providerID === provider.id && item.modelID === model) - ? "(Favorite)" - : undefined, - category: connected() ? provider.name : undefined, - disabled: provider.id === "opencode" && model.includes("-nano"), - footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined, - onSelect() { - onSelect(provider.id, model) - }, - })), - filter((option) => { - if (!showSections) return true - if ( - favorites.some( - (item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID, - ) - ) - return false - if ( - recents.some( - (item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID, - ) - ) - return false - return true + map(([modelID, info]) => { + const warn = WARNLIST[modelID] + const isFav = favorites.some((f) => f.providerID === provider.id && f.modelID === modelID) + return { + value: { providerID: provider.id, modelID } as { providerID: string; modelID: string } | string, + title: info.name ?? modelID, + description: warn ? `${provider.name} · ${warn}` : provider.name, + category: "READY", + footer: isFav ? "★" : undefined, + onSelect() { + // altimate_change — go through the shared onSelect(providerID, modelID) + // helper so a ready pick also honors the model-variant follow-up flow, + // and mark setup complete so the first-run chat lock lifts. + onSelect(provider.id, modelID) + markSetupComplete() + }, + } }), - (options) => sortModelOptions(options, props.providerID !== undefined), + sortBy((x) => x.title), ), ), ) - const popularProviders = !connected() - ? pipe( - providers(), - map((option) => ({ - ...option, - category: "Popular providers", - })), - take(6), - ) - : [] + // NEEDS SETUP — providers without valid credentials (selecting routes into their + // auth flow first), plus the free Big Pickle option. Hidden when scoped to one + // provider (post-connect model list). + const setupOptions = props.providerID + ? [] + : (() => { + const list = providers() + .filter((o) => !providerReady(o.value)) + .map((o) => ({ + value: o.value as { providerID: string; modelID: string } | string, + title: o.title, + description: o.description, + category: "NEEDS SETUP", + footer: undefined as string | undefined, + onSelect: o.onSelect, + })) + const bigPickle = { + value: "big-pickle" as { providerID: string; modelID: string } | string, + title: "Big Pickle", + description: "free, no signup — slower, unreliable tool-calling", + category: "NEEDS SETUP", + footer: undefined as string | undefined, + async onSelect() { + dialog.replace(() => ) + }, + } + // Big Pickle sits at priority 4 — just above OpenCode Zen (priority 5). + const zenIdx = list.findIndex((o) => o.value === "opencode") + if (zenIdx === -1) list.push(bigPickle) + else list.splice(zenIdx, 0, bigPickle) + return list + })() if (needle) { return [ - ...fuzzysort.go(needle, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj), - ...fuzzysort.go(needle, popularProviders, { keys: ["title"] }).map((x) => x.obj), + ...fuzzysort.go(needle, readyOptions, { keys: ["title", "description"] }).map((x) => x.obj), + ...fuzzysort.go(needle, setupOptions, { keys: ["title", "description"] }).map((x) => x.obj), ] } - return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders] + return [...readyOptions, ...setupOptions] }) const provider = createMemo(() => @@ -167,6 +162,9 @@ export function DialogModel(props: { providerID?: string }) { title: "Favorite", hidden: !connected(), onTrigger: (option) => { + // altimate_change — NEEDS-SETUP rows carry plain string values (provider + // ids / "big-pickle"); only real {providerID, modelID} rows are favoritable. + if (typeof option.value === "string") return local.model.toggleFavorite(option.value as { providerID: string; modelID: string }) }, }, @@ -179,7 +177,11 @@ export function DialogModel(props: { providerID?: string }) { /> ) } +// altimate_change end +// altimate_change — kept for packages/tui/test/cli/cmd/tui/model-options.test.ts; +// no longer used internally by DialogModel above (see READY/NEEDS-SETUP shaping), +// but preserved as a public export so the existing upstream test keeps passing. export function sortModelOptions( options: T[], newestFirst: boolean, diff --git a/packages/tui/src/component/dialog-provider.tsx b/packages/tui/src/component/dialog-provider.tsx index e9f6ca0560..9620dbf132 100644 --- a/packages/tui/src/component/dialog-provider.tsx +++ b/packages/tui/src/component/dialog-provider.tsx @@ -1,4 +1,4 @@ -import { createMemo, createSignal, onMount, Show } from "solid-js" +import { createMemo, createSignal, onCleanup, onMount, Show } from "solid-js" import { useSync } from "../context/sync" import { map, pipe, sortBy } from "remeda" import { DialogSelect } from "../ui/dialog-select" @@ -15,15 +15,33 @@ import { isConsoleManagedProvider } from "../util/provider-origin" import { useConnected } from "./use-connected" import { useBindings, useOpencodeKeymap } from "../keymap" import { useClipboard } from "../context/clipboard" +import { useLocal } from "../context/local" +// altimate_change — mark first-run setup complete once the gateway sign-in succeeds +// (used by AutoMethod below); flips useReady() so the first-run chat lock lifts. +import { markSetupComplete } from "./altimate-onboarding" -const PROVIDER_PRIORITY: Record = { - opencode: 0, - "opencode-go": 1, +export const PROVIDER_PRIORITY: Record = { + // altimate_change start — Part 1 onboarding: Altimate LLM Gateway is the + // recommended default first; the BYOK providers rank next; OpenCode Zen loses + // its "Recommended" tag and drops below. (Big Pickle occupies priority 4, injected + // by dialog-model between Google and Zen.) + "altimate-backend": 0, + anthropic: 1, openai: 2, - "github-copilot": 3, - anthropic: 4, - google: 5, + google: 3, + // 4 reserved for Big Pickle (see dialog-model) + opencode: 5, + "opencode-go": 6, + "github-copilot": 7, } +// altimate_change end + +// altimate_change start — known-bad tool-callers, surfaced inline in the model picker +// (imported by dialog-model's READY/NEEDS-SETUP list). +export const WARNLIST: Record = { + "qwen-plus": "⚠ known tool-calling issues", +} +// altimate_change end const CUSTOM_PROVIDER_OPTION_VALUE = "__opencode_custom_provider__" const CUSTOM_PROVIDER_ID = /^[a-z0-9][a-z0-9-_]*$/ @@ -55,15 +73,19 @@ export function providerOptions(list: { id: string; name: string }[]): ProviderO ), map((provider) => ({ type: "provider" as const, - title: provider.name, + // altimate_change start — brand the gateway entry + relabel priorities + title: provider.id === "altimate-backend" ? "Altimate LLM Gateway" : provider.name, value: provider.id, providerID: provider.id, description: { - opencode: "(Recommended)", + "altimate-backend": "Recommended · best tool-calling · 10M free tokens", anthropic: "(API key)", openai: "(ChatGPT Plus/Pro or API key)", + google: "(API key)", + opencode: "Bring your own Zen key", "opencode-go": "Low cost subscription for everyone", }[provider.id], + // altimate_change end category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Providers", })), ), @@ -147,12 +169,6 @@ export function createDialogProviderOptions() { gutter: connected && onboarded() ? () => : undefined, async onSelect() { if (consoleManaged) return - // altimate_change start — restore Altimate credential validation/save/model-picker flow - if (providerID === "altimate-backend") { - keymap.dispatchCommand("altimate.provider.connect") - return - } - // altimate_change end const methods = sync.data.provider_auth[providerID] ?? [ { @@ -191,31 +207,59 @@ export function createDialogProviderOptions() { inputs = value } - const result = await sdk.client.provider.oauth.authorize({ - providerID, - method: index, - inputs, - }) - if (result.error) { - toast.show({ - variant: "error", - message: JSON.stringify(result.error), + // altimate_change start — guard the authorize (e.g. loopback port busy) so + // the recommended /connect path surfaces the error instead of failing + // silently (parity with DialogAltimateAuth). + try { + const result = await sdk.client.provider.oauth.authorize({ + providerID, + method: index, + inputs, }) + if (result.error) { + toast.show({ + variant: "error", + message: JSON.stringify(result.error), + }) + dialog.clear() + return + } + if (result.data?.method === "code") { + dialog.replace(() => ( + + )) + } else if (result.data?.method === "auto") { + dialog.replace(() => ( + + )) + } else { + dialog.clear() + } + } catch (err) { + toast.error(err instanceof Error ? err : new Error("Failed to start sign-in")) dialog.clear() - return - } - if (result.data?.method === "code") { - dialog.replace(() => ( - - )) - } - if (result.data?.method === "auto") { - dialog.replace(() => ( - - )) } + // altimate_change end } if (method.type === "api") { + // altimate_change start — restore Altimate credential validation/save/model-picker + // flow: the instance-name::api-key entry is opencode-side (needs AltimateApi), so + // it's re-homed as a plugin (see docs/internal/2026-06-23-tui-fork-features-as-plugins-adr.md). + if (providerID === "altimate-backend") { + keymap.dispatchCommand("altimate.provider.connect") + return + } + // altimate_change end let metadata: Record | undefined if (method.prompts?.length) { const value = await PromptsMethod({ dialog, prompts: method.prompts }) @@ -239,6 +283,52 @@ export function DialogProvider() { return } +// altimate_change start — /auth entry: go straight to the Altimate LLM Gateway +// sign-in (the OAuth loopback method, index 0), skipping the provider picker. +export function DialogAltimateAuth() { + const { theme } = useTheme() + const sdk = useSDK() + const dialog = useDialog() + const toast = useToast() + + onMount(async () => { + const providerID = "altimate-backend" + try { + const result = await sdk.client.provider.oauth.authorize({ providerID, method: 0 }) + if (result.error) { + toast.show({ variant: "error", message: JSON.stringify(result.error) }) + dialog.clear() + return + } + if (result.data?.method === "auto") { + dialog.replace(() => ( + + )) + } else if (result.data?.method === "code") { + dialog.replace(() => ( + + )) + } else { + dialog.clear() + } + } catch (err) { + // e.g. the loopback port is busy — don't hang on "Starting sign-in…". + toast.error(err instanceof Error ? err : new Error("Failed to start sign-in")) + dialog.clear() + } + }) + + return ( + + + Altimate LLM Gateway + + Starting sign-in… + + ) +} +// altimate_change end + interface AutoMethodProps { index: number providerID: string @@ -250,25 +340,40 @@ function AutoMethod(props: AutoMethodProps) { const sdk = useSDK() const dialog = useDialog() const sync = useSync() + // altimate_change start — `local` sets the connected model as the default post-connect + const local = useLocal() + // altimate_change end const toast = useToast() const clipboard = useClipboard() + // altimate_change start — success state: confirm inline (green) below the "waiting" + // line, then auto-close, instead of jumping into the model picker. + const [connected, setConnected] = createSignal(false) + // Guard against a late callback / auto-close firing after the dialog is dismissed. + let disposed = false + let closeTimer: ReturnType | undefined + onCleanup(() => { + disposed = true + if (closeTimer) clearTimeout(closeTimer) + }) useBindings(() => ({ - bindings: [ - { - key: "c", - desc: "Copy provider code", - group: "Dialog", - cmd: () => { - const code = - props.authorization.instructions.match(/[A-Z0-9]{4}-[A-Z0-9]{4,5}/)?.[0] ?? props.authorization.url - clipboard - .write?.(code) - .then(() => toast.show({ message: "Copied to clipboard", variant: "info" })) - .catch(toast.error) - }, - }, - ], + bindings: connected() + ? [] + : [ + { + key: "c", + desc: "Copy provider code", + group: "Dialog", + cmd: () => { + const code = + props.authorization.instructions.match(/[A-Z0-9]{4}-[A-Z0-9]{4,5}/)?.[0] ?? props.authorization.url + clipboard + .write?.(code) + .then(() => toast.show({ message: "Copied to clipboard", variant: "info" })) + .catch(toast.error) + }, + }, + ], })) onMount(async () => { @@ -276,7 +381,10 @@ function AutoMethod(props: AutoMethodProps) { providerID: props.providerID, method: props.index, }) + if (disposed) return if (result.error) { + // altimate_change — surface the failure instead of clearing silently. The + // precise reason is also logged server-side by the plugin callback. toast.show({ variant: "error", message: @@ -289,6 +397,36 @@ function AutoMethod(props: AutoMethodProps) { } await sdk.client.instance.dispose() await sync.bootstrap() + if (disposed) return + // altimate_change start — mark setup complete (flips useReady → unlocks first-run chat/tips) + markSetupComplete() + // The gateway sign-in already shows the auth URL + "Waiting for authorization…". + // On success, confirm inline (green) and auto-close after a moment rather than + // opening the model picker. Auto-select a model so the user can chat right away. + if (props.providerID === "altimate-backend") { + const provider = sync.data.provider.find((p) => p.id === props.providerID) + const model = provider + ? Object.entries(provider.models).find(([, info]) => info.status !== "deprecated")?.[0] + : undefined + if (!model) { + // Connected, but nothing usable to select — don't fake a green ✓; open the + // picker so the user can choose a model (or another provider). + toast.show({ + message: "Connected, but no model is available yet — pick one to start.", + variant: "warning", + }) + dialog.replace(() => ) + return + } + local.model.set({ providerID: props.providerID, modelID: model }, { recent: true }) + setConnected(true) + closeTimer = setTimeout(() => { + if (!disposed) dialog.clear() + }, 5000) + return + } + // altimate_change end + toast.show({ message: `Connected to ${props.title}`, variant: "success" }) dialog.replace(() => ) }) @@ -298,21 +436,39 @@ function AutoMethod(props: AutoMethodProps) { {props.title} - dialog.clear()}> - esc - + + dialog.clear()}> + esc + + {props.authorization.instructions} - Waiting for authorization... - - c copy - + {/* altimate_change — swap the "waiting" line for a green success confirmation */} + + Waiting for authorization... + + c copy + + + } + > + {/* theme.success is plain ANSI green (col 2) — dim/gray in many palettes; + diffHighlightAdded is the bright green (greenBright) so it reads clearly. */} + + ✓ Authentication successful + + You are all set — returning to Altimate Code… + ) } +// altimate_change end interface CodeMethodProps { index: number diff --git a/packages/tui/src/component/dialog-scan-gate.tsx b/packages/tui/src/component/dialog-scan-gate.tsx new file mode 100644 index 0000000000..ab2d79f069 --- /dev/null +++ b/packages/tui/src/component/dialog-scan-gate.tsx @@ -0,0 +1,133 @@ +import { createSignal, For, onMount } from "solid-js" +import { TextAttributes, RGBA } from "@opentui/core" +import { useKeyboard } from "@opentui/solid" +import { useTheme, selectedForeground } from "../context/theme" +import { useDialog } from "../ui/dialog" + +// altimate_change — Part 2 scan gate. Shown once, immediately after Part 1 +// completes (a model is ready and chat is live). We do NOT auto-scan; the user +// chooses. Yes/No each submit the hidden `/onboard-connect` command (scan|skip), +// which starts a session and lets the agent run the branch flow. Yes carries a +// "(Recommended)" tag (theme.success, matching the model picker's house style). +// +// `onChoose` is injected by App (which lives inside PromptRefProvider); the dialog +// overlay is mounted above that provider, so the gate cannot resolve the prompt +// ref itself — it must be handed in. +export function DialogScanGate(props: { onChoose: (arg: "scan" | "skip") => void }) { + const { theme } = useTheme() + const dialog = useDialog() + const [selected, setSelected] = createSignal(0) // 0 = Yes (default, per spec ❯) + + onMount(() => dialog.setSize("large")) + + // altimate_change — keyboard (return / y / n) and mouse handlers all call run() directly, and + // nothing stops two firing before the dialog unmounts. Without this guard a fast double-press + // submits `/onboard-connect` twice and double-counts the funnel choice. + let chosen = false + + function run(arg: "scan" | "skip") { + if (chosen) return + chosen = true + dialog.clear() + props.onChoose(arg) + } + + const options = [ + { + label: "Yes", + recommended: true, + run: () => run("scan"), + help: "Reads local config and env vars. Nothing leaves your computer; no credentials needed.", + }, + { + label: "No", + recommended: false, + run: () => run("skip"), + help: "Skip for now. Run /discover anytime.", + }, + ] + + useKeyboard((evt) => { + if (evt.name === "up" || evt.name === "down") { + setSelected((prev) => (prev + 1) % 2) + evt.preventDefault() + return + } + if (evt.name === "return") { + evt.preventDefault() + evt.stopPropagation() + options[selected()].run() + return + } + if (evt.name === "y" && !evt.ctrl && !evt.meta) { + evt.preventDefault() + run("scan") + return + } + if (evt.name === "n" && !evt.ctrl && !evt.meta) { + evt.preventDefault() + run("skip") + } + }) + + const selFg = selectedForeground(theme) + const transparent = RGBA.fromInts(0, 0, 0, 0) + + return ( + + + + + Scan your environment? + + dialog.clear()}> + esc + + + I'll look for your dbt project and warehouses. + + + {(option, index) => { + const active = () => selected() === index() + return ( + setSelected(index())} onMouseUp={() => option.run()}> + + {active() ? "❯" : " "} + + + + {option.label} + + + + + {option.recommended ? (Recommended) : null} + {option.help} + + + + ) + }} + + + + + ) +} diff --git a/packages/tui/src/component/prompt/autocomplete.tsx b/packages/tui/src/component/prompt/autocomplete.tsx index 137899db5c..a54917c4f6 100644 --- a/packages/tui/src/component/prompt/autocomplete.tsx +++ b/packages/tui/src/component/prompt/autocomplete.tsx @@ -21,6 +21,9 @@ import type { PromptInfo } from "../../prompt/history" import { useFrecency } from "../../prompt/frecency" import { useBindings, useCommandSlashes, useOpencodeModeStack } from "../../keymap" import { displayCharAt, mentionTriggerIndex } from "../../prompt/display" +// altimate_change — first run: slash menu shows only local actions (e.g. /connect) +// until a model is ready +import { useReady } from "../altimate-onboarding" function removeLineRange(input: string) { const hashIndex = input.lastIndexOf("#") @@ -89,6 +92,9 @@ export function Autocomplete(props: { const project = useProject() const slashes = useCommandSlashes() const modeStack = useOpencodeModeStack() + // altimate_change start — readiness gate for first-run slash filtering + const ready = useReady() + // altimate_change end const { theme } = useTheme() const dimensions = useTerminalDimensions() const frecency = useFrecency() @@ -448,24 +454,32 @@ export function Autocomplete(props: { ) // altimate_change end - for (const serverCommand of sync.data.command) { - if (serverCommand.source === "skill") continue - // altimate_change start — keep one autocomplete row per slash command name - if (localSlashNames.has(serverCommand.name)) continue - // altimate_change end - const label = serverCommand.source === "mcp" ? ":mcp" : "" - results.push({ - display: "/" + serverCommand.name + label, - description: serverCommand.description, - onSelect: () => { - const newText = "/" + serverCommand.name + " " - const cursor = props.input().logicalCursor - props.input().deleteRange(0, 0, cursor.row, cursor.col) - props.input().insertText(newText) - props.input().cursorOffset = Bun.stringWidth(newText) - }, - }) + // altimate_change start — first run: hide server-defined commands (/discover etc.) + // until a model is ready; local slash actions (e.g. /connect) above stay visible. + if (ready()) { + for (const serverCommand of sync.data.command) { + if (serverCommand.source === "skill") continue + // altimate_change start — keep one autocomplete row per slash command name + if (localSlashNames.has(serverCommand.name)) continue + // altimate_change end + // altimate_change — onboard-connect is invoked programmatically by the Part 2 + // scan gate, not typed; keep it out of the slash menu. + if (serverCommand.name === "onboard-connect") continue + const label = serverCommand.source === "mcp" ? ":mcp" : "" + results.push({ + display: "/" + serverCommand.name + label, + description: serverCommand.description, + onSelect: () => { + const newText = "/" + serverCommand.name + " " + const cursor = props.input().logicalCursor + props.input().deleteRange(0, 0, cursor.row, cursor.col) + props.input().insertText(newText) + props.input().cursorOffset = Bun.stringWidth(newText) + }, + }) + } } + // altimate_change end results.sort((a, b) => a.display.localeCompare(b.display)) diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 176c27ec2e..87d112dbf5 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -16,7 +16,6 @@ import { fileURLToPath } from "url" import { useLocal } from "../../context/local" import { Flag } from "@opencode-ai/core/flag/flag" import { tint, useTheme } from "../../context/theme" -import { EmptyBorder, SplitBorder } from "../../ui/border" import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime" import { useClipboard } from "../../context/clipboard" import { Spinner } from "../spinner" @@ -43,7 +42,10 @@ import { errorMessage } from "../../util/error" import { formatDuration } from "../../util/format" import { createColors, createFrames } from "../../ui/spinner" import { useDialog } from "../../ui/dialog" -import { DialogProvider as DialogProviderConnect } from "../dialog-provider" +import { DialogProvider as DialogProviderConnect, WARNLIST } from "../dialog-provider" +// altimate_change — first-run submit gate: open the curated welcome picker instead +// of erroring when no model is ready yet (see altimate-onboarding.tsx). +import { DialogModelWelcome, useReady } from "../altimate-onboarding" import { DialogAlert } from "../../ui/dialog-alert" import { useToast } from "../../ui/toast" import { useKV } from "../../context/kv" @@ -246,6 +248,12 @@ export function Prompt(props: PromptProps) { const [cursorVersion, setCursorVersion] = createSignal(0) const currentProviderLabel = createMemo(() => local.model.parsed().provider) const hasRightContent = createMemo(() => Boolean(props.right)) + // altimate_change start — readiness gate: guide (don't block) when no provider is set + // up; and flag known-bad tool-callers with a persistent "⚠ unreliable model" chip in + // the prompt meta row (same WARNLIST the model picker warns with). + const ready = useReady() + const unreliableModel = createMemo(() => Boolean(WARNLIST[local.model.parsed().model])) + // altimate_change end function promptModelWarning() { toast.show({ @@ -1036,6 +1044,22 @@ export function Prompt(props: PromptProps) { void exit() return true } + // altimate_change start — first-run gate without dead chat / error copy: a normal + // message with no provider ready opens the welcome picker (the message is + // discarded) with a friendly line, rather than erroring. + if (!ready()) { + dialog.replace(() => ( + + )) + input.clear() + input.extmarks.clear() + setStore("prompt", { input: "", parts: [] }) + return false + } + // altimate_change end const selectedModel = local.model.current() if (!selectedModel) { void promptModelWarning() @@ -1440,26 +1464,26 @@ export function Prompt(props: PromptProps) { return ( <> (anchor = r)} visible={props.visible !== false} width="100%"> - + {/* altimate_change start — Claude-Code-style input bar: thin rule above and + below, "›" prompt char, no filled background; agent/model hints move to a + line under the bottom rule */} + + + › +