---
title: "Working with Semantic Models (DAX queries)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Working with Semantic Models (DAX queries)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE)
```

A semantic model is the report-ready data behind Power BI and Fabric reports.
It combines tables with relationships, measures, and business calculations.
From R, you can query that model with DAX and refresh it after its source data
changes.

Start by querying a small result. Refresh and monitoring come next, followed by
the more specialized controls used for production models.

This guide uses a discovered `FabricSemanticModel` R6 object. Read its service
fields directly. `$dax_query()` corresponds to `fabric_pbi_dax_query()`, while
the `$refresh*()` methods correspond to the `fabric_pbi_refresh*()` function
family:

```{r, eval = FALSE}
library(fabricQueryR)

model <- fabric_semantic_models("Analytics workspace")[[1]]
```

## Query the model with DAX

DAX is the query and calculation language used by semantic models. Use
`$dax_query()` (`fabric_pbi_dax_query()`) to run a query. It
normally begins with `EVALUATE` and returns a table. If you are new to DAX,
create and test a query in the model's *DAX query view* in Fabric, then use the
same text from R:

```{r, eval = FALSE}
rows <- model$dax_query(
  dax = "EVALUATE TOPN(100, 'Customers')"
)

head(rows)
```

Query the semantic model when you want its measures and relationships, or need
the same business definition used by a report. Query the Warehouse or
Lakehouse instead when you need the underlying source rows and SQL is a better
fit.

For example, call `$dax_query()` (`fabric_pbi_dax_query()`) to group a DAX
measure by a model column:

```{r, eval = FALSE}
sales <- model$dax_query(
  dax = paste(
    "EVALUATE",
    "SUMMARIZECOLUMNS(",
    "  'Region'[Region],",
    "  \"Total Sales\", [Total Sales]",
    ")"
  )
)
```

The normal JSON mode returns one tibble and is the simplest option. The later
`api = "arrow"` mode is useful for richer types, multiple result tables, or a
stream, but requires a capacity-backed model and additional tenant settings.
The `timeout` argument bounds the client HTTP request. For Arrow queries,
`arrow_options$queryTimeout` separately controls the server-side query limit.

## Check access before refreshing

Querying normally requires Read and Build permission on the model and the
tenant setting that enables Execute Queries. Refreshing requires Write access.
If a query succeeds but a refresh does not, ask the model owner or Fabric
administrator to check those permissions and tenant settings.

## Refresh after an upstream update

A standard refresh starts with `$refresh()` (`fabric_pbi_refresh()`) and waits
with `$refresh_wait()` (`fabric_pbi_refresh_wait()`):

```{r, eval = FALSE}
refresh <- model$refresh()
completed <- model$refresh_wait(refresh, timeout = 1800)

completed$state
completed$start_time
completed$end_time
```

Pass the returned handle to status, wait, or cancel functions without copying
IDs. If you save it and restore it in another R process, authenticate again.

For example, a data-load workflow can use the Lakehouse `$write_table()` method
(`fabric_lakehouse_write_table()`) and refresh only after the source update has
committed successfully. Here `lakehouse` is a discovered Lakehouse containing
the model's source table, and `new_sales` is the data frame to load:

```{r, eval = FALSE}
load <- lakehouse$write_table(
  table = "Sales",
  data = new_sales,
  schema = "dbo",
  mode = "overwrite"
)

refresh <- model$refresh(mode = "enhanced", type = "Full")
completed <- model$refresh_wait(
  refresh,
  timeout = 1800,
  cancel_on_timeout = TRUE
)
```

Client-side `timeout` in `$refresh_wait()` (`fabric_pbi_refresh_wait()`) only
bounds how long R waits. Without `cancel_on_timeout = TRUE`, the Power BI
refresh keeps running.

Use `type = "Full"` to re-import updated source data and recalculate the model.
Enhanced refresh defaults to `"Automatic"`, which processes partitions whose
state is not Ready; a previously processed Import model can remain unchanged
after its source data changes. See Microsoft's
[processing-type definitions](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/refresh-dataset-in-group#datasetrefreshtype).

## Use enhanced refresh controls

Enhanced refresh through `$refresh()` (`fabric_pbi_refresh()`) requires Power BI
Premium, Premium per user, Embedded, or Fabric capacity. Supplying an enhanced
option selects enhanced mode automatically, or set `mode = "enhanced"`
explicitly:

```{r, eval = FALSE}
refresh <- model$refresh(
  mode = "enhanced",
  type = "Full",
  objects = list(
    list(table = "Sales", partition = "2026"),
    list(table = "Calendar")
  ),
  commit_mode = "Transactional",
  max_parallelism = 4L,
  retry_count = 1L,
  timeout = "02:00:00"
)

completed <- model$refresh_wait(refresh, timeout = 5 * 60 * 60)
```

The submission `timeout` controls an individual Power BI attempt; the numeric
timeout in `$refresh_wait()` (`fabric_pbi_refresh_wait()`) controls how long R
waits. Use `"Transactional"` when the previous model should remain available
unless the complete refresh succeeds. See `?fabric_pbi_refresh` before using
partial-batch commits or refresh-policy options.

## Diagnose attempts and failures

Use `$refresh_status()` (`fabric_pbi_refresh_status()`) when a refresh takes
longer than expected or fails:

```{r, eval = FALSE}
status <- model$refresh_status(refresh)

status$state
status$attempts
status$messages
status$service_error
status$objects
status$details_url
```

The normalized state distinguishes work that is queued, running, completed,
completed with warnings, failed, timed out, cancelled, or disabled. Attempt
details and `details_url` help diagnose the service response; the complete
response remains available in `raw`.

By default, wait raises typed R conditions for service failures, cancellation,
service timeouts, and disabled refreshes. Keep the terminal detail instead when
building a monitoring table by calling `$refresh_wait()`
(`fabric_pbi_refresh_wait()`) with `error_on_failure = FALSE`:

```{r, eval = FALSE}
result <- model$refresh_wait(
  refresh,
  error_on_failure = FALSE
)

if (result$state != "Completed") result$details_url
```

## Inspect history

Inspect recent refreshes with `$refresh_history()`
(`fabric_pbi_refresh_history()`) when you need to compare duration or failure
patterns:

```{r, eval = FALSE}
history <- model$refresh_history(top = 10L)

history[[1]]$refresh_type
history[[1]]$state
history[[1]]$attempts

# Refresh an old history entry from its request ID and stored model context
latest <- model$refresh_status(history[[1]])
```

Here `$refresh_status()` calls `fabric_pbi_refresh_status()`, just as it does
for a newly submitted refresh.

## More information

See Microsoft's documentation for more information:
- [Refresh Dataset API](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/refresh-dataset-in-group)
- [Enhanced refresh](https://learn.microsoft.com/en-us/power-bi/connect-data/asynchronous-refresh)
- [Refresh execution details](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/get-refresh-execution-details-in-group)
- [Data refresh and capacity limits](https://learn.microsoft.com/en-us/power-bi/connect-data/refresh-data)
- [How Direct Lake works](https://learn.microsoft.com/en-us/fabric/fundamentals/direct-lake-how-it-works)
