{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.13.0"
    },
    "blog_metadata": {
      "topic": "Why Azure SQL Is Becoming the Default Database for Coding Agents",
      "slug": "why-azure-sql-is-becoming-the-default-database-for-coding-ag",
      "generated_by": "LinkedIn Post Generator + Azure OpenAI",
      "generated_at": "2026-09-17T16:22:23.209Z"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Why Azure SQL Is Becoming the Default Database for Coding Agents\n",
        "\n",
        "This notebook turns the blog post into a hands-on validation flow. It focuses on the core claim: for production coding agents, the winning database is often the one that fits approved provisioning, identity, governance, and least-privilege access patterns. We will simulate the controlled-access architecture, validate tool contracts, inspect database-side SQL, and show how Azure SQL token-based access would look in Python."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install azure-identity pyodbc pandas"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "from typing import Any, Dict, List\n",
        "import json\n",
        "import textwrap\n",
        "import struct\n",
        "\n",
        "try:\n",
        "    import pyodbc\n",
        "except Exception:\n",
        "    pyodbc = None\n",
        "\n",
        "try:\n",
        "    from azure.identity import DefaultAzureCredential\n",
        "except Exception:\n",
        "    DefaultAzureCredential = None"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Controlled-access architecture\n",
        "\n",
        "The blog argues that agents should not connect directly to operational databases with broad privileges. Instead, they should call a narrow tool or API, which then maps to approved stored procedures or views under least-privilege roles.\n",
        "\n",
        "Below, we render the architecture as text so it can be validated directly in Python."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "architecture = {\n",
        "    \"flow\": [\n",
        "        \"Coding Agent -> Approved Data Tool / API\",\n",
        "        \"Approved Data Tool / API -> Azure Function or App Service\",\n",
        "        \"Azure Function or App Service -> Azure SQL Stored Procedure / View\",\n",
        "        \"Azure SQL Stored Procedure / View -> Least-Privilege Database Role\",\n",
        "        \"Least-Privilege Database Role -> Azure SQL Database\"\n",
        "    ],\n",
        "    \"platform_controls\": [\n",
        "        \"Platform Team -> Managed Identity\",\n",
        "        \"Platform Team -> Private Endpoint\",\n",
        "        \"Platform Team -> Auditing / Defender / Policies\",\n",
        "        \"Managed Identity -> Azure Function or App Service\",\n",
        "        \"Auditing / Defender / Policies -> Azure SQL Database\"\n",
        "    ]\n",
        "}\n",
        "\n",
        "print(\"Controlled-access architecture:\\n\")\n",
        "for step in architecture[\"flow\"]:\n",
        "    print(\"-\", step)\n",
        "print(\"\\nPlatform controls:\\n\")\n",
        "for step in architecture[\"platform_controls\"]:\n",
        "    print(\"-\", step)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Agent tool pattern\n",
        "\n",
        "This example demonstrates the preferred pattern: the agent calls a narrowly scoped tool instead of issuing raw SQL. The important part is the contract shape, including explicit tool naming and capped arguments."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "from typing import Any, Dict, List\n",
        "\n",
        "def get_recent_customer_orders(customer_id: int, limit: int = 5) -> List[Dict[str, Any]]:\n",
        "    approved_tool_payload = {\n",
        "        \"tool\": \"orders.get_recent_by_customer\",\n",
        "        \"arguments\": {\"customer_id\": customer_id, \"limit\": min(limit, 20)},\n",
        "    }\n",
        "    print(\"Calling approved tool:\", approved_tool_payload)\n",
        "    return [\n",
        "        {\"order_id\": 101, \"status\": \"Shipped\"},\n",
        "        {\"order_id\": 102, \"status\": \"Processing\"}\n",
        "    ]\n",
        "\n",
        "orders = get_recent_customer_orders(customer_id=42, limit=5)\n",
        "for order in orders:\n",
        "    print(order)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## API validation and SQL mapping\n",
        "\n",
        "This layer validates intent before touching the database. It only allows approved tools with approved arguments, then maps the request to a safe stored procedure contract."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "from typing import Dict, Any\n",
        "\n",
        "ALLOWED_TOOLS = {\n",
        "    \"orders.get_recent_by_customer\": {\"required\": {\"customer_id\", \"limit\"}}\n",
        "}\n",
        "\n",
        "def handle_tool_call(request: Dict[str, Any]) -> Dict[str, Any]:\n",
        "    tool = request[\"tool\"]\n",
        "    args = request[\"arguments\"]\n",
        "    if tool not in ALLOWED_TOOLS or set(args) != ALLOWED_TOOLS[tool][\"required\"]:\n",
        "        raise ValueError(\"Tool or arguments not approved\")\n",
        "    sql_command = \"EXEC api.GetRecentOrdersByCustomer @CustomerId=?, @Limit=?\"\n",
        "    sql_params = (int(args[\"customer_id\"]), min(int(args[\"limit\"]), 20))\n",
        "    return {\"sql_command\": sql_command, \"sql_params\": sql_params}\n",
        "\n",
        "result = handle_tool_call({\n",
        "    \"tool\": \"orders.get_recent_by_customer\",\n",
        "    \"arguments\": {\"customer_id\": 42, \"limit\": 5}\n",
        "})\n",
        "print(result)\n",
        "\n",
        "print(\"\\nNegative test:\")\n",
        "try:\n",
        "    handle_tool_call({\n",
        "        \"tool\": \"orders.get_recent_by_customer\",\n",
        "        \"arguments\": {\"customer_id\": 42, \"limit\": 5, \"debug\": True}\n",
        "    })\n",
        "except Exception as e:\n",
        "    print(type(e).__name__, str(e))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Sequence of a safe request\n",
        "\n",
        "The blog also describes the runtime sequence: the agent calls a tool, the API validates the request, the app obtains a Microsoft Entra token, then executes an approved stored procedure and returns a filtered business response."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "sequence = [\n",
        "    \"Agent -> Approved Tool API: tool=orders.get_recent_by_customer\",\n",
        "    \"Approved Tool API -> Approved Tool API: validate tool + arguments\",\n",
        "    \"Approved Tool API -> Managed Identity: request Entra access token\",\n",
        "    \"Managed Identity -> Approved Tool API: SQL access token\",\n",
        "    \"Approved Tool API -> Azure SQL: EXEC api.GetRecentOrdersByCustomer\",\n",
        "    \"Azure SQL -> Approved Tool API: result set\",\n",
        "    \"Approved Tool API -> Agent: filtered business response\"\n",
        "]\n",
        "\n",
        "print(\"Safe request sequence:\\n\")\n",
        "for i, step in enumerate(sequence, start=1):\n",
        "    print(f\"{i}. {step}\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Database-side contract\n",
        "\n",
        "This example shows the database-side shape the post recommends: expose a smaller, safer surface through a view and a stored procedure rather than allowing broad table access."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "schema_sql = \"\"\"\n",
        "CREATE VIEW api.vRecentOrders AS\n",
        "SELECT TOP (1000) OrderId, CustomerId, Status, OrderDate\n",
        "FROM dbo.Orders\n",
        "WHERE IsDeleted = 0;\n",
        "\n",
        "CREATE OR ALTER PROCEDURE api.GetRecentOrdersByCustomer\n",
        "    @CustomerId INT,\n",
        "    @Limit INT = 5\n",
        "AS\n",
        "BEGIN\n",
        "    SET NOCOUNT ON;\n",
        "    SELECT TOP (@Limit) OrderId, Status, OrderDate\n",
        "    FROM api.vRecentOrders\n",
        "    WHERE CustomerId = @CustomerId\n",
        "    ORDER BY OrderDate DESC;\n",
        "END;\n",
        "\"\"\"\n",
        "print(schema_sql)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Required variables for Azure SQL token-auth example\n",
        "\n",
        "If you want to run the next example against a real Azure SQL database, provide these values in the notebook or your environment:\n",
        "\n",
        "- `AZURE_SQL_SERVER` — e.g. `myserver.database.windows.net`\n",
        "- `AZURE_SQL_DATABASE` — e.g. `appdb`\n",
        "- Azure identity context that `DefaultAzureCredential` can use, such as Azure CLI login, managed identity, or service principal configuration\n",
        "\n",
        "The example avoids SQL usernames and passwords and uses Microsoft Entra token auth instead."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Azure SQL connection with Microsoft Entra token auth\n",
        "\n",
        "This code demonstrates the secure connection pattern from the post. It is written to be safe in a notebook: by default it prints the connection setup and only attempts a live connection if you explicitly set `RUN_LIVE_QUERY = True` and the required dependencies and identity are available."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import os\n",
        "import struct\n",
        "\n",
        "server = os.getenv(\"AZURE_SQL_SERVER\", \"myserver.database.windows.net\")\n",
        "database = os.getenv(\"AZURE_SQL_DATABASE\", \"appdb\")\n",
        "scope = \"https://database.windows.net/.default\"\n",
        "RUN_LIVE_QUERY = False\n",
        "\n",
        "conn_str = (\n",
        "    \"Driver={ODBC Driver 18 for SQL Server};\"\n",
        "    f\"Server=tcp:{server},1433;Database={database};\"\n",
        "    \"Encrypt=yes;TrustServerCertificate=no;\"\n",
        ")\n",
        "\n",
        "print(\"Connection string template:\")\n",
        "print(conn_str)\n",
        "\n",
        "if RUN_LIVE_QUERY:\n",
        "    if DefaultAzureCredential is None:\n",
        "        raise ImportError(\"azure-identity is not installed\")\n",
        "    if pyodbc is None:\n",
        "        raise ImportError(\"pyodbc is not installed\")\n",
        "\n",
        "    token = DefaultAzureCredential().get_token(scope).token.encode(\"utf-16-le\")\n",
        "    token_struct = struct.pack(f\"<I{len(token)}s\", len(token), token)\n",
        "\n",
        "    with pyodbc.connect(conn_str, attrs_before={1256: token_struct}) as conn:\n",
        "        rows = conn.cursor().execute(\"EXEC api.GetRecentOrdersByCustomer ?, ?\", 42, 5).fetchall()\n",
        "        print([tuple(r) for r in rows])\n",
        "else:\n",
        "    print(\"RUN_LIVE_QUERY is False; skipping live database call.\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Provisioning workflow as code\n",
        "\n",
        "The blog emphasizes that provisioning should be boring and repeatable. Since this notebook uses Python, we store the Azure CLI and PowerShell examples as strings for inspection and validation."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "provisioning_script = r'''# Repeatable Azure SQL-backed agent environment provisioning with org-approved placeholders.\n",
        "param(\n",
        "  [string]$ResourceGroup = \"rg-agent-data-dev\",\n",
        "  [string]$Location = \"eastus\",\n",
        "  [string]$SqlServer = \"sql-agent-demo-001\",\n",
        "  [string]$Database = \"appdb\"\n",
        ")\n",
        "\n",
        "az group create --name $ResourceGroup --location $Location\n",
        "az sql server create --resource-group $ResourceGroup --name $SqlServer `\n",
        "  --location $Location --enable-ad-only-auth true `\n",
        "  --external-admin-principal-type User `\n",
        "  --external-admin-name \"<ORG_APPROVED_ENTRA_ADMIN_NAME>\" `\n",
        "  --external-admin-sid \"<ORG_APPROVED_ENTRA_OBJECT_ID>\"\n",
        "\n",
        "az sql db create --resource-group $ResourceGroup --server $SqlServer `\n",
        "  --name $Database --service-objective S0\n",
        "'''\n",
        "\n",
        "print(provisioning_script)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Guardrails after provisioning\n",
        "\n",
        "After the database exists, the next step is to add guardrails such as firewall lockdown, auditing, Defender, and policy hooks. This reflects the post's sequence: provision, lock down, assign identity, grant least privilege, then observe."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "guardrails_script = r'''# Add platform guardrails: firewall lockdown, auditing, Defender, and placeholder policy hooks.\n",
        "param(\n",
        "  [string]$ResourceGroup = \"rg-agent-data-dev\",\n",
        "  [string]$SqlServer = \"sql-agent-demo-001\",\n",
        "  [string]$Database = \"appdb\",\n",
        "  [string]$LogAnalyticsWorkspaceId = \"<ORG_APPROVED_WORKSPACE_RESOURCE_ID>\"\n",
        ")\n",
        "\n",
        "az sql server firewall-rule create --resource-group $ResourceGroup --server $SqlServer `\n",
        "  --name \"AllowAzureServicesTemporarily\" --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0\n",
        "\n",
        "az sql db audit-policy update --resource-group $ResourceGroup --server $SqlServer `\n",
        "  --name $Database --state Enabled --log-analytics-target-state Enabled `\n",
        "  --workspace-resource-id $LogAnalyticsWorkspaceId\n",
        "\n",
        "az security atp sql update --resource-group $ResourceGroup --server $SqlServer --state Enabled\n",
        "Write-Host \"Apply org policy assignments for private endpoints, CMK, and tagging.\"\n",
        "'''\n",
        "\n",
        "print(guardrails_script)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Identity assignment and least-privilege grants\n",
        "\n",
        "The final infrastructure example assigns an application identity and grants only the permissions required by the tool contract. This is the core governance pattern: no broad raw SQL access for the agent path."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "identity_script = r'''# Provision an agent app identity and grant least-privilege database access through Entra.\n",
        "param(\n",
        "  [string]$ResourceGroup = \"rg-agent-data-dev\",\n",
        "  [string]$AppName = \"app-agent-api-demo\",\n",
        "  [string]$SqlServer = \"sql-agent-demo-001\",\n",
        "  [string]$Database = \"appdb\"\n",
        ")\n",
        "\n",
        "az webapp identity assign --resource-group $ResourceGroup --name $AppName | Out-Null\n",
        "$principalId = az webapp identity show --resource-group $ResourceGroup --name $AppName --query principalId -o tsv\n",
        "\n",
        "Write-Host \"Run the following T-SQL through your approved deployment pipeline or query tool:\"\n",
        "$query = @\"\n",
        "CREATE ROLE agent_executor;\n",
        "GRANT EXECUTE ON OBJECT::api.GetRecentOrdersByCustomer TO agent_executor;\n",
        "\n",
        "/*\n",
        "Use an org-approved Entra display name for the application identity.\n",
        "Exact CREATE USER syntax depends on how the external principal is represented in your tenant.\n",
        "Example:\n",
        "CREATE USER [app-agent-api-demo] FROM EXTERNAL PROVIDER;\n",
        "ALTER ROLE agent_executor ADD MEMBER [app-agent-api-demo];\n",
        "*/\n",
        "\"@\n",
        "\n",
        "Write-Output $query\n",
        "'''\n",
        "\n",
        "print(identity_script)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Provisioning sequence summary\n",
        "\n",
        "This is the operational sequence recommended by the post. It helps validate that the process is platform-led rather than agent-led."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "provisioning_flow = [\n",
        "    \"Provision Resource Group\",\n",
        "    \"Create Azure SQL Server\",\n",
        "    \"Enable Entra-only Auth\",\n",
        "    \"Create Database\",\n",
        "    \"Assign Managed Identity to Agent App\",\n",
        "    \"Grant EXEC on Stored Procedures\",\n",
        "    \"Enable Auditing and Defender\",\n",
        "    \"Apply Private Networking and Org Policies\"\n",
        "]\n",
        "\n",
        "print(\"Provisioning and hardening flow:\\n\")\n",
        "for i, step in enumerate(provisioning_flow, start=1):\n",
        "    print(f\"{i}. {step}\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Decision checklist\n",
        "\n",
        "The post recommends evaluating the database choice with governance and operability criteria rather than developer taste. The following checklist can be used interactively for a project review."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "checklist = {\n",
        "    \"approved_workflow\": \"Can the database and access path be provisioned through an approved workflow?\",\n",
        "    \"narrow_tools\": \"Can the agent use narrow tools or APIs instead of unrestricted credentials?\",\n",
        "    \"reviewable_controls\": \"Are schemas, migrations, permissions, and classifications reviewable?\",\n",
        "    \"platform_support\": \"Can platform engineering support it without creating a custom snowflake?\",\n",
        "    \"blast_radius\": \"Can security explain the blast radius of a compromised agent identity in one whiteboard diagram?\",\n",
        "    \"observability\": \"Can you rotate, audit, and observe the whole path without heroics?\"\n",
        "}\n",
        "\n",
        "responses = {\n",
        "    \"approved_workflow\": True,\n",
        "    \"narrow_tools\": True,\n",
        "    \"reviewable_controls\": True,\n",
        "    \"platform_support\": True,\n",
        "    \"blast_radius\": True,\n",
        "    \"observability\": True\n",
        "}\n",
        "\n",
        "print(\"Agent database readiness checklist:\\n\")\n",
        "score = 0\n",
        "for key, question in checklist.items():\n",
        "    answer = responses.get(key, False)\n",
        "    score += int(bool(answer))\n",
        "    print(f\"- {question} -> {'YES' if answer else 'NO'}\")\n",
        "\n",
        "print(f\"\\nScore: {score}/{len(checklist)}\")\n",
        "if score == len(checklist):\n",
        "    print(\"Azure SQL is a strong default candidate under this framework.\")\n",
        "else:\n",
        "    print(\"Investigate gaps before adopting Azure SQL as the default.\")"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Trade-off framework\n",
        "\n",
        "Azure SQL is presented as a strong default candidate, not a universal answer. This small helper captures the exception logic discussed in the post."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def recommend_database(governance=True, cost_ok=True, latency_ok=True, relational_fit=True):\n",
        "    if governance and cost_ok and latency_ok and relational_fit:\n",
        "        return \"Default candidate: Azure SQL\"\n",
        "    reasons = []\n",
        "    if not governance:\n",
        "        reasons.append(\"governance model mismatch\")\n",
        "    if not cost_ok:\n",
        "        reasons.append(\"cost pressure\")\n",
        "    if not latency_ok:\n",
        "        reasons.append(\"latency/locality constraints\")\n",
        "    if not relational_fit:\n",
        "        reasons.append(\"nonrelational data model\")\n",
        "    return \"Consider exception path due to: \" + \", \".join(reasons)\n",
        "\n",
        "scenarios = [\n",
        "    {\"name\": \"Microsoft-centric enterprise app\", \"args\": (True, True, True, True)},\n",
        "    {\"name\": \"Document-heavy workload\", \"args\": (True, True, True, False)},\n",
        "    {\"name\": \"Ultra-low-latency specialized platform\", \"args\": (True, True, False, True)},\n",
        "    {\"name\": \"Budget-constrained sprawl\", \"args\": (True, False, True, True)}\n",
        "]\n",
        "\n",
        "for s in scenarios:\n",
        "    print(s[\"name\"], \"->\", recommend_database(*s[\"args\"]))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Summary\n",
        "\n",
        "This notebook validated the blog's main pattern: coding agents should reach data through approved tools, validated arguments, managed identity, and least-privilege database contracts. In Microsoft-centric enterprises, Azure SQL often becomes the default not because every workload is relational, but because it aligns well with provisioning, identity, governance, networking, and audit expectations.\n",
        "\n",
        "## Next Steps\n",
        "\n",
        "- Replace the mock tool handler with a real API endpoint.\n",
        "- Deploy the view and stored procedure in a non-production Azure SQL database.\n",
        "- Test Microsoft Entra token auth with a managed identity.\n",
        "- Add logging, auditing, and policy checks around the tool path.\n",
        "- Document exception criteria for workloads where Azure SQL should not be the default."
      ]
    }
  ]
}