Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ADBC Driver for Power BI Semantic Models

An ADBC driver for querying Power BI semantic models with DAX via the Execute DAX Queries REST API, which returns results natively as Apache Arrow IPC streams. Because the wire format is already Arrow, the driver is a thin streaming adapter: record batches flow from the Power BI service to your DataFrame with no re-serialization.

Built on driverbase-go.

⚠️ Preview — not for production. This driver targets Fabric's new native Arrow IPC Execute DAX Queries REST API, which is still in preview. That upstream API can change or break without notice, so don't depend on this in production yet.

Requirements (service side)

  • Semantic model on a Premium, Fabric, or Embedded capacity (not Pro-only)
  • Tenant settings enabled: Dataset Execute Queries REST API and Allow XMLA endpoints; Allow service principals to use Power BI APIs if using a service principal
  • Caller has Read + Build permission on the semantic model
  • Rate limit: 120 query requests/minute/user — the driver retries 429s with exponential backoff + jitter (respecting Retry-After)

Capabilities

Queries. ExecuteQuery runs DAX — SetSqlQuery carries the query text verbatim (the same way the BigQuery driver carries GoogleSQL) — and returns a streaming Arrow RecordReader. ExecuteSchema returns the result schema without materializing rows (via the endpoint's schemaOnly). Prepare is accepted as a no-op (tools often call it before executing).

Metadata discovery. GetObjects (catalog → schema → table → column, with depth control and LIKE-pattern filters), GetTableSchema, GetTableTypes (TABLE, HIDDEN TABLE), and GetInfo. These are built on INFO.VIEW.TABLES/COLUMNS/MEASURES, so an agent can explore a model before writing DAX. Catalog = the dataset (semantic model) GUID; schema = model.

Result handling. Fully streaming (memory bounded by one record batch), transparent LZ4 decompression, dictionary-encoding passthrough or decode, error rowsets (IsError in schema metadata) surfaced as adbc.Error with the service FaultCode/FaultString, multi_result modes (first, error), and automatic 429 retry with backoff.

Authentication. default, azure_cli*, device_code, interactive_browser, client_secret, client_certificate, static_token; plus impersonated_user for RLS. (*azure_cli cannot reach the Arrow endpoint — see Status.)

Not supported — by design, because the Execute DAX Queries endpoint is a read-only query API:

  • Writes / bulk ingest (ExecuteUpdate returns NotImplemented)
  • Bound query parameters (Bind)
  • Transactions (autocommit only)
  • Substrait (InfoVendorSubstrait = false)
  • ExecutePartitions (reserved; multi_result=partitions errors today)
  • DAX Variant columns (dense_union): an optional cast to utf8 is not yet implemented

Repository layout

powerbi-adbc/
├── go/                          # the Go module (github.com/crazy-treyn/powerbi-adbc-driver)
│   ├── powerbi.go               # NewDriver, option constants, DriverInfo
│   ├── database.go              # option validation, credential construction, Open
│   ├── connection.go            # statements, catalog/schema surface, error mapping
│   ├── metadata.go              # GetObjects via INFO.VIEW.{TABLES,COLUMNS,MEASURES}
│   ├── statement.go             # DAX passthrough, request params, ExecuteQuery/Schema
│   ├── driver_test.go           # end-to-end suite against a mock service
│   ├── internal/
│   │   ├── auth/auth.go         # Entra ID credential chain (azidentity)
│   │   └── client/
│   │       ├── client.go        # POST executeDaxQueries, retry, error mapping
│   │       ├── stream.go        # concatenated-IPC splitter, error-rowset
│   │       │                    #   detection, dictionary decoding
│   │       └── *_test.go
│   ├── manifest.toml            # driver metadata (name/publisher/license/[ADBC])
│   ├── pkg/                     # generated C ABI wrapper — see Building
│   └── validation/              # live-tenant pytest suite (skips without creds)
├── examples/powerbi.toml        # ADBC driver manifest for local install
├── examples/profile-mymodel.toml# ADBC connection profile example
└── examples/mock_smoke_test.py  # verify the built shared lib via Python + mock service

Conventions: Apache-2.0 headers on every file (rat-check compatible), driverbase-go for all ADBC boilerplate, manifest.toml metadata, options under the powerbi.* namespace, errors raised through driverbase.ErrorHelper with proper adbc.Status codes, and a C-ABI pkg/ generated with driverbase-go's ffitemplate rather than written by hand.

Connection options

Option Notes
powerbi.dataset_id Required. Semantic model GUID
powerbi.workspace_id Workspace GUID; omit for My Workspace
powerbi.auth_type default | azure_cli | device_code | interactive_browser | client_secret | client_certificate | static_token. Note: azure_cli cannot reach the Arrow executeDaxQueries endpoint (see Status). Use client_secret (service principal) or device_code/interactive_browser with an app registration granted delegated Dataset.Read.All.
powerbi.tenant_id / client_id / client_secret / client_certificate_path Per flow
powerbi.access_token Pre-acquired bearer token (static_token)
powerbi.endpoint Default https://api.powerbi.com; override for sovereign clouds
powerbi.impersonated_user effectiveUserName for RLS (not supported with service principals upstream)
powerbi.strings.dictionary_encoded true (default) passes dictionary-encoded utf8 through; false decodes

Statement options: powerbi.statement.query_timeout_seconds, .resultset_rowcount_limit, .memory_limit_mb, .multi_result (first|error|partitions). .execution_metrics is reserved and currently rejected when enabled until metrics rowsets are validated against the live service.

Design decisions worth knowing

  • The "SQL" dialect is DAX. SetSqlQuery carries DAX text verbatim, the same way the BigQuery driver carries GoogleSQL. InfoVendorSql is reported false.
  • Errors hide inside HTTP 200. The service returns query failures as an error rowset (IsError=true in Arrow schema metadata) possibly after data rowsets in the same body. The stream reader checks metadata on every embedded IPC stream and surfaces failures as adbc.Error with the service FaultCode/FaultString.
  • One body, many IPC streams. One self-contained stream per EVALUATE. Default mode surfaces the first data rowset and scans trailing rowsets for errors when the first rowset is fully consumed; early Release() closes the response without draining. multi_result=error rejects additional data rowsets; partitions is reserved for ExecutePartitions.
  • Fully streaming. Batches are surfaced as they arrive over chunked transfer; memory use is bounded by one record batch. LZ4 buffer compression is decoded by arrow-go's IPC reader transparently.
  • Metadata via INFO functions. DMVs aren't supported by the endpoint, but INFO.VIEW.TABLES() / INFO.VIEW.COLUMNS() / INFO.VIEW.MEASURES() are — GetObjects is built on them, which is what lets an AI agent discover the model before writing DAX. GetTableSchema uses schemaOnly=true with EVALUATE TOPN(0, 'Table').

Building

The shared library's file extension is a per-platform convention you choose via -o (Go builds the same c-shared artifact either way):

Platform Build output (use with -o)
Linux libadbc_driver_powerbi.so
macOS libadbc_driver_powerbi.dylib
Windows adbc_driver_powerbi.dll

Substitute your platform's name for <lib> below.

cd go
go build ./...
go test ./...

# Build the shared library (the generated C ABI wrapper in pkg/ is checked in).
# Linux example — swap the -o name per the table above:
go build -tags driverlib -buildmode=c-shared -o <lib> ./pkg

# Verify the shared library end to end (no tenant needed):
pip install adbc-driver-manager pyarrow
python ../examples/mock_smoke_test.py <lib>

Note: go mod tidy currently fails on a broken test-only dependency declared by upstream driverbase-go (testutil pseudo-version); use go get ./... / go get -t ./... to manage dependencies until that is fixed upstream.

To regenerate pkg/ after a driverbase-go upgrade, run driverbase-go's ffitemplate from a checkout (it needs goimports and clang-format on PATH; the tool cannot be go run by module path today because its declared module path diverged from its directory):

git clone https://github.com/adbc-drivers/driverbase-go /tmp/driverbase-go
(cd /tmp/driverbase-go/ffitemplate && go run . -prefix PowerBI -driver /path/to/this/repo/go)

Install locally with an ADBC driver manifest so every driver manager finds it by name (see examples/powerbi.toml; drop it in ~/.config/adbc/drivers/ on Linux, ~/Library/Application Support/ADBC/drivers/ on macOS, or a directory on ADBC_DRIVER_PATH).

Using it from AI agents / CLIs

Python (what most agent tools shell into):

import adbc_driver_manager.dbapi as adbc

with adbc.connect(
    driver="powerbi",
    db_kwargs={
        "powerbi.workspace_id": "WORKSPACE_GUID",
        "powerbi.dataset_id": "DATASET_GUID",
        # Service principal added to the workspace (azure_cli does NOT work
        # against the Arrow endpoint — see Status).
        "powerbi.auth_type": "client_secret",
        "powerbi.tenant_id": "TENANT_GUID",
        "powerbi.client_id": "APP_CLIENT_ID",
        "powerbi.client_secret": "APP_CLIENT_SECRET",
    },
) as con, con.cursor() as cur:
    cur.execute("EVALUATE TOPN(100, 'Sales')")
    print(cur.fetch_arrow_table().to_pandas())

Or keep credentials out of code with a connection profile (examples/profile-mymodel.toml), then from DuckDB:

INSTALL adbc FROM 'https://columnar-tech.github.io/duckdb-adbc-client';
LOAD adbc;
SELECT * FROM read_adbc('profile://mymodel', 'EVALUATE INFO.VIEW.MEASURES()');

Getting started with databow

databow is a small CLI for querying any ADBC driver. Build the shared library and point a connection profile at it — no manifest required.

Prerequisites: Go (to build), uv, and a Power BI service principal with Read+Build on a model hosted on Premium/Fabric capacity.

# 1. Build the driver. Name the -o output per your platform (see Building):
#    Linux .so · macOS .dylib · Windows .dll
cd go && go build -tags driverlib -buildmode=c-shared \
    -o libadbc_driver_powerbi.dylib ./pkg          # macOS example

# 2. Install the CLI
uv tool install databow
  1. Create one file, powerbi.toml — the profile is the only config you need. Its driver key can be a driver name (needs an installed manifest) or, as here, a direct path to the shared library you built in step 1:

    profile_version = 1
    # point at the .so / .dylib / .dll you built above
    driver = "/absolute/path/to/go/libadbc_driver_powerbi.dylib"
    
    [Options]
    "powerbi.auth_type"     = "client_secret"
    "powerbi.workspace_id"  = "WORKSPACE_GUID"
    "powerbi.dataset_id"    = "DATASET_GUID"
    "powerbi.tenant_id"     = "TENANT_GUID"
    "powerbi.client_id"     = "APP_CLIENT_ID"
    "powerbi.client_secret" = "APP_CLIENT_SECRET"
  2. Query (the query string is DAX, always starting with EVALUATE):

    databow --profile powerbi.toml --query "EVALUATE INFO.VIEW.TABLES()"
    databow --profile powerbi.toml --query "EVALUATE TOPN(10, 'YourTable')"
    databow --profile powerbi.toml --file report.dax        # from a file
    databow --profile powerbi.toml --query "EVALUATE 'Sales'" --output out.csv
    databow --profile powerbi.toml                          # interactive shell

A driver manifest (examples/powerbi.toml in ~/Library/Application Support/ADBC/drivers/ on macOS) is only needed if you prefer to reference the driver by name — databow --driver powerbi or driver = "powerbi" in the profile — instead of by path.

Status

v0.1. The stream splitter, options surface, auth chain, statement lifecycle, metadata enumeration (GetObjects via INFO.VIEW.*, measures included), and dictionary decoding are all implemented, with unit tests plus an end-to-end suite (Go and Python) against a mock service. Validated against a live Fabric (F64) tenant (2026-07-11): metadata discovery, DAX queries, multi-batch streaming (500k rows), dictionary encoding, schema-only, and the error-rowset path all confirmed — see go/validation/. ExecutePartitions (multi_result=partitions) is reserved for v0.2.

Auth caveat. auth_type=azure_cli does not work against the executeDaxQueries (Arrow) endpoint — the Azure CLI can only mint a user_impersonation token, which the endpoint rejects (HTTP 401), and Azure AD refuses to grant the CLI app the Dataset.Read.All scope. Use a service principal (client_secret) or an app registration with delegated Dataset.Read.All. See Connection options.

License

Apache-2.0. Not affiliated with Microsoft.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages