Skip to content

feat: release v0.13.0 — expand Reconcile capabilities, add SQL/Oracle/Redshift profilers, and upgrade runtime dependencies#2

Open
a0x8o wants to merge 68 commits into
alexxx-db:mainfrom
databrickslabs:main
Open

feat: release v0.13.0 — expand Reconcile capabilities, add SQL/Oracle/Redshift profilers, and upgrade runtime dependencies#2
a0x8o wants to merge 68 commits into
alexxx-db:mainfrom
databrickslabs:main

Conversation

@a0x8o

@a0x8o a0x8o commented May 27, 2026

Copy link
Copy Markdown

Changes
What does this PR do?
This PR aggregates critical feature additions, ecosystem expansions, and maintenance updates culminating in the v0.13.0 milestone. The primary focus of this work is expanding the capabilities of the reconciliation (recon) engine and profilers to support diverse external data sources via Lakehouse Federation, while modernizing the project's dependency baseline and CI/CD infrastructure.

Key Highlights:

Reconciliation Engine (recon) Enhancements: Migrated integration tests from local environments directly to Databricks. Added foreign connections as a reconciliation source and resolved critical schema fetch failures on Foreign Catalogs.

New Profiler Implementations: Added native support for SQL Server Profiler, Legacy SQL DW Profiler, Oracle Profiler, and initiated Redshift connection/credential support.

Ecosystem & Runtime Upgrades: Updated the project baseline to support Python 3.14 and updated critical linting/formatting tools (e.g., black bump to 26.3.1).

CLI Additions: Added the --switch-config-path parameter to the llm-transpile command and implemented DatabaseConnector#health_check().

Enterprise Features: Implemented Maven and PyPI mirror support during deployment/installation phases and fully revamped user-facing documentation.

Relevant implementation details
Testing Infrastructure: Created a dedicated, isolated test cluster for recon end-to-end integration tests (databrickslabs#2453) to replace brittle local test setups.

Dependency Pruning: Removed cryptography as an explicit dependency, relying on downstream abstractions to minimize surface vulnerability (databrickslabs#2458).

Storage Consolidation: Collapsed DuckDB write helpers and standardized shared profiler infrastructure internally (databrickslabs#2093).

CI/CD: Explicitly marked non-deployment environments in GitHub Actions to optimize pipeline runners (databrickslabs#2443).

Caveats/things to watch out for when reviewing:
Large File Diff: This PR contains changes across 220 files due to extensive lockfile linting, dependency updates, and documentation revamps. Focus reviews on the core connector logic in recon and the new profiler modules.

Python 3.14 Support: Ensure that runtime syntax additions or dependency changes do not break backward compatibility with older supported Python versions.

Database Drivers: New profiles (Oracle, SQL Server, Redshift) introduce specific connector configurations—verify that network topologies and driver handshakes behave safely during connection failures (validated by the updated non-zero exit behavior in the test profiler connection).

Linked issues
Resolves databrickslabs#2151, Resolves databrickslabs#2187, Resolves databrickslabs#2333, Resolves databrickslabs#2339, Resolves databrickslabs#2362, Resolves databrickslabs#2365, Resolves databrickslabs#2421, Resolves databrickslabs#2434, Resolves databrickslabs#2437, Resolves databrickslabs#2441, Resolves databrickslabs#2442, Resolves databrickslabs#2443, Resolves databrickslabs#2444, Resolves databrickslabs#2451, Resolves databrickslabs#2453, Resolves databrickslabs#2457, Resolves databrickslabs#2458

Functionality
[x] added relevant user documentation

[x] added new CLI command (e.g., DatabaseConnector#health_check())

[x] modified existing command: databricks labs lakebridge llm-transpile --switch-config-path

[x] Added SQL Server, Legacy SQL DW, Oracle, and Redshift Profilers.

Tests
[x] manually tested

[x] added unit tests

[x] added integration tests (Migrated to dedicated Databricks E2E cluster)

m-abulazm and others added 30 commits May 4, 2026 14:49
…l spark (#2394)

### What does this PR do?
Migrates the reconcile integration tests off local Spark and onto the
Databricks `spark` fixture (UC), and removes the no-longer-needed
`local_test_run` plumbing that existed only to support that local-Spark
path.
### Relevant implementation details
- Replaces the local `mock_spark` fixture (Spark Connect to
`sc://localhost`) with the `spark` fixture from
`databricks-labs-pytester`, so all tests run against Databricks Spark
with UC.
`DELTA_FAILED_TO_MERGE_FIELDS` from the hand-rolled
`report_tables_schema`).
  - Drops the `local_test_run` flag
- Metadata tables are always addressed as `catalog.schema.<table>`.
Tests updated accordingly (every assertion now reads
`{catalog}.{schema}.MAIN` and derived from a real `make_schema` +
`make_volume`
- Skips tests that depend on currently-unavailable infra (Snowflake)
   
### Caveats/things to watch out for when reviewing:
- Integration tests now require a Databricks workspace + UC. They will
not run against a bare local Spark anymore.
- snowflake infra is not working and respective integration tests are
ignored
                                                                    
  ### Functionality
  - [ ] added relevant user documentation
  - [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
  ### Tests                                                         
  - [x] manually tested
- [ ] added unit tests
  - [x] added integration tests

---------

Co-authored-by: Andrew Snare <andrew.snare@databricks.com>
Co-authored-by: Andrew Snare <asnare@users.noreply.github.com>
#2405)

## Changes

This PR implements minimal support for using a Maven mirror to install
morpheus, instead of requiring direct access to Maven Central. This has
been implemented by allowing an environment variable,
`LAKEBRIDGE_MAVEN_URL` to override the repository URL so that a mirror
is used instead of Maven Central.

If credentials are required to access the mirror, they can be specified
in `~/.netrc`. (Alternately, `NETRC` can be set in the environment to
specify the location of this file.)

### Relevant implementation details

Support for `~/.netrc` (and `NETRC`) is provided automatically by the
`requests` library that we use to issue HTTP calls against the maven
repository.

### Notes for reviewers

This PR also syncs the `jfrog-auth` action to the latest version: this
sets up the netrc file needed for this to work during CI.

Lots of integration tests are still failing on `main`. Relevant
integration tests for this PR include:

 - `test_gets_maven_artifact_version`
 - `test_downloads_from_maven`
 - `test_installs_and_runs_maven_morpheus`
 - `test_transpiles_all_dbt_project_files`
 - `test_transpile_sql_file`

_Documentation updates will follow._

### Linked issues

Resolves #2359

### Functionality

- modified existing command: `databricks labs lakebridge
install-transpile`

### Tests

- manually tested
- added unit tests
- existing integration tests
## Summary
The `test-profiler-connection` CLI command always exited 0 on failure
because
`logger.fatal()` only logs — it doesn't terminate the process. The
blueprint
framework's `except Exception` catch-all swallowed everything else. This
replaces
  all three broken error handlers with `raise SystemExit(...)`


### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [x] manually tested
- [ ] added unit tests
- [x] added integration tests
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?

**Replaced direct JDBC connections with Databricks Unity Catalog
remote_query().**
                                                                       
Previously, reconciliation connected to external databases (Oracle,
Snowflake, SQL Server) by managing JDBC credentials through Databricks
secret scopes — each connector built JDBC URLs, handled
  authentication (passwords, PEM keys), and executed queries directly.
Now, connections are managed through **UC Connections** — a centralized
Databricks-native way to connect to foreign data sources. The connectors
call `remote_query()` via Spark SQL, and Databricks handles
  authentication and connectivity.
  ### Why                                                              

- **No more secret management** — credentials are managed in Unity
Catalog, not in application code or secret scopes
- **Simpler configuration** — users provide a UC connection name instead
of configuring secret scopes with individual credential keys
- **Centralized governance** — UC Connections are auditable,
permission-controlled, and managed through standard Databricks tooling
   
### Config change (v1 → v2)
                                                                       
  ```yaml
  # Before (v1)
  data_source: oracle
  secret_scope: remorph_oracle
database_config:
source_schema: HR
    target_catalog: main                                               
    target_schema: reconcile_target

  # After (v2)
  source:
    dialect: oracle
    catalog: ORCL
schema: HR
    uc_connection_name: my_oracle_connection
target:
    catalog: main                                                      
    schema: reconcile_target

  Existing v1 configs are auto-migrated on load.
  ```

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [ ] manually tested
- [ ] added unit tests
- [ ] added integration tests

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes

This PR updates the configuration used to build the source and wheel
distributions from this repository. With these changes:

 - The source distribution is correct;
- The wheel can be built from either the source distribution (or
directly from the project repository).

As a bonus a lot of unintended resources (documentation) are no longer
included in the wheel that we build.

### Relevant implementation details

The build configuration was previously incorrect, and worked
coincidentally when building a wheel directly from this repository but
not when building from the source distribution. A quick summary of the
gory details:

- A source distribution is intended to facilitate, in a reproducible
manner: a) testing the package; b) building a binary wheel distribution.
- Wheels can be built _either_ from a source distribution _or_ from the
project repository.
- Previously, using hatch, the wheel was produced directly from the
project repository. A source distribution was produced, but its layout
was different: a wheel built from this was bogus and didn't contain the
intended package sources.
 - Now, using uv, the wheel is produced in a 2-step process:
   1. Build the source distribution;
   2. Build the wheel from the source distribution.

   This exposed the problem with the source distribution.

With these changes building the wheel directly from the project repo or
the source distribution yield the same results. This also provided an
opportunity to ensure that both the source distribution and wheel only
include the intended files from the repository.

### Tests

- manually tested
## Changes

### What does this PR do?

This PR adds a new profiler for `mssql`. 

### Relevant implementation details

This profiler closely follows the implementation of the Azure Synapse
profiler. However, this implementation provides a `last_execution_time`
param for all `mssql` server queries, allowing for a future scheduler
component to hook in an repeat queries.

### Caveats/things to watch out for when reviewing:

This profiler will not support on-prem `mssql` servers.

### Linked issues

N/A

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [X] added new profiler component
- [ ] ... +add your own

### Tests

- [X] manually tested
- [ ] added unit tests
- [ ] added integration tests

---------

Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
…eration) (#2422)

## Summary
- Fixes #2366
- Foreign Catalogs (created via Lakehouse Federation) lack the
Databricks-specific `full_data_type` column in
`information_schema.columns`, causing `UNRESOLVED_COLUMN` errors for all
reconcile report types (`schema`, `data`, `row`, `all`)
- Add `DatabricksNonUnityCatalogDataSource(DatabricksDataSource)` that
overrides only `get_schema` to use `DESCRIBE TABLE`, covering
hive_metastore, global_temp views, and Foreign Catalogs
- `DatabricksDataSource` is scoped to native Unity Catalog only
(`information_schema.columns` with `full_data_type`)
- `source_adapter.create_adapter` dispatches via `is_target`: target →
`DatabricksDataSource`, source → `DatabricksNonUnityCatalogDataSource`
- `catalog` parameter on `get_schema` / `read_data` tightened from `str
| None` to `str` (now required by `SourceConnectionConfig`)

## Test plan
- [x] Existing unit tests pass (1159)
- [x] New / updated unit tests:
`test_get_schema_uses_information_schema`,
`test_get_schema_non_uc_uses_describe_table`,
`test_get_schema_non_uc_foreign_catalog`,
`test_get_schema_information_schema_exception_handling`,
`test_get_schema_describe_exception_handling`
- [x] Source adapter tests:
`test_create_adapter_for_databricks_dialect_source`,
`test_create_adapter_for_databricks_dialect_target`
- [x] Integration test: Reconcile with Foreign Catalog (Lakebase
PostgreSQL via Lakehouse Federation) as direct source

---

Reopened from #2367 on an upstream branch to bypass the fork-PR OIDC
restriction on JFrog auth (CI cannot run on fork PRs). All review
comments and history are preserved on the original PR.
Add Redshift connector to Recon

---------

Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
# Summary

This PR revamps the Lakebridge documentation with a focus on clarity,
structure, and first-time user experience.

- **New pages:** Added a Getting Started tutorial (end-to-end SQL Server
→ Databricks SQL walkthrough), a tool selection decision guide (Choosing
Tools), a
dedicated Morpheus transpiler page, and a Switch architecture page split
out from the Switch index
- **Restructured Reconcile docs**: Consolidated from 5 files (~1,400
lines) to 3 (~750 lines). The index was rewritten as a concise overview
with a
report-type comparison table. The former reconcile_configuration.mdx and
example_config.mdx were merged into a single Configuration Reference
page;
recon_notebook.mdx and reconcile_automation.mdx were merged into a
single Running Reconcile page covering both CLI and notebook execution
paths
- **Restructured SSIS docs**: Moved the three SSIS pages (ssis.mdx,
ssis_supported_components.mdx, ssis_examples.mdx) into a dedicated ssis/
subfolder,
creating a collapsible SSIS category in the sidebar consistent with how
other multi-page sections are structured; updated all cross-links
between the
  pages
- **Other restructured pages**: Rewrote Installation for brevity;
expanded FAQ from 3 to 25+ questions; merged the Transpile overview into
the Transpile
index; split Switch docs into a quickstart index and a separate
architecture page
- **Deduplication**: Removed install-transpile and configure-reconcile
commands from Getting Started (they now live exclusively in
Installation); Getting
Started Step 3 is reframed as configuration guidance with a back-link to
Installation, and Step 5 runs reconcile directly
- **Navigation overhaul**: Reordered the sidebar to match the actual
user journey: Installation → Getting Started → Choosing Tools →
Assessment →
Transpile → Reconcile → SQL Splitter → FAQ — with Installation moved
before Getting Started since it is a prerequisite. Resolved a position
conflict
between Installation and Choosing Tools which were both set to
sidebar_position: 2

---------

Co-authored-by: Andrew Snare <asnare@users.noreply.github.com>
Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
* test oracle reconcile
* improve parsing of remote query options as it was not working for
oracle
* remove unused `WorkspaceClient`
* remove old tests of oracle and docker script that were not automated
and needed lots of manual efforts to run

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #2416 

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [x] manually tested
- [ ] added unit tests
- [x] added integration tests
## Changes

This primary purpose of this PR is:

- Fixing some changes to `uv.lock` that were inadvertently introduced in
#2151.
 - Updating our CI to detect issues in PRs relating to the lock-files.

The intention within this repo is:

 - The lock-files should only update under 2 circumstances:

    - A refresh-only update of the lock-files.
    - A dependency in `pyproject.toml` has been changed.

- Outside of this, `uv.lock` and `.build-constraints.txt` should not be
changing.
- When they change, it should be via `make lock-dependencies` which
ensures `uv.lock` refers to PyPI properly. (Manual review is unlikely to
spot the error if a dev proxy is in there, so this PR automates the
check.)

Further to these linting-releated changes:

- Running `make lock-dependencies` now locks to the latest versions of
dependencies instead of keeping the existing versions if they're still
within their allowed version range.
- The use of `databricks-bb-analyzer==0.3.1` is blocked: the analyzer
binary in this release doesn't work properly. (Issue #2373.)

Finally, to verify the linting this PR updates some dependencies:

 - Versions are locked against the highest version.
 - Outstanding dependabot updates are included.

### Relevant implementation details

The linting script has been written so that it can be run locally by
developers, it doesn't require the GHA environment to be in place.

### Linked Issues

Resolves: #2373

Subsumes:
 - #2409
 - #2410 
 - #2411
 - #2412
 - #2413
 - #2415

### Tests

- manually tested
- new CI check
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes

Traditionally GitHub Actions associate the use of an `environment` as
deploying in some way. These days we can mark environment usage as
unrelated to deployments, which reduces a bunch of clutter in PRs and
various places in the UI.
## Changes

This PR adjusts the some of the lockfile handling:

- We now use `https://pypi.org/simple` (no trailing-slash) instead of
`https://pypi.org/simple/` as the PyPI registry URL: this avoids
spurious updates from dependabot.
- Detection of lockfile drift is more robust: sometimes uv uses
hatchling to examine the project, and hatchling needs more than the
skeleton `pyproject.toml` that was being checked.

Some trivial dependency updates are included in this PR.

### Linked issues

Follows: #2434

Subsumes:
 - #2436
 - #2439
 - #2440

### Functionality

- updated uv linting

### Tests

- manually tested
- `lint-uv` job (workflow: `push.yml`
Bump version to 0.13.0
)

## Summary
- Add optional `--switch-config-path` parameter to `llm-transpile` CLI
command
- Parameter allows users to specify a custom Switch configuration file
in the workspace
- Path must start with `/Workspace/` (validation included)
- When not specified, Switch uses its default configuration

Closes #2255

## Test plan
- [x] Unit tests added and passing
- [x] E2E test: verify `--switch-config-path` works with actual
workspace execution
- [x] Verify Switch side correctly reads `switch_config_path` job
parameter

---------

Co-authored-by: Guenia Izquierdo <guenia.izquierdo@databricks.com>
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
creates a test cluster to run recon e2e tests
### Relevant implementation details
use pytester and pin spark version

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [X] manually tested
- [ ] added unit tests
- [ ] added integration tests

---------

Co-authored-by: Andrew Snare <andrew.snare@databricks.com>
## Changes

### What does this PR do?

This PR updates this project so that the CLI supports Python 3.14. Aside
the from the project metadata, this involved:

- Updating to current versions of `mypy`, `pylint` and `pytest` that
work properly with python 3.14. Some code-changes were required to
satisfy the new versions of these tools.
- Expands both unit and integration testing to cover all supported
python versions.

### Relevant implementation details

Downstream dependencies already been released with support for Python
3.14:

 - Blueprint
 - Bladebridge

The `pytester` and `lsql` dependencies already work with python 3.14,
although the project metadata may not reflect this.

A further improvement here is integration testing across the python
version matrix.

### Caveats/things to watch out for when reviewing:

We have our first system imports that depend on the version of python:

- `importlib.abc.Traversable` was moved to
`importlib.abs.resources.Traversable` in Python 3.11, with the old
location deprecated and removed in 3.14.
- An import-guard based on the python version is the only way to both
ensure the import works _and_ that mypy is aware of which import is
currently active.

Due to the expanded unit and integration tests, the set of mandatory
checks on PRs for this repository will need to be adjusted.

### Linked issues

Resolves #2137.

### Functionality

- updated relevant user documentation
- modifies existing commands:

   - `databricks labs install lakebridge`
   - `databricks labs lakebridge install-transpile`
   - etc. 

### Tests

- manually tested
- existing unit tests, extended to cover python 3.14.
- existing integration tests, extended to cover all supported python
versions.
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
add health check on database clients to allow e.g. oracle to implement
health check which works differently than current implementation

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Unblocks #2187
…frastructure to `assessments/common/` (#2431)

# Collapse DuckDB insert helpers and move shared profiler helpers to
`assessments/common/`

Closes #2093.

## Summary

`synapse/common/duckdb_helpers.py` carried two parallel write helpers —
`save_resultset_to_db` (for `FetchResult` from SQL pools, supported
`overwrite`/`append` with an inlined per-table schemas dict) and
`insert_df_to_duckdb` (for `pd.DataFrame` from Azure SDK / monitoring,
only `overwrite`, no schema). They behaved differently on errors (one
swallowed, one re-raised), shared no code, and had drifted enough that
adding the SQL Server profiler in #2151 was forced to reach across into
`synapse/common/` and inline 13 new MSSQL schemas into the same
Synapse-named function.

This pull request replaces both helpers with a single
`save_to_duckdb(df, table_name, db_path, mode="overwrite", schema=None)`
and lifts the shared profiler helpers out of `synapse/common/` to a new
`assessments/common/` package so MSSQL (and future profilers) no longer
have to import from a Synapse-named module.

### Folder layout after this PR

```
assessments/
├── common/                          NEW (sibling of synapse/, mssql/)
│   ├── cli.py                       arguments_loader
│   └── duckdb_helpers.py            save_to_duckdb, get_max_column_value_duckdb, _table_exists
├── synapse/
│   └── common/
│       ├── functions.py             only Azure SDK clients now (arguments_loader removed)
│       └── schemas.py               NEW: SYNAPSE_SCHEMAS
└── mssql/
    └── common/
        └── schemas.py               NEW: MSSQL_SCHEMAS
```

`synapse/common/duckdb_helpers.py` is gone. Zero cross-backend imports
remain between `synapse/` and `mssql/`.

### `save_to_duckdb` behavior matrix

| mode | schema | table state | action |
|---|---|---|---|
| `overwrite` | provided | any | DROP, recreate with schema, INSERT
(skip insert if df empty) |
| `overwrite` | none | exists | TRUNCATE, INSERT (preserves DDL types
from prior run) |
| `overwrite` | none | missing | `CREATE TABLE AS SELECT *` (with `LIMIT
0` if df is empty) |
| `append` | provided | exists | INSERT |
| `append` | provided | missing | CREATE with schema, INSERT |
| `append` | none | exists | INSERT (DataFrame schema must match) |
| `append` | none | missing | `CREATE TABLE AS SELECT *` from first
batch (logs a warning — brittle for incremental appends) |
| `append` | any | empty df | no-op |

The "schema = explicit string" path is what makes incremental appends
robust: pandas dtype inference can drift between batches when a column
is null-only in one batch but typed in the next, breaking the INSERT.
With an explicit schema DuckDB owns the type and casts on insert.

## Commits (7, each independently buildable and bisectable)

1. **`Introduce shared profiler helpers in assessments/common/`** — adds
the new shared modules, the per-backend `synapse/common/schemas.py`,
slims `synapse/common/duckdb_helpers.py` to back-compat shims so
unmigrated callers keep working, adds 10 unit tests.
2. **`Migrate workspace_extract.py to assessments/common/`** — 14 sites.
3. **`Migrate monitoring_metrics_extract.py to assessments/common/`** —
3 sites.
4. **`Migrate dedicated_sqlpool_extract.py to assessments/common/`** — 7
sites; uses `SYNAPSE_SCHEMAS`.
5. **`Migrate serverless_sqlpool_extract.py to assessments/common/`** —
10 sites; uses `SYNAPSE_SCHEMAS`.
6. **`Migrate mssql extracts to assessments/common/ and pin
MSSQL_SCHEMAS`** — adds `mssql/common/schemas.py` (13 entries lifted
from #2151), migrates `mssql/info_extract.py` (9 sites) and
`mssql/activity_extract.py` (4 sites).
7. **`Remove deprecated profiler helpers from synapse/common/`** —
deletes the file and removes the `arguments_loader` re-export.

Total: 47 call sites across 6 files migrated.

## Behavior changes (worth flagging in review)

- **Errors propagate consistently.** The old `save_resultset_to_db`
swallowed exceptions, which meant a profiler run could silently drop
data while reporting success. `pipeline.py`'s `_run_python_script`
already converts re-raised script errors into the `{"status": "error"}`
JSON it expects, so this is the correct behavior.
- **`overwrite` without schema now TRUNCATEs an existing table** instead
of dropping and recreating with whatever pandas inferred. This preserves
the DDL-declared types from a prior run — relevant for the
workspace/monitoring extracts where the table was first created via
CTAS.
- **Empty DataFrame with no columns** is now skipped with a warning
instead of attempting an invalid `CREATE TABLE`.

## Argument-order swap

`insert_df_to_duckdb(df, db_path, table_name)` → `save_to_duckdb(df,
table_name, db_path)`. Mechanical, but worth a sanity scan in the
workspace and monitoring migrations.

## Why?

#2151 had to reach into `synapse/common/` from MSSQL because there was
no shared profiler module to import from. Splitting "collapse the
helpers" and "give them a proper home" into two PRs would mean PR #2
depends on PR #1 in non-trivial ways, and the merge conflict that kicked
off this rebase would just resurface for the next contributor. Done
together, the result is one coherent story and removes the cross-cutting
smell entirely.

The `arguments_loader` move is in the same spirit and was free to bundle
here because every migrated file was already updating its imports.

## Testing

- 10 new unit tests in `tests/unit/assessment/test_duckdb_helpers.py`
cover overwrite/append, explicit-schema dtype pinning, empty DataFrames,
and a regression guard for the incremental-append dtype drift case.
- Full `tests/unit/assessment/` suite passes (85 tests).
- `ruff check` and `pylint -E,F` clean across the touched packages.
- **Integration tests haven't been ran as of the current state of the
pull request for either Synapse or MSSQL. Reviewers please advice.**


## Follow-ups (out of scope)

- **Cross-run history accumulation for `workspace_*` and `metrics_*`
tables.** They're written in `overwrite` mode today, so each profiler
run wipes the prior run's data. With `save_to_duckdb` accepting an
optional schema, switching them to `append` is now a one-line-per-call
change once a watermark column is agreed on. Will open a separate issue.

---------

Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
…database_manager (#2305)

## Changes

This PR adds Redshift as a supported profiler assessment platform. 

### What does this PR do?

- Adds Redshift as a supported platform for the profiler assessment
(alongside Synapse).
- Implements Redshift credential flows: database password, federated
user, secrets manager (ARN), temporary credentials db user, temporary
credentials IAM; allows SSL for all; configurator prompts and connection
handling in database_manager.py.
- Files changed
  - .gitignore
  - pyproject.toml
  - src/databricks/labs/lakebridge/connections/database_manager.py

### Relevant implementation details

- Credentials: database password, federated user, secrets manager (ARN),
temporary credentials db user, temporary credentials IAM. Federated and
Secrets Manager use an optional lazy boto3 import.

### Caveats/things to watch out for when reviewing:

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #..

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests

1. Manually Tested all credential flows on all clusters in AWS Sandbox
account aws-sandbox-field-eng (332745928618)
tests/resources/assessments/pipeline_config_main_redshift.yml: pipeline
config that runs that script.
- [x] manually tested
- [ ] added unit tests
- [ ] added integration tests
## Changes

This PR updates the installation documentation, with a primary goal of
how to use mirrors and/or proxies when direct access to GitHub, PyPI or
Maven Central is unavailable.

### Linked issues

Documents the changes made for #2359.

### Functionality

- added user documentation

### Tests

Local preview, content renders as:
<img width="2514" height="4870" alt="image"
src="https://github.com/user-attachments/assets/98b7fc8b-8342-4244-bd28-869b7ff906ce"
/>
## Changes

This PR updates our documentation to build with node 26, including
re-locking the yarn dependencies. This is necessary because although the
documentation still builds with node 25, that version is no longer the
active version and it's currently unavailable via Homebrew: this means
the local dev-loop is broken.

### Tests

- manually tested
- CI automation
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes

This PR removes the explicit dependency on the `cryptography` package:
previously we used this directly in our code for setting up connections
to Snowflake, but it hasn't been needed since #2362.

### Caveats/things to watch out for when reviewing:

The package is still present as a transient dependency, and therefore
present in the lockfile, but we no longer need to declare an explicit
dependency or track the supported versions ourselves.

### Linked Issues

This removes the need for #2447.

### Tests

- existing unit tests
- existing integration tests
Bumps [black](https://github.com/psf/black) from 25.9.0 to 26.3.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/psf/black/releases">black's
releases</a>.</em></p>
<blockquote>
<h2>26.3.1</h2>
<h3>Stable style</h3>
<ul>
<li>Prevent Jupyter notebook magic masking collisions from corrupting
cells by using
exact-length placeholders for short magics and aborting if a placeholder
can no longer
be unmasked safely (<a
href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li>
</ul>
<h3>Configuration</h3>
<ul>
<li>Always hash cache filename components derived from
<code>--python-cell-magics</code> so custom
magic names cannot affect cache paths (<a
href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li>
</ul>
<h3><em>Blackd</em></h3>
<ul>
<li>Disable browser-originated requests by default, add configurable
origin allowlisting
and request body limits, and bound executor submissions to improve
backpressure
(<a
href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li>
</ul>
<h2>26.3.0</h2>
<h3>Stable style</h3>
<ul>
<li>Don't double-decode input, causing non-UTF-8 files to be corrupted
(<a
href="https://redirect.github.com/psf/black/issues/4964">#4964</a>)</li>
<li>Fix crash on standalone comment in lambda default arguments (<a
href="https://redirect.github.com/psf/black/issues/4993">#4993</a>)</li>
<li>Preserve parentheses when <code># type: ignore</code> comments would
be merged with other
comments on the same line, preventing AST equivalence failures (<a
href="https://redirect.github.com/psf/black/issues/4888">#4888</a>)</li>
</ul>
<h3>Preview style</h3>
<ul>
<li>Fix bug where <code>if</code> guards in <code>case</code> blocks
were incorrectly split when the pattern had
a trailing comma (<a
href="https://redirect.github.com/psf/black/issues/4884">#4884</a>)</li>
<li>Fix <code>string_processing</code> crashing on unassigned long
string literals with trailing
commas (one-item tuples) (<a
href="https://redirect.github.com/psf/black/issues/4929">#4929</a>)</li>
<li>Simplify implementation of the power operator &quot;hugging&quot;
logic (<a
href="https://redirect.github.com/psf/black/issues/4918">#4918</a>)</li>
</ul>
<h3>Packaging</h3>
<ul>
<li>Fix shutdown errors in PyInstaller builds on macOS by disabling
multiprocessing in
frozen environments (<a
href="https://redirect.github.com/psf/black/issues/4930">#4930</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Introduce winloop for windows as an alternative to uvloop (<a
href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li>
<li>Remove deprecated function <code>uvloop.install()</code> in favor of
<code>uvloop.new_event_loop()</code>
(<a
href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li>
<li>Rename <code>maybe_install_uvloop</code> function to
<code>maybe_use_uvloop</code> to simplify loop
installation and creation of either a uvloop/winloop evenloop or default
eventloop
(<a
href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li>
</ul>
<h3>Output</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/psf/black/blob/main/CHANGES.md">black's
changelog</a>.</em></p>
<blockquote>
<h2>Version 26.3.1</h2>
<h3>Stable style</h3>
<ul>
<li>Prevent Jupyter notebook magic masking collisions from corrupting
cells by using
exact-length placeholders for short magics and aborting if a placeholder
can no longer
be unmasked safely (<a
href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li>
</ul>
<h3>Configuration</h3>
<ul>
<li>Always hash cache filename components derived from
<code>--python-cell-magics</code> so custom
magic names cannot affect cache paths (<a
href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li>
</ul>
<h3><em>Blackd</em></h3>
<ul>
<li>Disable browser-originated requests by default, add configurable
origin allowlisting
and request body limits, and bound executor submissions to improve
backpressure
(<a
href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li>
</ul>
<h2>Version 26.3.0</h2>
<h3>Stable style</h3>
<ul>
<li>Don't double-decode input, causing non-UTF-8 files to be corrupted
(<a
href="https://redirect.github.com/psf/black/issues/4964">#4964</a>)</li>
<li>Fix crash on standalone comment in lambda default arguments (<a
href="https://redirect.github.com/psf/black/issues/4993">#4993</a>)</li>
<li>Preserve parentheses when <code># type: ignore</code> comments would
be merged with other
comments on the same line, preventing AST equivalence failures (<a
href="https://redirect.github.com/psf/black/issues/4888">#4888</a>)</li>
</ul>
<h3>Preview style</h3>
<ul>
<li>Fix bug where <code>if</code> guards in <code>case</code> blocks
were incorrectly split when the pattern had
a trailing comma (<a
href="https://redirect.github.com/psf/black/issues/4884">#4884</a>)</li>
<li>Fix <code>string_processing</code> crashing on unassigned long
string literals with trailing
commas (one-item tuples) (<a
href="https://redirect.github.com/psf/black/issues/4929">#4929</a>)</li>
<li>Simplify implementation of the power operator &quot;hugging&quot;
logic (<a
href="https://redirect.github.com/psf/black/issues/4918">#4918</a>)</li>
</ul>
<h3>Packaging</h3>
<ul>
<li>Fix shutdown errors in PyInstaller builds on macOS by disabling
multiprocessing in
frozen environments (<a
href="https://redirect.github.com/psf/black/issues/4930">#4930</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Introduce winloop for windows as an alternative to uvloop (<a
href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li>
<li>Remove deprecated function <code>uvloop.install()</code> in favor of
<code>uvloop.new_event_loop()</code>
(<a
href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li>
<li>Rename <code>maybe_install_uvloop</code> function to
<code>maybe_use_uvloop</code> to simplify loop
installation and creation of either a uvloop/winloop eventloop or
default eventloop
(<a
href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/psf/black/commit/c6755bb741b6481d6b3d3bb563c83fa060db96c9"><code>c6755bb</code></a>
Prepare release 26.3.1 (<a
href="https://redirect.github.com/psf/black/issues/5046">#5046</a>)</li>
<li><a
href="https://github.com/psf/black/commit/69973fd6950985fbeb1090d96da717dc4d8380b0"><code>69973fd</code></a>
Harden blackd browser-facing request handling (<a
href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li>
<li><a
href="https://github.com/psf/black/commit/4937fe6cf241139ddbfc16b0bdbb5b422798909d"><code>4937fe6</code></a>
Fix some shenanigans with the cache file and IPython (<a
href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li>
<li><a
href="https://github.com/psf/black/commit/2e641d174469c505d5ae905e75d4c769597e681f"><code>2e641d1</code></a>
docs: remove outdated Black Playground references (<a
href="https://redirect.github.com/psf/black/issues/5044">#5044</a>)</li>
<li><a
href="https://github.com/psf/black/commit/c014b22a2d5e0632587b47b81151658bddfa0b88"><code>c014b22</code></a>
Remove unused internal code (<a
href="https://redirect.github.com/psf/black/issues/5041">#5041</a>)</li>
<li><a
href="https://github.com/psf/black/commit/0dae20b2d009f2f03de8696d06b0c947d3abafc9"><code>0dae20b</code></a>
Add new changelog (<a
href="https://redirect.github.com/psf/black/issues/5036">#5036</a>)</li>
<li><a
href="https://github.com/psf/black/commit/c5c1cbddd92cecb554ac2a77a24139dd76831030"><code>c5c1cbd</code></a>
Minor release patches (<a
href="https://redirect.github.com/psf/black/issues/5035">#5035</a>)</li>
<li><a
href="https://github.com/psf/black/commit/7e5a828c37d71b6a6666e28eed444816def6a8f4"><code>7e5a828</code></a>
docs: clarify relationship between Black style and PEP 8 (<a
href="https://redirect.github.com/psf/black/issues/5025">#5025</a>)</li>
<li><a
href="https://github.com/psf/black/commit/69705deb8776e7c5e585668da106d1abe2cb8d77"><code>69705de</code></a>
docs: add clearer pyproject configuration guidance (<a
href="https://redirect.github.com/psf/black/issues/5026">#5026</a>)</li>
<li><a
href="https://github.com/psf/black/commit/35ea67920b7f6ac8e09be1c47278752b1e827f76"><code>35ea679</code></a>
Prepare release 26.3.0 (<a
href="https://redirect.github.com/psf/black/issues/5032">#5032</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/psf/black/compare/25.9.0...26.3.1">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Andrew Snare <andrew.snare@databricks.com>
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
profile legacy sql dw

### Relevant implementation details
added extraction queries

### Caveats/things to watch out for when reviewing:

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #2224 

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [X] modified existing command: `databricks labs lakebridge
configure-database-profiler`

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [x] manually tested
- [X] added unit tests
- [ ] added integration tests
## Changes
Creating the oracle extractor.
Unit tests passed for:
- dialog to configure the extractor
- run scripts to gather data
- local database population with extracted data from the Oracle
Database.
### What does this PR do?
Oracle profiler data extraction
### Relevant implementation details
The data analysis is still WIP.

### Caveats/things to watch out for when reviewing:

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #..

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [x] manually tested
- [x] added unit tests
- [ ] added integration tests

Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
## Changes

### What does this PR do?
* Standardizes the reconcile sampling query across all dialects
* Azure Synapse Dedicated SQL Pool does not accept VALUES as a derived
table, so the previous T-SQL path failed at runtime on Synapse
connections.

### Implementation Details
replaces the previous CTE form (non-T-SQL) and `VALUES` derived-table
form (T-SQL) with a single derived-table-join shape that works
everywhere.
                                                                       
```sql
  -- Before (T-SQL path):                                              
INNER JOIN (VALUES (...), (...)) AS recon([col1], [col2]) ON ...
-- Before (other dialects):
WITH recon AS (SELECT ... UNION SELECT ...), src AS (...)
SELECT ... FROM src INNER JOIN recon ON ...
   
-- After (all dialects):
SELECT ... FROM (...) AS src
  INNER JOIN (SELECT ... UNION SELECT ...) AS recon ON ...
```                                                                                                                                                                                                                 

### Linked issues
Resolves #2418

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`

### Tests

- [x] manually tested
- [x] added unit tests
- [x] added integration tests
…QL scripts (#2482)

## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
* Converts the MSSQL profiler from `type: python` venv steps to `type:
sql` / `type: ddl` in-process steps

### Key changes
- Replace `activity_extract.py`, `info_extract.py`, and
`mssql/common/*`.
 - Add and modify 13 query/DDL SQL pairs to profile.
- Use exisitng connector `MSSQLConnector` instead of duplicate in
`get_sqlserver_reader`.
- DDL types match documented source types (`SMALLINT`, `INTEGER`,
`DOUBLE`, `TIMESTAMP`).
  - `sys_info.sql` enumerates 37 columns explicitly (no `SELECT *`).
- `database` configurator prompt no longer defaults to `master`; user
supplies the actual target DB. Docs (`mssql.mdx`, `legacy_synapse.mdx`)
aligned with actual prompts.

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #2459 

### Functionality

- [ ] modified existing command: `databricks labs lakebridge
execute-database-profiler --source-tech mssql`

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [X] manually tested
- [ ] added unit tests
- [ ] added integration tests
…ation schema (#2304)

## Changes

This PR adds Redshift as a supported profiler assessment platform. It
introduces Redshift resources for serverless, provisioned, and
provisioned_multi_az

### What does this PR do?

- Adds Redshift as a supported platform for the profiler assessment
(alongside Synapse).
- Adds Redshift resources for all three variants (serverless,
provisioned, provisioned_multi_az): query/SQL files (e.g. query_view,
rs_managed_storage_gb, rs_nodes, chart queries), pipeline configs, and
validation schema.
- Files (2 levels: folder → subfolder, 3rd level: files under each)):
- resources/assessments/redshift/ → provisioned/, provisioned_multi_az/,
serverless/ with their .sql and pipeline_config.yml
  - resources/assessments/validation/ → redshift_extract_schema.yml

### Relevant implementation details

- Resources: Same query/file layout for serverless, provisioned, and
provisioned_multi_az under resources/assessments/redshift/{variant}/
(e.g. 0_query_view.sql … 9_chart_*.sql, pipeline_config.yml); serverless
also has 10_cost_incurred.sql. Shared validation:
resources/assessments/validation/redshift_extract_schema.yml.

### Caveats/things to watch out for when reviewing:

- Multi-AZ Redshift has limited system views (e.g. some STV views
unavailable); provisioned_multi_az SQL reflects that where applicable.
- Serverless Redshift has one additional query

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #..

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests

1. Manually Tested all credential flows on all clusters in AWS Sandbox
account aws-sandbox-field-eng (332745928618)
tests/resources/assessments/pipeline_config_main_redshift.yml: pipeline
config that runs that script.
- [x] manually tested
- [ ] added unit tests
- [ ] added integration tests

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
…CO summaries (#2420)

## Changes

### What does this PR do?

Adds a **Snowflake source** to the Lakebridge profiler (`databricks labs
lakebridge configure-database-profiler` / `execute-database-profiler`).
End-to-end this:

1. Prompts for Snowflake connection details + a Programmatic Access
Token (PAT) and saves credentials.
2. Extracts from `SNOWFLAKE.ACCOUNT_USAGE` (warehouse usage, query
history, storage, users, account info, plus optional pipe /
autoclustering / mat-view refresh credits) into a timestamped DuckDB
file (`profiler_extract_YYYYMMDD_HHMMSS.db`).
3. Runs post-extraction DuckDB computations to produce summary tables:
`query_mapper`, `warehouse_mapper`, `classified_queries`,
`annual_usage_by_category`, `storage_summary`, and
`migration_complexity`.
4. Ships a full PAT-setup walkthrough (with screenshots) under
`docs/lakebridge/docs/assessment/profiler/snowflake.mdx`.

### Relevant implementation details

- **New profiler source registered** in `assessments/_constants.py`
(`PROFILER_SOURCE_SYSTEM`, `PLATFORM_TO_SOURCE_TECHNOLOGY_CFG`,
`CONNECTOR_REQUIRED`).
- **`SnowflakeConnector`** in `connections/database_manager.py` is now
implemented (was `NotImplementedError`). Uses `snowflake-sqlalchemy` +
PAT (password authenticator). Eagerly opens a connection so PAT /
network-policy errors surface up front with actionable guidance and a
docs link.
- **`ConfigureSnowflakeAssessment`** in
`assessments/configure_assessment.py` collects connection + profiler
config (lookback 1–365 days, default 365 to match the legacy notebook).
- **Pipeline changes** (`assessments/pipeline.py`):
- DB filename now timestamped (`profiler_extract_<ts>.db`) so reruns
don't clobber prior extracts.
- SQL templates support `{lookback_days}` substitution from saved
config.
- Optional steps that fail no longer abort the run; they log a warning
and (for `adjusted_rate`) write a sensible default row.
  - Every extracted table gets an `extract_timestamp` column.
- **Migration complexity analysis**
(`assessments/snowflake_computations.py`) classifies each unique query
as native/simple/medium/complex by parsing function calls against
bundled Snowflake + Photon function lists. Defaults to a 6h wall-clock
timeout; on timeout the user is prompted once to retry without a limit.
- **Resource-path resolution**
(`_constants.py::_find_product_path_prefix`) now works for both
`databricks labs install` layouts *and* editable `pip install -e .`
source layouts — earlier the CLI couldn't find SQL resources outside the
labs install path.
- **Auth ergonomics**: `cli.py::_warn_on_databricks_auth_ambiguity`
warns (non-blocking) when `~/.databrickscfg` has multiple profiles on
the same host or when `DATABRICKS_TOKEN` is set alongside cfg profiles —
both produced opaque SDK errors during testing.
- **New deps**: `snowflake-connector-python>=3.6.0`,
`snowflake-sqlalchemy>=1.7.0`.

### Caveats/things to watch out for when reviewing

- **Post-merge follow-up tracked**: the two `# TODO` comments in
`assessments/configure_assessment.py` and
`connections/database_manager.py` reference a branch URL for the docs
walkthrough (`feature/snowflake-profiler`). After this PR merges and the
docs site rebuilds, both must be flipped to the published URL:
- `configure_assessment.py` →
`https://databrickslabs.github.io/lakebridge/docs/assessment/profiler/snowflake#create-a-snowflake-personal-access-token-pat`
- `database_manager.py` (`docs_url` stem) →
`https://databrickslabs.github.io/lakebridge/docs/assessment/profiler/snowflake`
(anchors are concatenated at usage sites —
`#part-3--bypass-the-network-policy-temporary`,
`#part-2--create-a-personal-access-token`)
- Greppable with: `grep -rn "feature/snowflake-profiler/docs"`. Will be
done in a small follow-up PR.
- `tests/unit/test_install.py::test_configure_all_override_installation`
switched from picking source `0` to `1` because `Prompts.choice` sorts
alphabetically — adding `snowflake` shifted `synapse` from index 0 to 1.
Worth a sanity-check that the test is asserting the intent we want.
- `pipeline.py` now silently swallows failures of optional steps with a
warning + default row for `adjusted_rate`. Other optional steps just
log; if you'd rather keep the explicit error, flag it.
- Migration complexity is intentionally synchronous + slow on big
histories (6h default, opt-in unbounded retry). It runs *after* the rest
of the extract is on disk, so a Ctrl+C still leaves a usable DuckDB.
- PAT auth uses Snowflake's `password` authenticator (PATs are accepted
there). External browser auth is wired up but untested in this PR.
- New Snowflake deps are pulled in at the project level — they're heavy.
Acceptable, but worth confirming.

### Linked issues

<!-- Add a Resolves #... line here if this maps to an existing issue.
-->

### Functionality

- [x] added relevant user documentation
(`docs/lakebridge/docs/assessment/profiler/snowflake.mdx` + index
update)
- [ ] added new CLI command
- [x] modified existing command: `databricks labs lakebridge
configure-database-profiler`
- [x] modified existing command: `databricks labs lakebridge
execute-database-profiler`
- [x] new profiler source: Snowflake (via `ACCOUNT_USAGE`)

### Tests

- [x] manually tested end-to-end against a live Snowflake account (PAT
auth, full 365-day lookback, all summary tables produced)
- [x] updated
`tests/unit/test_install.py::test_configure_all_override_installation`
for the new alphabetical source ordering
- [x] **added unit tests** (30 tests, all passing locally):
- `tests/unit/assessment/test_snowflake_configurator.py` — full
credential flow + lookback validation loop.
- `tests/unit/assessment/test_snowflake_computations.py` — seeds an
in-memory DuckDB extract and asserts all summary tables, mapper sizes,
credit math, latest-snapshot storage filter, `extract_timestamp`
coverage, and the `duckdb.InterruptException` →
`MigrationComplexityTimeout` conversion.
- `tests/unit/connections/test_snowflake_connector.py` —
`_raise_friendly_connect_error` branches (network-policy + PAT
credential errors point at the right docs anchors), unmapped errors
propagate unchanged, `parse_snowflake_account` /
`validate_snowflake_account` URL handling.
- Engine-mocking tests for `_connect` deliberately skipped — would
require mocking `snowflake.sqlalchemy` and add little coverage value
beyond the friendly-error tests.
- [ ] added integration tests (manual run against live Snowflake covers
this; full CI integration would require a managed Snowflake account)

This pull request and its description were written by Isaac.
jneil17 and others added 30 commits June 11, 2026 05:49
## What
Adds a `rate_sheet` extract to the Snowflake profiler that pulls the
effective per-credit/unit rate from
`SNOWFLAKE.ORGANIZATION_USAGE.RATE_SHEET_DAILY` — a 90-day average,
scoped to the current account and split by rating type. The same rows
also carry the account tier (`SERVICE_LEVEL`) and the billing currency.

Also logs the absolute path of the DuckDB extract on successful
completion, so users can find the output file (this benefits all
profilers, not just Snowflake).

## Why
The downstream TCO / value model needs the customer's effective
`$/credit` rate and Snowflake tier to convert consumed credits into
dollars and to derive their contract discount. The profiler emitted
credits but no rate, so the dollar conversion had to be sourced
manually.

## Changes
- New `resources/assessments/snowflake/rate_sheet.sql`
- Register the `rate_sheet` step in the Snowflake `pipeline_config.yml`
- Log the extract output path on successful completion in `pipeline.py`

## Access requirement
`ORGANIZATION_USAGE` lives in the shared `SNOWFLAKE` database. The
existing default role `ACCOUNTADMIN` can read it from an
ORGADMIN-enabled account (or grant a custom role the
`ORGANIZATION_USAGE_VIEWER` database role). When access isn't available
the step simply returns 0 rows and is skipped — the run still succeeds.

## Testing
- Ran the Snowflake profiler end-to-end against an ORGADMIN-enabled
account; `rate_sheet` populated with tier (Enterprise), region, currency
(USD), and effective rate ($3.00/credit).
- `tests/unit/assessment/` unit tests pass (87 passed).

Classifying usage into the value model (BI/ETL split, discount
derivation) is downstream work outside this PR.
Adds a BigQuery source-tech to the Lakebridge profiler.

## What it does

Runs 16 region-qualified `INFORMATION_SCHEMA` queries against the
customer's BigQuery project(s) (configurable via `project.region` pairs)
and writes 12 analysis tables into a local DuckDB file at
`~/.databricks/labs/lakebridge_profilers/bigquery_assessment/profiler_extract.db`.

Pipeline structure mirrors the existing Synapse / MSSQL / legacy_synapse
/ Oracle source-techs:
- `configure-database-profiler` interactive prompt → writes
`credentials.yml`
- `execute-database-profiler --source-tech bigquery` → runs
`bq_metadata_extract.py`

## Design highlights

- **Auth**: ADC-only (`gcloud auth application-default login` or
`GOOGLE_APPLICATION_CREDENTIALS` for non-interactive). Service-account
impersonation supported via ADC.
- **Pair syntax**: `project.region` — matches Google's [fully-qualified
resource-path
convention](https://cloud.google.com/iam/docs/full-resource-names#bigquery),
bulk-entry friendly for multi-project sizing.
- **Region qualifier in SQL**: required by BQ for INFORMATION_SCHEMA
views per
[docs.cloud.google.com](https://docs.cloud.google.com/bigquery/docs/information-schema-intro#region_qualifier)
— the client's `location` parameter doesn't substitute for it.
- **Shared helpers**: uses `assessments.common.{cli, duckdb_helpers}`
from #2431 — no duplication.

## Verification

- `make fmt && make lint && make test` clean (1187 unit tests pass, 2
skipped, 3 xfailed).
- End-to-end smoke test produces the expected 12 DuckDB tables.

## Follow-up PRs

- BigQuery profiler dashboard (Lakeview JSON) — separate PR after this
merges.
- Integration test + `sandbox_bigquery_*` fixtures — separate PR after
this merges.
- Continuation of #2445 (closed; see #2449 for the JFrog/OIDC fork-PR
tracking).



---

### Review follow-ups
Addressed the blocking `table_storage` billing-model split in this PR
(commit `60e08f13`; verified against a real BQ project).

The non-blocking notes are tracked for a follow-up in #2493:
- streaming/write-API profiling-window filter
- `timeline_analysis` `bytes_processed` diagnostic
- catch `GoogleAPIError` in the per-SQL handler
- CHANGELOG note for the profiler-schema default change

---------

Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
## Changes
This PR fixes a bug in the Redshift serverless profiler managed storage
extract.
### What does this PR do?
- Fixes `rs_managed_storage_gb` aggregation for the Redshift serverless
variant.
- Files changed
-
src/databricks/labs/lakebridge/resources/assessments/redshift/serverless/2_rs_managed_storage_gb.sql
### Relevant implementation details
`sys_serverless_usage` holds hourly snapshots of allocated storage. The
previous `sum(round(data_storage / 1024.0, 2))` accumulated those
snapshots, so the reported managed storage grew linearly with the
lookback window (e.g., 24 x actual_GB for a one-day extract). Replaced
with `round(avg(data_storage) / 1024.0, 2)` so the extract returns the
representative allocation.
### Caveats/things to watch out for when reviewing:
Affects only the `redshift_serverless` variant. The `provisioned` and
`provisioned_multi_az` variants use different source tables
(`stv_node_storage_capacity` / placeholder) and are unchanged. Output
schema (`set_name VARCHAR`, `rs_managed_storage_gb DOUBLE`) is identical
so no validator schema, schema-definition YAML, mock DuckDB fixture, or
test changes are required.
### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->
Resolves #..
### Functionality
- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own
### Tests
1. Manually tested in AWS Sandbox account aws-sandbox-field-eng
(332745928618) against a serverless Redshift workspace.
- [x] manually tested
- [ ] added unit tests
- [ ] added integration tests
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
add source system and report type to user agent extras
## Changes

This PR adds Redshift as a supported profiler assessment platform. It
wires Redshift into the CLI and assessment config, and parameterizes
profiler/validator tests for both synapse and Redshift using mocks (no
live cluster).

### What does this PR do?

- Adds Redshift as a supported platform for the profiler assessment
(alongside Synapse).
- Wires Redshift into the CLI (source technology / transfer) and
assessment constants (_constants.py: variants, config path template,
PLATFORM_TO_SOURCE_TECHNOLOGY_CFG, PROFILER_SOURCE_SYSTEM).
- Parameterizes existing profiler and profiler-validator tests for
synapse and redshift; adds Redshift mock extract and test resources so
all tests run for both platforms without a live Redshift cluster.
- Full list of changed files:
  - Source
    - src/databricks/labs/lakebridge/assessments/__init__.py
    - src/databricks/labs/lakebridge/assessments/_constants.py
    - src/databricks/labs/lakebridge/assessments/configure_assessment.py
    - src/databricks/labs/lakebridge/assessments/profiler.py
    - src/databricks/labs/lakebridge/cli.py
    - src/databricks/labs/lakebridge/connections/credential_manager.py
  - Tests – integration
    - tests/integration/assessments/profiler_extract_utils.py
    - tests/integration/assessments/test_profiler.py
    - tests/integration/assessments/test_profiler_validator.py
  - Tests – resources
    - tests/resources/assessments/db_extract_redshift.py
    - tests/resources/assessments/pipeline_config_main_redshift.yml
    - tests/resources/assessments/redshift_schema_def.yml

### Relevant implementation details

- Tests: Session-scoped mock_redshift_profiler_extract and
PLATFORM_VALIDATOR_CONFIG drive parameterized tests;
db_extract_redshift.py and pipeline_config_main_redshift.yml provide a
script-based Redshift-shaped DuckDB for execution tests with no real
cluster.

### Caveats/things to watch out for when reviewing:

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #..

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->
1. Redshift mock extract
(tests/integration/assessments/profiler_extract_utils.py)
Added Redshift table definitions: query_view, rs_managed_storage_gb,
rs_nodes (same pattern as Synapse: 3 tables, 1 empty).
Added RedshiftProfilerBuilder and build_mock_redshift_extract() so
validator tests can use a Redshift-shaped DuckDB without a real cluster.
2. Test schema for Redshift
(tests/resources/assessments/redshift_schema_def.yml)
Minimal schema for validator tests: query_view, rs_managed_storage_gb,
rs_nodes.
rs_managed_storage_gb.rs_managed_storage_gb is defined as VARCHAR (mock
has DOUBLE) so test_validate_invalid_schema_check still fails as
intended.
3. Profiler validator tests
(tests/integration/assessments/test_profiler_validator.py)
Added session-scoped mock_redshift_profiler_extract and
PLATFORM_VALIDATOR_CONFIG for platform-specific table names and counts.
All 7 validator tests are parameterized with
@pytest.mark.parametrize("platform", ["synapse", "redshift"]).
Each test runs once per platform; logic is shared, no new test cases.
test_get_profiler_extract_path is unchanged (no platform param).
4. Profiler tests (tests/integration/assessments/test_profiler.py)
All 5 tests parameterized with @pytest.mark.parametrize("platform",
["synapse", "redshift"]).
Execution tests use script-based configs only (no live DB):
Synapse: pipeline_config_main.yml + db_extract.py.
Redshift: pipeline_config_main_redshift.yml + db_extract_redshift.py.
test_profile_execution_config_override copies the platform-specific
script (db_extract.py or db_extract_redshift.py) into a temp dir and
runs it.
5. Redshift script and config (for execution tests without live
Redshift)
tests/resources/assessments/db_extract_redshift.py: standalone script
that creates a DuckDB at --db-path with the same 3 Redshift tables (no
imports from tests).
6. Manually Tested all credential flows on all clusters in AWS Sandbox
account aws-sandbox-field-eng (332745928618)
tests/resources/assessments/pipeline_config_main_redshift.yml: pipeline
config that runs that script.
- [x] manually tested
- [x] added unit tests
- [x] added integration tests

---------

Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
## Changes

This PR Redshift profiler docs to the assessment platform. 

### What does this PR do?

- Adds Redshift profiler docs for the assessment (alongside Synapse).
- Files changed
  - .gitignore
  - docs/lakebridge/docs/assessment/profiler/index.mdx
  - docs/lakebridge/docs/assessment/profiler/redshift.mdx

### Relevant implementation details

File index.mdx changed to add reference to Redshift profiler. File
redshift.mdx is added.

### Caveats/things to watch out for when reviewing:

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #..

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests

1. Manually Tested all credential flows on all clusters in AWS Sandbox
account aws-sandbox-field-eng (332745928618)
tests/resources/assessments/pipeline_config_main_redshift.yml: pipeline
config that runs that script.
- [x] manually tested
- [ ] added unit tests
- [ ] added integration tests

---------

Co-authored-by: dgomez04 <diego.gomez@databricks.com>
Bump version to 0.14.0
## Changes

This PR updates the Java requirements for the CLI from 11 to 21. The
most recent builds of Morpheus switched from Java 11 to 21, so this is
now a requirement.

### Linked issues

Context: databrickslabs/morpheus#952 (private)

### Functionality

- updated relevant user documentation
- modified existing command: `databricks labs lakebridge
install-transpile`

### Tests

- updated integration tests
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
Fix integration test assertion after databricks workspace behavior
change

- before: `` [`system`.`builtin`, `system`.`session`,
`catalog`.`schema`] ``
- after: `` [`system`.`session`, `system`.`builtin`, `system`.`ai`,
`catalog`.`schema`] ``

### Caveats/things to watch out for when reviewing:
A more resilient fix (wildcarding the path inside the assertion) was
considered and deliberately skipped in favour of the minimal change.

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #2515 

### Functionality

- [X] update test code

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [ ] manually tested
- [ ] added unit tests
- [X] added integration tests
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
Introduce the concept of a variant of a profiler. the user is prompted
to select a variant when executing profiler if supported

### Relevant implementation details
- **Variant model** — add `SOURCE_SYSTEM_VARIANTS` ;
`PROFILER_SOURCE_SYSTEM` is now the flat list of real source systems.
Remove `REDSHIFT_VARIANTS`, and `source_system_family()`.
- **Pipeline resolution** — Remove `SOURCE_SYSTEM_TO_PIPELINE_CFG`,
replace the path map with convention-based `get_pipeline(source_system,
variant)` →
`resources/assessments/<source>/<variant>/pipeline_config.yml`.
- **CLI** — `execute-database-profiler` accepts `--variant`.
`parse_profiler_variant` prompts for a variant when the source has
variants and none is given.

### Caveats/things to watch out for when reviewing:
- `configure-profiler` and `test-profiler-connection` currently do not
accept variant.
- integration with desktop app needs to be defined

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [X] modified existing command: `databricks labs lakebridge
execute-database-profiler`

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [ ] manually tested
- [X] added unit tests
- [ ] added integration tests
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
Remove the profiler dashboards

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Resolves #2473 

### Functionality

- [X] removed existing command: `databricks labs lakebridge
create-profiler-dashboard`
#2512)

<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
Fix connection testing implementation that relied on magic if conditions
and duplicate config that was not modeled correctly and could be broken
easily

### Relevant implementation details
Introduce `AssessmentConfigurator.test_connection()` to fetch the saved
creds and delegates to `_check_connection()`:
- base → `DatabaseManager` health check (snowflake, oracle, mssql,
legacy_synapse, redshift)
- `ConfigureSynapseAssessment` → `validate_synapse_pools` (per SQL pool)
- `ConfigureBigQueryAssessment` → `validate_bigquery_pairs` (per
project/region)

This removes the `if source_tech == "synapse"` special-case in
`test-profiler-connection` and the hand-maintained `CONNECTOR_REQUIRED`
source-system flag which can be derived automatically


### Functionality

- [X] modified existing command: `databricks labs lakebridge
test-profiler-connection`
- [X] modified existing command: `databricks labs lakebridge
configure-database-profiler`


### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [ ] manually tested
- [X] added unit tests
- [ ] added integration tests
…ests (#2521)

## Summary

This PR addresses
[#2479](#2479) by
migrating reconcile integration tests to use the real `pytester` `ws`
fixture instead of `mock_workspace_client`.

### Changes made

- Replaced `mock_workspace_client` fixture usage with `ws` in:
  - `tests/integration/reconcile/test_recon_capture.py`
  - `tests/integration/reconcile/test_aggregates_recon_capture.py`
  - `tests/integration/reconcile/query_builder/test_execute.py`
- Removed now-unnecessary assignments like `ws = mock_workspace_client`.
- Renamed a local mock variable in `test_execute.py`
(`mock_workspace_client` -> `ws_client_mock`) to avoid fixture-style
confusion while keeping test behavior unchanged.

## Why

Integration tests now run with a real Spark workspace context, so
`WorkspaceClient` should come from the shared `ws` fixture for
consistency with the current integration test model.

Fixed issue: #2479
## Test plan

- [x] `python -m pytest
tests/integration/reconcile/test_aggregates_recon_capture.py
--collect-only -q`
- [x] `python -m pytest
tests/integration/reconcile/test_recon_capture.py --collect-only -q`
- [x] `python -m pytest
tests/integration/reconcile/query_builder/test_execute.py --collect-only
-q`
- [ ] Run full integration test execution in CI / Databricks-enabled
environment
## What does this PR do?

Adds **Teradata profiler ** to Lakebridge. The profiler connects to a
Teradata system, runs a set of extraction queries defined entirely in
configuration, and produces a local DuckDB extract.

Teradata ships in two **variants** — `core` and `pdcr` — selected at
extraction time via the existing `--variant` flag, each backed by its
own pipeline configuration.


## Relevant implementation details

- **Source registration** (`assessments/_constants.py`): registers
`teradata` in `PROFILER_SOURCE_SYSTEM` and its variants (`core`, `pdcr`)
in `SOURCE_SYSTEM_VARIANTS`, mirroring Redshift.
- **Connector** (`connections/database_manager.py`): `TeradataConnector`
(SQLAlchemy `teradatasql` driver) + factory registration; supports a
configurable `port` (default `1025`). Adds the `teradatasqlalchemy`
dependency.
- **Credential dialog** (`assessments/configure_assessment.py`):
`ConfigureTeradataAssessment` collects host / port / user / password /
default database, with `local | env` secret-vault handling.
- **Extraction resources** (`resources/assessments/teradata/`):
per-variant `core/pipeline_config.yml` and `pdcr/pipeline_config.yml`
plus shared SQL/DDL assets for system info, nodes info, disk/usage
aggregates, storage, object types, user databases, UDFs, PDCR
aggregates, and DBQL-core.
- **Variant behavior**: the `core` variant extracts
`td_dbql_core_info_extract` (DBQL-core); the `pdcr` variant extracts
`td_pdcr_info_agg_extract` and `td_pdcr_sp_exe_info_agg_extract` (PDCR
aggregated history). Selected via `--source-tech teradata --variant
core|pdcr`.
- **Docs** (`docs/.../assessment/profiler/teradata.mdx` + index):
Teradata profiler guide covering prerequisites, the two variants, and
how to choose between them.

This PR makes **no changes** to the generic CLI, installer, deployment,
or dashboard code — Teradata is supported purely through the existing
config-driven extractor + variant framework, mirroring Redshift/Oracle.

## Caveats / things to watch out for when reviewing

- Scope is **extractor only**: no Unity Catalog ingestion, no Lakeview
dashboard publishing, no `create-profiler-dashboard` CLI command, and no
SQLTextInfo masking/tagging in this PR. (The DBQL-core extract redacts
`SQLTextInfo` to `[REDACTED]` at extraction time, so raw query text
never leaves the source.)
- `teradatasqlalchemy` is added as a runtime dependency.
- Variant selection (`core` vs `pdcr`) is explicit via `--variant`; if
PDCR (`PDCRINFO`) is unavailable, use `--variant core`.

## Linked issues

Resolves #..

## Functionality

- [x] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command
- [x] added Teradata profiler extraction resources (config-driven; no
framework changes)

## Tests

- [x] manually tested (extractor CLI run end-to-end)
- [x] added unit tests
- `test_teradata_profiler.py` — variant (`core`/`pdcr`) pipeline-config
resolution + missing/invalid-config handling
  - `test_teradata_variants.py` — variant → pipeline-config path mapping
- `test_assessment.py` — `ConfigureTeradataAssessment` dialog + factory
  - `test_database_manager.py` — `TeradataConnector`
- [ ] added integration tests

`make test` green (1276 passed, aside from an unrelated pre-existing
`test_cli_analyze` timeout flake); `make fmt` / ruff / mypy / pylint
10.00/10.
…profiler (#2531)

## Summary
This pull request aligns the four schema strings to match the `SELECT`
column order in `queries.py`.

Because rows land positionally into the declared schema, queries on
these tables against an extract were silently reading the wrong column:
- `dedicated_tables` and `serverless_tables` had `TABLE_NAME` and
`TABLE_SCHEMA` swapped relative to what `SynapseQueries.list_tables()`
actually returns (`TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE,
POOL_NAME`).
- `dedicated_views` and `serverless_views` had `CHECK_OPTION` and
`IS_UPDATABLE` listed first, when the query returns `TABLE_CATALOG,
TABLE_SCHEMA, TABLE_NAME, CHECK_OPTION, IS_UPDATABLE, VIEW_DEFINITION,
POOL_NAME`.


Closes #2530
…ing` (#2534)

`AUTOMATIC_CLUSTERING_HISTORY` includes clustering activity on
Snowflake-owned system databases (`SNOWFLAKE`, `UTIL_DB`,
`SNOWFLAKE_SAMPLE_DATA`). These are not customer workloads and their
inclusion inflates the clustering credit picture in the extract.

`database_objects.sql` already excludes these same databases in every
`UNION` branch. This pull requst adds the equivalent filter to
`automatic_clustering.sql` to bring it in line.
…#2535)

## Summary

- Add intermediate `write_and_read_df_with_volumes` checkpoints in
reconcile computation paths that were still missing materialization
(marked with TODOs in #2257).
- Materialize threshold mismatch rows, aggregate per-rule outputs, and
sampled mismatch capture DataFrames before downstream `count()` /
sampling work.

## Motivation

Large Spark plans in reconcile can retain long lineages and cause
backpressure. The reconcile runtime already materializes some
intermediate results (e.g. after source/target join); this PR fills the
remaining gaps called out in
[#2257](#2257).

## Changes

| Phase | Location | Change |
|---|---|---|
| Threshold comparison | `reconciliation._compute_threshold_comparison`
| Materialize filtered `mismatched_df` before `count()` |
| Aggregate per-rule | `compare.reconcile_agg_data_per_rule` | Thread
`persistence` and materialize `mismatch`, `missing_in_src`,
`missing_in_tgt` |
| Mismatch capture (sampling) |
`compare.capture_mismatch_data_and_columns` | Require `persistence`;
wired from `Reconciliation._get_mismatch_data` |

Fixed issue: #2257

**Note:** These are temporary intermediate writes via
`ReconIntermediatePersist` (UC volume / parquet), not changes to final
`ReconCapture` MAIN/METRICS/DETAILS output tables. Temp data is still
cleaned up in `trigger_recon` / `trigger_recon_aggregates`.

## Test plan

- [x] CI integration reconcile tests (`test_compare.py`,
`test_aggregates_reconcile.py`, `test_execute.py`) — covers aggregate
per-rule, mismatch capture, and threshold paths via existing e2e
coverage
- [ ] Manual smoke on Databricks (optional): run reconcile job and
confirm intermediate volume writes under `metadata_config.volume`
…2503)

## Summary
- Refactored reconcile runtime internals to use `SourceConnectionConfig`
and `TargetConnectionConfig` instead of `DatabaseConfig`.
- Updated `Reconciliation`, `TriggerReconService`, and
`TriggerReconAggregateService` to pass source/target connection config
objects explicitly.
- Updated integration tests in reconcile query-builder and
aggregate/capture paths to align with the new config model.
- Removed remaining `DatabaseConfig` usage from
`tests/integration/reconcile/test_recon_capture.py`.
## Backward Compatibility
- Preserved `ReconCapture` constructor compatibility for legacy
`DatabaseConfig`-style callers during the migration period.
## Validation
- `uv run python -m pytest
tests/integration/reconcile/test_recon_capture.py -q`
  - `23 skipped`
- `uv run python -m pytest
tests/integration/reconcile/query_builder/test_execute.py -q`
  - `1 passed, 15 skipped`
- `uv run python -m pytest
tests/integration/reconcile/test_aggregates_reconcile.py -q`
  - `4 passed, 2 skipped`
- `uv run python -m pytest
tests/integration/reconcile/test_aggregates_recon_capture.py -q`
  - `1 skipped`
## Notes
- `uv.lock` changes from local environment sync were intentionally
excluded from this PR.

### Linked issues
Fixed issue : #2417

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [ ] manually tested
- [ ] added unit tests
- [ ] added integration tests
…s. (#2525)

### What does this PR do?

This PR updates the `lock-dependencies` mechanism so that scrubbing also
takes care of the CDN URLs that are embedded in there; some
mirrors/proxies, in addition to rewriting the registry, also rewrite the
URLs.

### Caveats/things to watch out for when reviewing:

The `lint-uv` check will fail, but can be safely ignored/overridden to
merge.

### Linked issues

Relates: databrickslabs/blueprint#392

### Functionality

- updates `make lock-dependencies`

### Tests

- manually tested: produces the `uv.lock` included in this PR.
- automated CI checks the resulting lock-file.
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
 Suppresses the `FutureWarning` from `google.api_core`

### Caveats/things to watch out for when reviewing:
- Suppression is scoped to these two import sites (the only ones
importing google in the repo). The warning fires only on first import of
`google.api_core` process-wide.
- Once the project moves to Python 3.11+, the warning stops firing on
its own and these `with` blocks become harmless no-ops that can be
removed.


### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [ ] modified existing command: `databricks labs lakebridge ...`
- [ ] ... +add your own

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [ ] manually tested
- [ ] added unit tests
- [ ] added integration tests
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?
* Add missing extract source otherwise the profiler doesnt load

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [X] manually tested
- [ ] added unit tests
- [ ] added integration tests
## What
Adds **BigQuery** as a reconcile source, at parity with the other
sources (`schema` / `row` / `data` / `all` / `aggregate`). BigQuery was
already supported by the transpiler and profiler; this closes the gap
for reconcile.

## How it works
BigQuery uses the same **Lakehouse Federation `remote_query`** path as
Snowflake/Oracle/etc. — no new dependencies. `BigQueryDataSource` uses
backtick-quoted 3-part `project.dataset.table` names and reads metadata
from `INFORMATION_SCHEMA.COLUMNS`. sqlglot already ships a BigQuery
dialect, so query generation and schema comparison are reused unchanged.

## Changes
- **Connector** `reconcile/connectors/bigquery.py`
(`BigQueryDataSource`); registered in `ReconSourceType` and
`source_adapter.create_adapter`; install prompt + `recon_capture`
display name.
- **Schema type handling**: the connector's `INFORMATION_SCHEMA` query
canonicalizes the few BigQuery types sqlglot can't bridge to Databricks
(`BIGNUMERIC`→`string`, bare `NUMERIC`→`decimal(38,9)`, `TIME`→`string`,
`JSON`→`variant`, `RANGE<T>`→`struct<…>`); everything else is left to
sqlglot.
- **Row hashing**: adds a BigQuery hash algorithm (`TO_HEX(SHA256())`,
matching Databricks `sha2(...,256)`) and a **scale-aware decimal
transform** (`FORMAT('%.<scale>f', col)`) so BigQuery's
trailing-zero-stripped numeric strings match Spark's scale-padded
`DECIMAL` strings.
- **Docs + tests**: supported-sources row, a BigQuery config tab (with a
compute note), connector tests, a type-coverage guardrail test, and an
adapter test.

## Testing
- `make fmt` / `make lint` (pylint 10.0/10.0) and `make test` — green
(1319 passed).
- **End-to-end on a real workspace**: a BigQuery source (UC Federation
connection) reconciled against an identical Databricks copy via the
deployed reconcile **job** — `schema`, `row`, and `data` all matched (0
mismatches / 0 missing).

## Notes / limitations
- **Compute**: BigQuery reads use `remote_query`, which requires
**Databricks Runtime 17.3+** or **serverless** compute (the reconcile
job's default cluster may run an older runtime — point it at a DBR 17.3+
cluster via `job_overrides.existing_cluster_id`, or run serverless).
This applies to all Lakehouse Federation reconcile sources. Documented
in the config tab.
- `INTERVAL` maps to two Databricks columns, which the 1:1 schema
comparison can't represent — surfaces as a visible mismatch
(documented).

---------

Co-authored-by: M Abulazm <mohamed.abulazm@databricks.com>
## Feedback

Concurrency numbers are currently unreliable when the same pool name
exists across multiple workspaces.

Because the Synapse session/request extract tables only track
`POOL_NAME` + `EXTRACT_TS`, data from different workspaces collides once
it lands in the extract, making it impossible to differentiate them.

## Fix

Added a `WORKSPACE_NAME` column to uniquely identify extracts. This
value is pulled from the workspace config's name property, which is
already available in both extracts, meaning no extra API calls were
required.

Downstream consumers can now accurately group metrics by
`(WORKSPACE_NAME, POOL_NAME)` when required.

This pull request and its description were written by Isaac.
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?

The mssql profiler previously assessed only a single database per run.
This PR adds automatic detection between single-database and
multi-database profiling based on the SQL Server edition and the
configured database, and introduces a reusable variant-resolution
mechanism to support it.

**mssql auto-detection**
- A configured database scopes profiling to that one database
(`single_db`).
- A blank database lets the edition decide: Azure SQL Database
(`EngineEdition = 5`) falls back to `single_db`; on-prem SQL Server and
Azure SQL Managed Instance profile every accessible database
(`multi_db`).
- Credential configuration now accepts a blank database name (blank =
all databases) for mssql.

**Multi-database extracts** (`mssql/multi_db/`, new)
- New pipeline config and SQL extracts that fan out across every online,
accessible user database (system DBs excluded), tagging each row with a
`database_name` column.
- UNION-based dynamic SQL for catalog views (tables, views, columns,
routines, indexed views); cursor + `USE` for database-scoped DMVs
(`db_sizes`, `table_sizes`) that can't use three-part naming.
- All extracts and DDL gain a `DATABASE_NAME` column. The original
config moves to `mssql/single_db/`.

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

Progresses #2483 

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [X] modified existing command: `databricks labs lakebridge
execute-database-profiler --source-tech mssql`
- [ ] ... +add your own

### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [X] manually tested: Azure SQL and On-prem SQL Server on Docker
- [X] added unit tests
- [ ] added integration tests
…#2497)

<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
  ## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
  ### What does this PR do?

Adds a `sampling_options` block to the reconcile table config so users
can control how many sample rows are captured per result set (mismatch,
missing-in-source,
missing-in-target) instead of the fixed 50. Previously large sample
sizes on Databricks blew up the UNION-based sampling query against Spark
Connect; the Databricks path now uses
  a temp view so it scales.
  
  ### Relevant implementation details

- `SamplingSpecifications` now defaults to `count` / `50` and validates
the value (coerces to int, rejects bools/non-numeric, keeps `fraction`
disabled).
  `Table.get_max_sample_size()` exposes the configured value.
- Sample-size bounds are applied in `NormalizeReconConfigService`
(engine-aware), not in the samplers:
    - Databricks source: clamped to `[1, 50_000]`.
- Non-Databricks source: honored up to `400`, larger values capped at
400 with a warning.
    - `value <= 0` falls back to 50.
- `SamplingQueryBuilder` registers the recon keys as a temp view for the
Databricks engine (`SELECT * FROM recon_keys_<uuid>`) so the generated
SQL stays small regardless of
  sample size; other dialects keep the inline-UNION derived table.
  - The old fixed `[50, 400]` clamp was removed from the samplers.

  ### Caveats/things to watch out for when reviewing:

- Non-Databricks sources are intentionally capped at 400 (matches prior
behaviour); only Databricks honors larger sizes up to 50,000.
  - `fraction` sampling type stays disabled and raises a config error.

  ### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes,
fixed, resolve, resolves, resolved. See

https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

  Resolves #2559 
  
  ### Functionality

  - [x] added relevant user documentation
  - [ ] added new CLI command
  - [ ] modified existing command: `databricks labs lakebridge ...`
- [x] new config field: `sampling_options` on the reconcile Table config

  ### Tests
<!-- How is this tested? Please see the checklist below and also
describe any other relevant tests -->

- [x] manually tested — end-to-end on Databricks source (default → 50,
100000 → capped to 50000) and Redshift source (default → 50, 500 →
capped to 400 with warning), covering
  column mapping, drop columns and schema validation
  - [x] added unit tests
  - [x] added integration tests
<!-- REMOVE IRRELEVANT COMMENTS BEFORE CREATING A PULL REQUEST -->
## Changes
<!-- Summary of your changes that are easy to understand. Add
screenshots when necessary, they're helpful to illustrate the before and
after state -->
### What does this PR do?

`Profiler._execute` (profiler.py:93-95) and `PipelineClass.execute()`
(pipeline.py:67,76) both correctly raise RuntimeError on step failures.
The exception gets swallowed one level up, in blueprint's `App._route`:
  ```
  # databricks/labs/blueprint/cli.py:119-126
  try:
      cmd.fn(**kwargs)
  except Exception as err:
      logger.error(f"{err.__class__.__name__}: {err}")
```
  The error is logged and the process falls off the end of main — so the Python entrypoint exits 0 on any profiler failure, and the databricks CLI in turn reports success. This affects every lakebridge command that signals failure with a plain exception, not just the profiler.

  There is an escape hatch already used: SystemExit derives from BaseException, so it sails through blueprint's except Exception and terminates the process with exit code 1. That's exactly why `test-profiler-connection` raises SystemExit at cli.py:1206-1217.

### Linked issues
<!-- DOC: Link issue with a keyword: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved. See https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword -->

Resolves #..

### Functionality

- [ ] added relevant user documentation
- [ ] added new CLI command
- [X] modified existing command: `databricks labs lakebridge execute-database-profiler`

### Tests
<!-- How is this tested? Please see the checklist below and also describe any other relevant tests -->

- [ ] manually tested
- [ ] added unit tests
- [ ] added integration tests
…ines schema (#2572)

## Summary

Fixes two customer-reported failures when running the Synapse profiler
on a serverless-only workspace.

### 1. `workspace_info` fails when there are no dedicated SQL pools

**Root cause:** When `list_sql_pools()` returns an empty list,
`pd.json_normalize([])` produces a DataFrame with zero rows and zero
columns. `save_to_duckdb` called `conn.register()` before checking for
column metadata, so DuckDB raised `Need a DataFrame with at least one
column` and aborted the entire `workspace_info` step — even though an
empty pool list is a valid outcome.

**Fix:** Reorder `_save_overwrite` in `duckdb_helpers` to check for
zero-column DataFrames before registering with DuckDB. When no schema is
provided and the table already exists, truncate it (clearing stale data
on re-run); otherwise warn and skip table creation. This is a systemic
fix that also protects other workspace resources that may return empty
lists.

### 2. `serverless_sqlpool_extract` fails on `serverless_routines`

**Root cause:** PR #2289 switched serverless routines extraction to
query `sys.objects` via `list_serverless_routines()` (7 columns) because
`information_schema.routines` is not supported on serverless pools. When
pinned schemas were added in #2431, `serverless_routines` was copied
from `dedicated_routines` (23 columns, matching
`information_schema.routines`). The resulting column count mismatch
caused `Binder Error: table serverless_routines has 23 columns but 7
values were supplied`.

**Fix:** Update `SYNAPSE_SCHEMAS["serverless_routines"]` to match the 7
columns returned by `list_serverless_routines()`: `ROUTINE_SCHEMA`,
`ROUTINE_NAME`, `ROUTINE_TYPE`, `CREATED`, `LAST_ALTERED`,
`ROUTINE_DEFINITION`, `POOL_NAME`.

## Test plan

- [x] `uv run pytest tests/unit/assessment/test_duckdb_helpers.py
tests/unit/assessment/test_synapse_schemas.py`
- [x] New tests cover zero-column overwrite (skip + truncate), empty
schema-backed tables, and serverless routines schema alignment with
insert
…m `QUERY_TEXT` (#2567)

### Summary 
This pull request addresses the massive Snowflake profiler extract sizes
on high-volume accounts by decoupling metrics tracking from raw SQL text
query history. We preserve high-value metrics while drastically reducing
the bloat.

### Key Changes
- **Omit `QUERY_TEXT` from full-window `query_history`.** Prevents
multi-gigabyte SQL text extraction while keeping the exact `QUERY_TYPE`
and performance metrics.
- **Introduce `query_samples`.** Extracts a random sample of up to
10,000 rows containing `QUERY_TEXT` and `QUERY_ID` (to join back to
`query_history` for downstream migration complexity analysis).
- **Drop unused `user_activity`.** Removed the `LOGIN_HISTORY`dump,
which is no longer used by `lakebridge_profilers` or GVP inputs and was
contributing millions of rows of secondary bloat.

### Measured Impact
On a sample ~20M-row extract, omitting the raw SQL text yielded dramatic
storage and network transfer savings:

Metric | Before | After | Change
-- | -- | -- | --
Extract Size | ~28 GiB | ~1 GiB | ~96% reduction (28× smaller)

**Note.** Workload spend-mix classification relies on `QUERY_TYPE`, not
`QUERY_TEXT`, meaning downstream classification accuracy remains
unaffected.

### Follow-Ups
- [ ] Updated `lakebridge-profilers`' `migration_complexity.sql` to read
from `query_samples` instead of `query_history.QUERY_TEXT`.

### Tests
- [ ] Execute `execute-database-profiler` on Snowflake staging/test
accounts and verified:
        - [ ] `query_history` no longer contains `query_text`
- [ ] `query_samples` is limited to $\le$ 10k rows and contains the
text.
        - [ ] `user_activity` table is completely gone.
- [ ] Confirm that `lakebridge-profilers` (after changes) spend-mix path
still functions correctly using `QUERY_TYPE` from `query_history`.

Closes #2532
Bump version to 0.14.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.