---
title: "Migrating SQL between dialects"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Migrating SQL between dialects}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```

```{r setup}
library(polyglotSQL)
```

This vignette walks through a realistic migration: a small collection of
queries written for one engine that must run on another.

## Typical rewrites

Function names, quoting and syntax differ per engine. polyglotSQL rewrites
them structurally:

```{r}
# MySQL constructs -> PostgreSQL
sql_transpile(
  "SELECT IFNULL(a, b), DATE_FORMAT(d, '%Y-%m-%d') FROM t LIMIT 5",
  from = "mysql", to = "postgres"
)

# T-SQL pagination and date functions -> PostgreSQL
sql_transpile(
  "SELECT TOP 10 name, GETDATE() AS now FROM users ORDER BY name",
  from = "tsql", to = "postgres"
)

# BigQuery -> Snowflake: quoting and safe casts
sql_transpile(
  "SELECT `user id`, SAFE_CAST(x AS INT64) FROM `proj.dataset.tbl`",
  from = "bigquery", to = "snowflake"
)
```

## Migrating a batch of queries

Because inputs and outputs are plain character vectors, migrating a whole
directory is a `vapply()`:

```{r}
queries <- c(
  orders  = "SELECT IFNULL(status, 'unknown') AS status FROM orders",
  daily   = "SELECT DATE(created_at) AS d, COUNT(*) FROM events GROUP BY DATE(created_at)",
  users   = "SELECT id, CONCAT(first, ' ', last) AS full_name FROM users"
)

vapply(queries, sql_transpile, character(1),
       from = "mysql", to = "duckdb")
```

## Handling unsupported constructs

With the default `unsupported = "raise"`, polyglotSQL refuses to emit SQL
when the target dialect cannot express a construct, raising a
`polyglot_transpile_error`. For an inventory pass you may prefer to collect
failures:

```{r}
migrate <- function(sql, from, to) {
  tryCatch(
    list(ok = TRUE, sql = sql_transpile(sql, from = from, to = to)),
    polyglot_error = function(e) list(ok = FALSE, error = conditionMessage(e))
  )
}
migrate("SELECT IFNULL(a, b) FROM t", "mysql", "postgres")
```

`unsupported = "warn"` or `"ignore"` instead return the closest supported
translation — useful for a first draft that a human reviews.

## Verifying the migration structurally

`sql_diff()` shows what actually changed between two statements, which is
handy when reviewing rewrites:

```{r}
sql_diff(
  "SELECT a FROM t",
  "SELECT a, b FROM t WHERE a > 1"
)
```

And `sql_validate()` confirms the output parses in the target dialect:

```{r}
out <- sql_transpile("SELECT IFNULL(a, b) FROM t", from = "mysql", to = "postgres")
sql_validate(out, dialect = "postgres")$valid
```

## What transpilation cannot do

Transpilation operates on *syntax*. It cannot:

* emulate engine-specific semantics (collations, implicit casts, `NULL`
  ordering, integer vs. float division, timezone behavior);
* create missing functions on the target engine;
* guarantee identical performance characteristics.

**Run the translated queries against the target database — ideally with
result comparisons — before trusting them in production.**
