{ "cells": [ { "cell_type": "markdown", "id": "1c8dffbd", "metadata": {}, "source": [ "# Notebook 2 - directory conversion, options, and dependencies\n", "\n", "This walkthrough mirrors `examples/03_convert_directory.py` and `examples/04_custom_options.py`. It converts the complete sample warehouse, inspects dependency rewrites, and compares conservative defaults with opt-in migration options.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "5ed382dd", "metadata": { "execution": { "iopub.execute_input": "2026-07-12T14:42:56.174563Z", "iopub.status.busy": "2026-07-12T14:42:56.174283Z", "iopub.status.idle": "2026-07-12T14:42:57.191945Z", "shell.execute_reply": "2026-07-12T14:42:57.189551Z" } }, "outputs": [], "source": [ "from collections import Counter\n", "from pathlib import Path\n", "\n", "import matplotlib.pyplot as plt\n", "\n", "from sql2sqlx import ConversionOptions, Layout, MergeStrategy, convert_directory, convert_string\n", "\n", "ROOT = next(\n", " path for path in [Path.cwd(), *Path.cwd().parents] if (path / \"pyproject.toml\").exists()\n", ")\n", "EXAMPLES = ROOT / \"examples\"\n", "assert (EXAMPLES / \"sql\").is_dir()" ] }, { "cell_type": "markdown", "id": "64a12877", "metadata": {}, "source": [ "## Convert the sample tree\n", "\n", "Passing no output directory keeps this notebook side-effect-free: files are returned in memory while preserving the relative paths that would be written under a Dataform `definitions/` directory.\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "65b5a4a0", "metadata": { "execution": { "iopub.execute_input": "2026-07-12T14:42:57.196546Z", "iopub.status.busy": "2026-07-12T14:42:57.195886Z", "iopub.status.idle": "2026-07-12T14:42:57.503464Z", "shell.execute_reply": "2026-07-12T14:42:57.496365Z" } }, "outputs": [ { "data": { "text/plain": [ "([('maintenance/backfill_script.sqlx', 'operations', 'backfill_script'),\n", " ('maintenance/customer_attrs_merge.sqlx',\n", " 'operations',\n", " 'customer_attrs_merge'),\n", " ('maintenance/customers_update.sqlx', 'operations', 'customers_update'),\n", " ('maintenance/orders_delete.sqlx', 'operations', 'orders_delete'),\n", " ('marts/customer_orders.sqlx', 'view', 'customer_orders'),\n", " ('marts/order_facts_insert.sqlx', 'operations', 'order_facts_insert'),\n", " ('sources/events.sqlx', 'operations', 'events'),\n", " ('staging/customers.sqlx', 'table', 'customers'),\n", " ('staging/orders.sqlx', 'table', 'orders')],\n", " 9)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result = convert_directory(str(EXAMPLES / \"sql\"))\n", "summary = [(file.relpath, file.action_type.value, file.action_name) for file in result.files]\n", "summary, len(summary)" ] }, { "cell_type": "markdown", "id": "f45599f9", "metadata": {}, "source": [ "## Confirm dependency-aware rewrites\n", "\n", "The converter scans every statement, records produced targets, and rewrites reads of those targets to `${ref(...)}`. It also adds explicit `dependencies` to operations when order matters.\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "ff53ef43", "metadata": { "execution": { "iopub.execute_input": "2026-07-12T14:42:57.510503Z", "iopub.status.busy": "2026-07-12T14:42:57.509907Z", "iopub.status.idle": "2026-07-12T14:42:57.522374Z", "shell.execute_reply": "2026-07-12T14:42:57.520859Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "===== maintenance/backfill_script.sqlx =====\n", "FROM ${ref(\"staging\", \"orders\")}\n", "===== maintenance/customer_attrs_merge.sqlx =====\n", " dependencies: [\"customers_update\"]\n", " SELECT customer_id, last_seen FROM ${ref(\"staging\", \"customers\")}\n", "===== marts/customer_orders.sqlx =====\n", " dependencies: [\"customers_update\", \"orders_delete\"]\n", "FROM ${ref(\"staging\", \"customers\")} c\n", "LEFT JOIN ${ref(\"staging\", \"orders\")} o USING (customer_id)\n", "===== marts/order_facts_insert.sqlx =====\n", " dependencies: [\"backfill_script\", \"orders_delete\"]\n", "FROM ${ref(\"staging\", \"orders\")}\n", "===== staging/customers.sqlx =====\n", " dependencies: [\"customers_update\"]\n", "FROM ${ref(\"raw\", \"events\")}\n", "===== staging/orders.sqlx =====\n", " dependencies: [\"orders_delete\"],\n", "FROM ${ref(\"raw\", \"events\")}\n" ] } ], "source": [ "rewritten = [file for file in result.files if \"${ref(\" in file.content]\n", "for file in rewritten:\n", " print(f\"===== {file.relpath} =====\")\n", " lines = [\n", " line for line in file.content.splitlines() if \"${ref(\" in line or \"dependencies:\" in line\n", " ]\n", " print(\"\\n\".join(lines))\n", "\n", "assert result.report.refs_rewritten >= 1\n", "assert rewritten" ] }, { "cell_type": "markdown", "id": "48f4a612", "metadata": {}, "source": [ "## Visualize the action mix\n", "\n", "A directory migration should produce a plausible distribution of Dataform action types. Unexpected spikes in `operations` often identify scripts that require deeper human review.\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "9faa5ec5", "metadata": { "execution": { "iopub.execute_input": "2026-07-12T14:42:57.525927Z", "iopub.status.busy": "2026-07-12T14:42:57.525479Z", "iopub.status.idle": "2026-07-12T14:42:57.799975Z", "shell.execute_reply": "2026-07-12T14:42:57.798688Z" } }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlkAAAFVCAYAAADc0vqhAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAARZlJREFUeJzt3XdUFdf7NfBNkYtIEUUREMSGWLBhARFFjYqxETs2bAmWxJh8TYwxRo1GTTGWaKKxxY41olgj9oJd1KgRKyJRLHTk0p73D1/mlxuKqIxXdH/Wci3vmTkzzxkG2MycO9dARAREREREVKgM9V0AERER0ZuIIYuIiIhIBQxZRERERCpgyCIiIiJSAUMWERERkQoYsoiIiIhUwJBFREREpAKGLCIiIiIVMGTRG2Pt2rX4888/9V0GPafX6esWEhKCTZs2FWhdrVaL3bt3Y9GiRVizZo3KlVFBnDp1CvPnz0dRf8b2mzIOYsiiV+Tvv//G/PnzsWvXrpfazqpVq7Bnz55cl02YMAELFix4qe2/qPzqovzl9nXL73iqeaxnzZqFqVOnPnO9xMRE1KlTB6NGjcLx48dx+fJlVeqh5xMSEoJhw4YhMzNT9X2tWbMGR48eVWXbr3IcpC5jfRdAb4eJEyciKCgI1tbWiI6Ohqmp6QttZ9y4cfDw8MA777yTY1mvXr1gb2//sqW+kPzqovzl9nXL73i+Dsf6999/x99//42YmBiUKVNGb3WQfsTExKBv375YvXq1vkuh1xxDFqnu0aNH+OOPP+Dv7481a9Zg48aN6NOnT6HvZ+LEiYW+TVJfUfy6RUREwNzcnAHrLRUSEgIjIyO0a9dO36XQa44hi1S3fPlyZGVlYebMmYiKisLChQvzDVl//fUXwsPDYWxsjIYNG6JixYoAgPnz5yMxMRHXr1/H/PnzAQA2Njbo1q0bgKdze0qVKoXWrVvrbC8zMxNHjhzB9evXYW5ujqZNm8LOzi5HjY6OjmjRogUuXryI06dPo0yZMnjnnXdgYmKS7/jyq2v16tUwNzdHp06dcvQ7ePAgrly5giFDhsDQ0FCnhrNnz+L8+fOws7NDixYtUKxYsVz3ff78eYSHhwMAGjZsCFdX11yP519//QURQa1atVCzZs18x/M82/93zefOncP58+fh4uICDw8PZZ3w8HCEh4fDwcEBLVu2hIGBgc42/vt1y+94PusceJ7jkpKSgtDQUMTGxqJx48aoVq3aM49HWloalixZgjNnziAzM1OpwdPTE3Xq1NE5Hn/99RdOnToFV1dXNG7cGADw+PFjHDx4ELGxsShfvjy8vb11rurevXsXW7duRceOHVG2bFn8+eefiI2NhY+PDxwcHAAA6enp2LNnD2JiYuDl5YUqVao8s+5sqampOHToEKKiolCyZEn4+PjA2toaABAXF4egoCC4uLigZcuWOv1CQkIQHR2NgIAAaDQaXLp0CQcPHgQAGBgYwNzcHHXr1s1xbh09ehSXLl3CkCFD8ODBA4SGhkKj0aBdu3bKuB88eIC9e/cCANq2bYuSJUvmuY2YmBhl3ZYtW6Js2bIFHntMTAwOHz6MuLg4VKxYEd7e3jA21v0V+PDhQ4SFheHRo0eoVKkSGjVqBI1Gk2NbwcHBaNGiBSwtLZ+7b1hYGK5cuaKcJ1evXsX+/fvRu3dvne3RG0KIVFazZk3p1auXiIisWbNGAMjVq1dzrBcdHS0tW7YUMzMzadu2rfTq1UuqVq0qo0ePFhGRwMBAMTc3l8qVK0tgYKAEBgbKlClTlP7VqlWTrl276mzz8uXL4urqKmXLlpUePXpI06ZNpVixYjJhwgSd9WxtbSUgIEC+/PJL8fb2lp49e4q1tbXUqlVL4uPj8x1ffnWNGDFCTE1N5eHDhzp9MjMzpUKFCtKyZcscNXz00Ufi7e0tPXr0EBsbG6levbrcvn1bp/+9e/ekRYsWYmFhIZ06dRI/Pz8pUaKEBAQESHp6uoiIpKeny3vvvScWFhbSpUsX6dOnj9SuXVt8fX3lyZMn+Y6pINv/d82jRo2Sli1bSteuXUWj0cjw4cOV8fv4+Ei3bt2kePHi4ufnl2Nf//265Xc8n3UOFLTuc+fOiYODgzg4OEivXr2kYcOGMm3aNGnVqpW4u7vneVxSUlIkMDBQXF1dxcTERKnhzz//1Dken3zyiTRt2lQ6duwoY8eOFRGR+fPni5mZmdStW1f8/f2lQoUK4uDgIIcPH1a2/+effwoAWblypXh7e0u3bt3Ew8NDihcvLqGhoRIVFSWenp7StWtXadKkiRgbG0tQUFC+X8ts27dvF1tbW6lSpYr07t1bPD09xcLCQtatW6esM3DgQDExMZETJ04obTt27BADAwP58ssvlbaDBw8qY3///ffl3XffFRMTE+nYsaNkZGQo63388cei0Whkz549Uq9ePfH39xcnJyepVq2aPHjwQHbs2CF16tQRf39/cXZ2lnLlysmtW7d06s7exu7du8XNzU38/f2lVq1aYmZmlmPsEyZMEAA6X2sRka+++kpMTEzEy8tL+vTpIxUrVhRXV1eJiIhQ1lm0aJGYmZlJs2bNZMCAAeLj4yOurq5y7NgxnW0lJydL8eLFZd68ec/VNyUlRTp27CimpqbSvn17ad++vbRu3VpmzZolAHRqyWscVPQwZJGqjhw5IgDk4MGDIiKSlpYmtra28vnnn+usl5WVJQ0aNBBHR0edAJaZmSkhISHK6woVKkjPnj1z3dd/f1mnpaVJlSpVpFq1ahITE6O0z5w5UwDI8uXLlTZbW1txcnKSX3/9VWm7ePGiGBoa6vwSz0tedf31118CQL7//nud9uDgYAEgq1at0qnBwcFB5syZo7TdvXtXnJycxMvLS2nLysoSDw8PcXJyksjISKX9woULUrx4cZk8ebKIiKxfv14AyNmzZ3X2vXv3bklKSspzLAXdfnbN5cuXl0WLFiltK1euFAAyYsQImT9/vtK+du1aASB79uzR2V9u4Ti/r3Neywpad1pamlSsWFHq168vcXFxynqfffaZ2NnZ5Ruysg0ePFisrKxytGcfj9mzZyttd+7ckYMHD4qBgYF8/PHHSntSUpI0a9ZMSpUqpYTw7JDl7u6uBOusrCxp166duLi4SO/eveXmzZvKNjp27CiOjo6SlpaWb70XLlwQjUYjAwcO1PnFPXHiRDExMZErV66IyNMgUKdOHalQoYI8evRIbt26JaVKlZKWLVvqhKfcXLp0SYoXL64z9o8//liMjY1l0KBBotVqRUTk4cOHUrJkSendu7f07NlTUlNTRUTk8ePHUqpUKRkyZIjOdrO30bt3b+WPg8zMTOnXr59oNJpnhpOffvpJAOgEspSUFGnWrJm4ublJZmampKWlSfHixWXMmDE6+46MjJRTp07ptG3evFkAyJ07d0RECtz3f//7nxgbG8vRo0eVtqNHj4qzszND1huMIYtUNWDAAKlVq5ZO27hx46Rs2bI6vxh2794tAGThwoX5bu95QtbWrVtzhCmRpz+gK1euLA0aNFDabG1tpWrVqpKVlaWzroeHh7Ro0SL/QT6jLh8fH6lcubLOtn19faVkyZI6V5RsbW3F2dlZMjMzdfpn/6V77tw5ERHZu3evAJDFixfn2NfgwYPF3t5eRJ5eOcktZD1LQbefXbOLi4vOOmlpaWJkZCSVKlXSGXNGRoaYmJjIF198obN+YYWsgtadfV5s2rRJZ53ExEQxNzd/6ZDl5OSU42vYs2dPKVGihCQkJOi0Z/8RMmvWLBH5v5A1adIknfWWL18uAGT8+PE67atXrxYAcvHixXzrHTRokJiYmOTYv1arFQsLC50/eiIiIsTKykp8fX2lYcOGYm9vL/fv38+xzbS0NDlw4ID8/vvvMn/+fPn111/FyclJ52v58ccfCwA5ffq0Tt8ePXoIADl+/LhOe+/evcXZ2VmnLXsb/w4nIk+vfBsYGOicT/8NJ1lZWWJrayutW7fOUX/2sT548KDExsYKgBxBKTcDBw7UOUcK0jcjI0MsLS2lR48eOZb16tWLIesNxjlZpJqEhASsX78eLVq0UOauZIuJicHWrVvRpUsXAMDp06cBQGcuz8u6cOECAMDd3V2n3dDQEPXq1cPWrVt12t3c3HLMF7K3t1e286JGjBiB7t27Y9euXfD19cWNGzewa9cuDB8+PMe7LGvXrg1DQ90nq9SrV08ZT506dXDy5EkAwO3bt7Fo0SLI0z+WADyd8xMdHY34+Hj4+flh8uTJ8PT0RIcOHdCiRQu0atXqmXOPCrp9KysrAECtWrV0+hcrVgw2NjaoVauWzvE0MjJCmTJlcPfu3ec6fgVV0Lqzv55169bV6W9ubo6qVau+dB1ubm45voYXLlyAi4sLLCwsdNrr168PAwODHOeYm5ubzuvsOYR5td+9ezffuXYnT56EjY0N1q5dCwDKsRERWFhY4NKlS8q6VapUweLFi9GtWzcYGxtj3759OeY+HT16FD169ICIoEmTJrC2toahoSHS0tJw//59nXUNDAxynCP5jSev8+O/Xy87OzuUK1cu3+/PO3fu4P79+zAyMlLOiezxP3z4EABw6dIleHt7o2/fvvjuu++wf/9+tGvXDj4+PmjSpInOfMisrCyEhIRg5MiRSlvJkiWf2TcyMhIJCQk5xgA8/f4OCgrKcwxUtDFkkWpWr14NU1NTODg44Ny5czrLGjZsiIULFyohKyMjAwCeOcn8eWQ/Yya3bZqYmOR4Bs1/fwECTwNDWlraS9Xh5+cHBwcH/Prrr/D19VUeMjh48OBc95dbrcD/HaPseq5fv57jF1rZsmURGBgIAwMD2Nra4tKlS1izZg1CQ0MxZcoUjBgxAq1bt8bGjRtzHe/zbD9bbtsxNjbOs/1lj2deClp39nHM61hnL39RNjY2OdoyMzNzPQ+NjY1haGj4zHMxe4J2Xu3POqZpaWnIysrCqVOncizr2LEjqlevrtOW/dyvrKwsPHnyJEef/v37o3z58jh48KDOuPbv35/jAZomJiY5xm5sbAwjIyMUL148R3t6enqO/RkYGMDIyChH+7O+XtnHJTY2NtexBwYGKsF6xYoV6N27t/JA2kmTJqF8+fJYvXo1mjZtCuBpuHzw4AE6d+6ss51n9X3WOUdvLoYsUs3ChQvRqVOnHFexAGD79u3o2LEjIiMj4eTkpPyQz/6LPy//vdKUn+x3XV25ciXHO7AuX778XO/Kepb86jI2Nsb777+PyZMn4+rVq1i6dCnq1q2rXKH6t6tXr+Zou3LlCoD/G0/2O+X69++PNm3a5FuXpaUlAgMDERgYCODpL4P+/fvj119/xeeff55rn+fZvlryO555LSto3dnH8erVqyhfvrzSnpmZiWvXrsHZ2fkFKs5flSpVEBYWhoyMDJ13tF29ehWZmZmFei7mxtXVFceOHcOvv/76zO+h0NBQTJgwASNHjsTZs2fRp08fnDlzRjlWcXFxuH79OgYMGKATEOLj43Hr1q1cQ+bLEhFcu3YNNWrUUNqSkpJw9+5ddOjQIc9+jo6OMDMzQ40aNXL9OfRf7dq1Ux7LcOPGDTRv3hwjR47EmTNnAABbtmxBxYoVc1yBe1ZfR0dHmJiY5Pv9TW8mPvGdVHH27FmcOXMGvr6+uS5v0aIFTExMsGTJEgBA+/bt4ejoiClTpiApKUln3WvXrin/L1u2LB4/flygGt59911YW1tjxowZOn/t7tq1C2fPnkW/fv2ed1h5elZdH3zwAQwNDdGlSxc8fPgQQ4YMyXW9mzdv6nzEjFarxc8//4xKlSrB09MTANChQwc4OTlh0qRJ0Gq1Ov1FRLl9cvXq1RzLs8NHcnJynrUWdPtqyu945rWsoHW3b98eVlZWmDlzJrKyspR1lixZotpVtj59+uDRo0dYvHixTvv06dNRrFgx9OrVS5X9Zhs+fDhiYmIwa9asHMsSEhJw+/ZtAEB0dDR69+4NT09PzJgxA0FBQTA0NETPnj2V7yErKytYWlrmeMr9lClTVLsqY2RkhF9//VWnbebMmcjIyEDfvn3z7KfRaDB48GCsWbMG58+fz7E8IiICT548QWxsLKKionSWVapUCVWqVNH5XgkODs5xFasgfU1NTdGlSxcEBQUhOjpaWS86OjrHtAV6s/BKFqli4cKFMDIyyvHMqmzFixdH8+bNsWTJEnz99dcwNTVFcHAwOnTogDp16qBHjx4oWbIkwsLCICLYvHkzAKBTp0745ptvMG7cODg6Oub6jKRslpaWWLVqFbp37w5vb2907twZ0dHRWLhwITp06IDPPvus0Mb7rLrs7Ozg5+eH9evXw9TUFL179851O35+fpg9ezZCQkJQrlw5bNy4EREREdi1a5dyu8TU1BQhISHo3LkzXF1d0bNnT9ja2uL27dsIDQ1FixYtMGfOHBw9ehRt27ZFmzZt4OLigtTUVKxZswbOzs744IMP8hxLQbevpvyOZ17LClq3paUlli5dip49e6JVq1Zo164drl27Bq1Wi0aNGiEuLq7Qx+Pv748DBw5g+PDhOH78OFxdXREaGoq9e/fit99+Q+XKlQt9n//Wpk0bzJ49G6NHj8auXbvg7e0NIyMjXLlyBfv27cPixYvh4OCAHj16AADWrVsHY2Nj2NvbY/Xq1WjTpg3GjBmDGTNmwMDAABMmTMDo0aNhZGSE2rVr4+DBg3ByckLt2rVVqd/Y2BjVq1dHhw4d0Lx5c4SHh2PVqlX46quvnjmP84cffkB0dDQaN26MPn36oEaNGoiNjcWZM2dw48YNHD9+HImJiWjZsiXc3NxQt25dWFpa4ujRozhy5AiWL18O4OkVp6tXr+b4CKiC9AWehsLTp0+jUaNGGDRoEEQEoaGhCAwMxIQJEwr/oNFrgSGLVGFpaYmvv/5aedBhbkaOHIktW7YgMjISzs7OqFevHq5evYqNGzciPDwciYmJGDx4MNq3b6/0GTt2LCpXroyTJ08iPDxc53ZPbh/P0q5dO0RERGDdunW4ceMGzM3NERwcnON2UkBAgM6tiGytW7cu0C/A/OrK1r9/f6xfvx5dunTJ87gYGRlh48aNWLlyJS5cuIBu3bqhX79+yoMos7m5ueHKlSvYsmULTp06hTt37qBy5coYNmyYMrF9wIABaN++PYKDg3HlyhWYmppi/PjxeO+99555xaEg28/vuPXr1y/X2759+vRBpUqVdNpy+7rldzzzW1bQut977z1cvHgRa9aswb1799CyZUt0794dc+fORUpKSr7HBgB8fHxyfXBkXscDePqQ1YCAAOzcuRNRUVFo2bIlfvnlF53zq3z58ggMDMxx/tjb2yMwMBBOTk467XZ2dggMDESFChWeWfPIkSPRpUsXbN68GdevX0eJEiXwzjvvYO7cuTA3N8fhw4dRq1YtTJ8+Xefr0bJlSyxatAjHjx/HP//8Azs7O3z66ado0KABdu/ejQcPHmDo0KF499138eOPP+rs08vLK9fP3/P09Mx1rpeHh4dya/u/hg8fjlq1amHbtm1wcHDAwYMH4e3trbNOgwYNEBgYqPPGA41Ggw0bNuD48ePYs2cPbt26BTs7O4wcORJt2rSBgYEBLC0t8ffff2P79u04ffo07t69C29vb8yePVs5FsHBwShVqpQyPyubk5PTM/sCQLly5XD27FksX75ceRhpcHAwgoODc4w1t3FQ0WQg/52lSESqmDhxIiZNmoTQ0NAcT9QGnv4Q9vX1xe+///7qiyN6TY0aNQrz589HamqqXuto0qQJqlSponN1qjAsWrQI77//PiIiIlSfm0evHq9kEb0CIoLVq1fD1dUVLVq00Hc5RPQcMjIyUK9ePVU+c5XebAxZRCpKTk7GihUrsH//fly7dg1btmx5rndIEpH+GRsbY968efoug4oghiwiFaWnp+PcuXNwdHTEvn370Lx58zzXzW8+D9HbKq95XW+K6tWrIzAwUHm4L71ZOCeLiIiISAV86wIRERGRChiyiIiIiFRQ5OdkZWVlITo6GhYWFpxQTERERKoSESQmJsLe3v6ZzzIr8iErOjoajo6O+i6DiIiI3iJ37tzJ9cHT/1bkQ1b2p9LfuXMn1ycwExERERWWhIQEODo6KvkjP0U+ZGXfIrS0tGTIIiIioleiIFOUOPGdiIiISAUMWUREREQqYMgiIiIiUoHeQ1ZSUhKmTp2Kd955B506dUJISIi+SyIiIiJ6aXqd+J6QkAAvLy9YWFhg7NixMDMzw7x582BjYwMPDw99lkZERET0UvQasiZOnIjY2FgcO3YM5ubmAIBWrVpBq9XqsywiIiKil6bX24WrVq1C3759lYCVTaPR6KkiIiIiosKhtytZDx8+RExMDFxcXDBq1CicPn0a9vb2CAgIwLvvvptnP61Wq3OlKyEh4VWUS0RERPRc9BaysoPS6NGjMXr0aHz77bc4fvw4OnfujEWLFiEgICDXftOmTcOkSZNeZanovdzvle6Pir7V/TfruwQiItIzAxERfew4JSUF5ubm8Pf3x6pVq5T2wYMH4/z58zh58mSu/XK7kuXo6Ij4+HjVnvjOkEXPiyGLiOjNlJCQACsrqwLlDr1dyTIzM0ONGjVQrlw5nXZbW1vExcXl2U+j0XDOFhEREb329DrxfejQoVi/fj2io6MBAPfv38fatWvRunVrfZZFRERE9NL0+giHESNGICIiAtWqVYOjoyNu376N9u3b4/vvv9dnWUREREQvTa8hy8DAALNnz8bEiRNx9+5dODo6wsrKSp8lERERERUKvYasbNbW1rC2ttZ3GURERESFRu+fXUhERET0JmLIIiIiIlIBQxYRERGRChiyiIiIiFTAkEVERESkAoYsIiIiIhUwZBERERGpgCGLiIiISAUMWUREREQqYMgiIiIiUgFDFhEREZEKGLKIiIiIVMCQRURERKQChiwiIiIiFTBkEREREamAIYuIiIhIBQxZRERERCpgyCIiIiJSAUMWERERkQoYsoiIiIhUwJBFREREpAKGLCIiIiIVMGQRERERqYAhi4iIiEgFxvrc+YMHDxAfH6/TptFo4OjoqKeKiIiIiAqHXkPW+PHjsXLlSpQrV05pc3Fxwfbt2/VYFREREdHL02vIAgBfX19s2LBB32UQERERFarXYk5WdHQ0kpOT9V0GERERUaHRe8jauHEj6tati1KlSqF+/foICwvTd0lEREREL02vIcvT0xOXLl1CTEwMYmNjUa9ePbRr1w53797Ns49Wq0VCQoLOPyIiIqLXjV5DVkBAAKpXrw4AMDMzwy+//IKMjAxs2rQpzz7Tpk2DlZWV8o/vRCQiIqLXkd5vF/6bRqNBuXLlcPv27TzXGTt2LOLj45V/d+7ceYUVEhERERWM3t5dKCLIysqCkZGR0hYVFYVbt26hSpUqefbTaDTQaDSvokQiIiKiF6a3K1np6elo3LgxVq9ejfDwcGzduhXt27dHxYoV0bdvX32VRURERFQo9BayTExMsHz5cuzZswcDBgzAzJkz4efnh9OnT8Pc3FxfZREREREVCr0+jLRGjRpYsmSJPksgIiIiUsVrNfGdiIiI6E3BkEVERESkAoYsIiIiIhUwZBER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XgAnvhdB69evxyeffILixYvjzp07qF27NoKDg2FnZ4f69eujRo0aiI2N1enTsWNHGBgY4NChQ+jevTuWLFmCgIAAHD9+HG5ubggLC0P58uURHBys9ClRogRKlCjxqodHRET0RuAliiLm5MmTCAgIwJo1axAREYGbN28iJSUF3bt3V9bp168fdu7cifT0dKXNzMwMFhYWSElJAQD06tULf//9N/r164caNWrgt99+w+7du2FjY/PKx0RERPQm4sT3IuaDDz7A3bt3lWdaAU/fRejh4YH9+/ejefPmiIqKQoUKFTB58mR8+eWXAIATJ06gadOmuHz5MipXrqyv8omIiN4avF1YxCQnJ+eYI9W4cWO4uLhg586daN68OcqXLw8fHx+MGzcOJ0+eRJkyZbBhwwZ89913DFhERESvCG8XFjH16tXD0aNHkZqaqtNepUoVPHjwQHndr18/mJmZoXPnznB3d8fx48fxySefvOpyiYiI3loMWUVM7969kZSUhNmzZyttaWlpuHDhAurVq6e0devWDQBgYWGBwMBAVK1a9ZXXSkRE9DbjnKwi6KeffsJnn32GTz/9FG5ubli6dClSUlKwf/9+5bENwNNHMCQlJem8Y5CIiIheDYasIuqPP/7AsmXLkJWVhbZt22LIkCE5Pjdw586deO+99xATEwMLCws9VUpERPR2Ysh6g2VmZiImJoafLUhERKQHDFlEREREKuDEdyIiIiIVMGQRERERqYAhi4iIiEgFDFlEREREKmDIIiIiIlIBQxYRERGRChiyiIiIiFTAkEVEqjp//jw2btyIw4cP67sUVf3999/YtWuXvssgotcIQxbRGyYpKQlBQUEICgrC2rVrERISgvDwcKSnp7/Q9h49eoSgoCBkZGQ8d98JEyagdevWCAoKwvHjx19o/6+jy5cvY/fu3TptW7duxZgxY/SybyJ6PRnruwAiKlz37t2Dv78/WrVqBRsbG6SkpODy5cuIjY3FJ598gi+++AJGRkYF3l5ERAT8/f3RoUMHmJubP1ctv/32G2bNmgV/f//nHcZrLTg4GBs2bECbNm2UNldXV/j6+upl30T0emLIInpDTZw4EU2bNlVe79y5Ez179sTDhw8xc+ZMAMCTJ08QHBwMAChWrBgqVaqEunXrwsDAAMDTq2J79uwBAGzcuBEajQbOzs6oU6dOvv3i4uKw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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "Counter({'operations': 6, 'table': 2, 'view': 1})" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "counts = Counter(file.action_type.value for file in result.files)\n", "fig, ax = plt.subplots(figsize=(7, 3))\n", "ax.bar(counts.keys(), counts.values(), color=\"#59A14F\")\n", "ax.set_title(\"Action types emitted from examples/sql\")\n", "ax.set_ylabel(\"files\")\n", "ax.set_xlabel(\"Dataform action type\")\n", "ax.tick_params(axis=\"x\", rotation=20)\n", "plt.show()\n", "counts" ] }, { "cell_type": "markdown", "id": "4cb60aad", "metadata": {}, "source": [ "## Compare non-default options\n", "\n", "`INCREMENTAL_WHEN_SAFE` converts a MERGE only when the converter can identify deterministic upsert semantics. `Layout.FLAT` is useful when teams want all output files in one folder, and `tags` adds Dataform tags to every generated action.\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "07c8d8a1", "metadata": { "execution": { "iopub.execute_input": "2026-07-12T14:42:57.803377Z", "iopub.status.busy": "2026-07-12T14:42:57.803049Z", "iopub.status.idle": "2026-07-12T14:42:58.119532Z", "shell.execute_reply": "2026-07-12T14:42:58.109107Z" } }, "outputs": [ { "data": { "text/plain": [ "[('backfill_script.sqlx', 'operations', 'backfill_script'),\n", " ('customer_attrs.sqlx', 'incremental', 'customer_attrs'),\n", " ('customer_orders.sqlx', 'view', 'customer_orders'),\n", " ('customers.sqlx', 'table', 'customers'),\n", " ('customers_update.sqlx', 'operations', 'customers_update'),\n", " ('events.sqlx', 'operations', 'events'),\n", " ('order_facts_insert.sqlx', 'operations', 'order_facts_insert'),\n", " ('orders.sqlx', 'table', 'orders'),\n", " ('orders_delete.sqlx', 'operations', 'orders_delete')]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "options = ConversionOptions(\n", " merge_strategy=MergeStrategy.INCREMENTAL_WHEN_SAFE,\n", " layout=Layout.FLAT,\n", " tags=[\"migrated\", \"reviewed\"],\n", " annotate=False,\n", ")\n", "custom = convert_directory(str(EXAMPLES / \"sql\"), options=options)\n", "custom_summary = [(file.relpath, file.action_type.value, file.action_name) for file in custom.files]\n", "custom_summary" ] }, { "cell_type": "markdown", "id": "4dbe89d5", "metadata": {}, "source": [ "## Validate option effects\n", "\n", "The safe MERGE example becomes an incremental action with `uniqueKey`, and flat layout removes nested directories from output paths.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "b3dc4b37", "metadata": { "execution": { "iopub.execute_input": "2026-07-12T14:42:58.128523Z", "iopub.status.busy": "2026-07-12T14:42:58.127464Z", "iopub.status.idle": "2026-07-12T14:42:58.144597Z", "shell.execute_reply": "2026-07-12T14:42:58.141641Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "config {\n", " type: \"incremental\",\n", " schema: \"staging\",\n", " name: \"customer_attrs\",\n", " protected: true,\n", " uniqueKey: [\"customer_id\"],\n", " dependencies: [\"customers_update\"],\n", " tags: [\"migrated\", \"reviewed\"]\n", "}\n", "\n", "-- Upsert latest customer attributes; provably convertible with\n", "-- --merge-strategy incremental-when-safe.\n", "SELECT customer_id, last_seen FROM ${ref(\"staging\", \"customers\")}\n", "\n" ] }, { "data": { "text/plain": [ "{'default_actions': {'operations': 6, 'view': 1, 'table': 2},\n", " 'custom_actions': {'operations': 5, 'incremental': 1, 'view': 1, 'table': 2},\n", " 'custom_warnings': ['ORDER_ASSUMED',\n", " 'ORDER_ASSUMED',\n", " 'ORDER_ASSUMED',\n", " 'SCRIPT_FILE',\n", " 'SCRIPT_WRITES',\n", " 'MERGE_INCREMENTAL',\n", " 'TARGET_SCHEMA_REQUIRED',\n", " 'CREATE_NO_AS']}" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "customer_attrs = next(file for file in custom.files if file.action_name == \"customer_attrs\")\n", "print(customer_attrs.content)\n", "\n", "assert customer_attrs.action_type.value == \"incremental\"\n", "assert \"uniqueKey:\" in customer_attrs.content\n", "assert 'tags: [\"migrated\", \"reviewed\"]' in customer_attrs.content\n", "assert all(\"/\" not in file.relpath for file in custom.files)\n", "{\n", " \"default_actions\": dict(result.report.actions_by_type),\n", " \"custom_actions\": dict(custom.report.actions_by_type),\n", " \"custom_warnings\": [warning.code for warning in custom.report.warnings],\n", "}" ] }, { "cell_type": "markdown", "id": "4d31b736", "metadata": {}, "source": [ "## Synthesize external declarations\n", "\n", "When `declare_external=True`, reads of tables not produced by the migration can become Dataform declarations. This lets the migrated project reference upstream warehouse tables explicitly.\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "badbca59", "metadata": { "execution": { "iopub.execute_input": "2026-07-12T14:42:58.147919Z", "iopub.status.busy": "2026-07-12T14:42:58.147548Z", "iopub.status.idle": "2026-07-12T14:42:58.163637Z", "shell.execute_reply": "2026-07-12T14:42:58.155959Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "===== fx.sqlx (table) =====\n", "config {\n", " type: \"table\",\n", " schema: \"marts\",\n", " name: \"fx\"\n", "}\n", "\n", "-- source: input.sql:1 (CREATE TABLE converted by sql2sqlx v0.1.0)\n", "SELECT * FROM ${ref(\"ext_finance\", \"currency_rates\")}\n", "\n", "===== sources/currency_rates.sqlx (declaration) =====\n", "config {\n", " type: \"declaration\",\n", " schema: \"ext_finance\",\n", " name: \"currency_rates\"\n", "}\n", "\n" ] } ], "source": [ "external = convert_string(\n", " \"CREATE TABLE marts.fx AS SELECT * FROM ext_finance.currency_rates;\",\n", " ConversionOptions(declare_external=True),\n", ")\n", "for file in external.files:\n", " print(f\"===== {file.relpath} ({file.action_type.value}) =====\")\n", " print(file.content)\n", "\n", "assert any(file.action_type.value == \"declaration\" for file in external.files)\n", "assert (\n", " '${ref(\"ext_finance\", \"currency_rates\")}'\n", " in next(file for file in external.files if file.action_name == \"fx\").content\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.13" } }, "nbformat": 4, "nbformat_minor": 5 }