diff --git a/Administration/Analytics/DataverseLink/FabricLink_SQLAnalyticsEndpoint/DVFabricLinkUtil.ipynb b/Administration/Analytics/DataverseLink/FabricLink_SQLAnalyticsEndpoint/DVFabricLinkUtil.ipynb index 6d5e06c0..6fbf292e 100644 --- a/Administration/Analytics/DataverseLink/FabricLink_SQLAnalyticsEndpoint/DVFabricLinkUtil.ipynb +++ b/Administration/Analytics/DataverseLink/FabricLink_SQLAnalyticsEndpoint/DVFabricLinkUtil.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"markdown","source":["# Notebook does following \n","\n","- 🏗️ Step 1: Create case-insensitive Data Warehouse.\n","- 🔹 Step 2: Connect to Synapse Serverless Database and get table list and schema information.\n","- 🔁 Step 3: Get parent and child tables information from local file.\n","- 🛠️ Step 4: Connect to Fabric Link lakehouse get table metadata and generate view ddl statements.\n","- 🏃‍♂️ Step 5: Connect to Fabric case in-senstive data warehouse and create views.\n","- 🚀 Step 6: Connect to Synaspe serverless virtual datawarehouse and collect views and dependencies.\n","- 🚀 Step 7: Connect to Fabric datawarehouse and deploy create views\n"],"metadata":{"microsoft":{"language":"python","language_group":"jupyter_python"},"nteract":{"transient":{"deleting":false}}},"id":"97ea5c26-d6c2-4e71-a58c-217d10d63b4f"},{"cell_type":"code","source":["# --- Configuration Constants ---\n","WORKSPACE_ID = \"cca1475b-c4fe-417f-844a-f3e8a061e55d\"\n","FABRIC_LH_DATABASE = \"dataverse_jjunoenvfre\"\n","FABRIC_WH_DATABASE = \"Sales_DW\"\n","\n","#Optional parameters to connect to your Synapse Serveress to get table and schema info\n","DRIVER = \"{ODBC Driver 18 for SQL Server}\"\n","SYNAPSE_SERVER = None # update to example value \"d365analyticsfabricsynapse-ondemand.sql.azuresynapse.net\" \n","SYNAPSE_EDL_DATABASE= None #update to example value \"analytics.sandbox.operations.dynamics.com\" \n","SYNAPSE_EDL_SCHEMA = None #\"dbo\" \n","SYNAPSE_EDL_CONST_COLUMN_NAME = None #=\"_SysRowId\" \n","\n","\n","#Optional parameters to connect to your Synapse Serveress Datawarehouse to get views and dependencies\n","SYNAPSE_DW_DATABASE = None # example value \"Dynamics365_DW\"\n","SYNAPSE_DW_SCHEMA = None # example value \"dbo\"\n","SYNAPSE_DW_VIEWS = None # example value \"CustomerDim,SalesFact,SupplierDim\"\n","FABRIC_WH_SCHEMA = None # example value \"edw\"\n","\n","#Fixed parameter - No need to change\n","GITHUB_RAW_BASE_URL = \"https://raw.githubusercontent.com/microsoft/Dynamics-365-FastTrack-Implementation-Assets/refs/heads/master/Analytics/DataverseLink/FabricLink_SQLAnalyticsEndpoint/DVFabricLinkUtil/\"\n","\n","DERIVED_TABLE_MAP_PATH = \"./builtin/resources/derived_table_map.json\"\n","LH_DDL_TEMPLATE_PATH = \"./builtin/resources/get_lh_ddl_as_view.sql\"\n","VIEW_DEPENDENCY_TEMPLATE_PATH = \"./builtin/resources/get_view_dependency.sql\"\n","REQUIRED_FILES = [\n"," (\"derived_table_map.json\", DERIVED_TABLE_MAP_PATH),\n"," (\"get_lh_ddl_as_view.sql\", LH_DDL_TEMPLATE_PATH),\n"," (\"get_view_dependency.sql\", VIEW_DEPENDENCY_TEMPLATE_PATH)]\n","\n","WAREHOUSE_VIEW_DDL_PARAMETERS = {\n"," \"source_schema\": \"dbo\",\n"," \"target_schema\": \"dbo\",\n"," \"only_fno_tables\": 1,\n"," \"tables_to_include\":\"*\",\n"," \"tables_to_exclude\": \"*\",\n"," \"filter_deleted_rows\": 1,\n"," \"join_derived_tables\": 1,\n"," \"change_collation\": 1,\n"," \"translate_enums\": 0,\n"," \"schema_map\": '[]',\n"," \"derived_table_map\": '[]'\n"," }\n"," \n","\n"],"outputs":[{"output_type":"display_data","data":{"application/vnd.jupyter.statement-meta+json":{"session_id":"801eb0aa-9a3a-4c30-b877-75c0402afbf2","normalized_state":"finished","queued_time":"2025-07-29T22:36:44.0741704Z","session_start_time":null,"execution_start_time":"2025-07-29T22:36:44.0752036Z","execution_finish_time":"2025-07-29T22:36:44.4572043Z","parent_msg_id":"7deffeec-b256-4647-bd8f-893cbaccb677"}},"metadata":{}}],"execution_count":3,"metadata":{"microsoft":{"language":"python","language_group":"jupyter_python"}},"id":"50733e90-a4fc-4424-896b-16399342f769"},{"cell_type":"code","source":["# --- Imports ---\n","import requests\n","import json\n","import logging\n","import time\n","import struct\n","import urllib.parse\n","import pandas as pd\n","from IPython.display import display, JSON\n","from sqlalchemy import create_engine, text, event\n","from requests.exceptions import HTTPError\n","import sys\n","import os\n","import re\n","\n","synapse_edl_engine = None\n","synapse_dw_engine = None\n","\n","def download_file_if_not_exists(url, local_path):\n"," \"\"\"Download a file from GitHub if it doesn't exist locally.\"\"\"\n"," if not notebookutils.fs.exists(local_path):\n"," notebookutils.fs.mkdirs(os.path.dirname(local_path))\n"," logger.info(f\"⬇️ Downloading {local_path} ...\")\n"," response = requests.get(url)\n"," response.raise_for_status() # Fail if not 200 OK\n"," notebookutils.fs.put(local_path, response.content.decode('utf-8')) # <-- decode bytes to string\n"," else:\n"," logger.info(f\"📄 File already exists locally: {local_path}\")\n","\n","def is_not_none_and_empty(s):\n"," return s is not None and s != ''\n","\n","\n","# --- Database Connections ---\n","def create_synapse_engine(server, database):\n"," connection_string = f\"DRIVER={DRIVER};SERVER={server};DATABASE={database};Encrypt=yes;TrustServerCertificate=no;\"\n"," odbc_conn_str = f\"mssql+pyodbc:///?odbc_connect={urllib.parse.quote_plus(connection_string)}\"\n"," engine = create_engine(odbc_conn_str)\n"," return engine\n","\n","synapse_edl_engine = create_synapse_engine(SYNAPSE_SERVER, SYNAPSE_EDL_DATABASE)\n","synapse_dw_engine = create_synapse_engine(SYNAPSE_SERVER, SYNAPSE_DW_DATABASE)\n","\n","@event.listens_for(synapse_edl_engine, \"do_connect\")\n","@event.listens_for(synapse_dw_engine, \"do_connect\")\n","def inject_access_token(dialect, conn_rec, cargs, cparams):\n"," token = notebookutils.credentials.getToken(\"https://database.windows.net/\")\n"," token_bytes = token.encode(\"utf-16-le\")\n"," token_struct = struct.pack(f\"