cancel
Showing results for 
Search instead for 
Did you mean: 

Databricks API JSON Result to Collection

naveed_raza
Level 9

Hi All,

Does anybody get a chance to work on Databricks API ?

Databricks API result comes in JSON and the JSON format is different compare to other API results as here in Databricks API we are getting columns in different array of object and actual data in different array of object.

What is the best way to convert this into proper data table (collection) with headers ?

Example: 

{
"schema": {
   "columns": 
      [
        {
          "name": "headername",
          "type": "string"
        },
        {
          "name": "headername2",
          "type": "date"
        }
      ]
  },
 "result": 
   {
     "data_array":[
           [
             "row1header1value",
             "row1header2value"
           ],
           [
             "row2header1value",
             "row2header2value"
           ]
         ]
   }
}

 #API, #databricks, #json

15 REPLIES 15

Thank you so much sharing this, let me try this 

naveed_raza
Level 9

looks like we have to get , EPPlus.dll and ClosedXML.dll dll files

They can be easily found in Visual Studio with Manage NuGet Packages. However, they are DLLs because the object generates an Excel file if the CreateExcel flag is set to true. Below is the version without those DLLs.

<process name="VBO - Databricks2" version="1.0" bpversion="7.1.0.62280" narrative="" byrefcollection="true" type="object" runmode="Exclusive" preferredid="965dd452-4483-4356-8e90-97dfb685bed9">
  <appdef>
    <element name="Application Root">
      <id>a1fcd9f9-3d26-4f70-bcc0-8406d53a2f05</id>
      <type>Application</type>
      <basetype>Application</basetype>
      <datatype>unknown</datatype>
      <diagnose>False</diagnose>
    </element>
  </appdef>
  <view>
    <camerax>0</camerax>
    <cameray>0</cameray>
    <zoom version="2">1.25</zoom>
  </view>
  <preconditions />
  <endpoint narrative="" />
  <subsheet subsheetid="7d7495b5-ea2b-424a-aae0-3c029b92f2a9" type="CleanUp" published="True">
    <name>Clean Up</name>
    <view>
      <camerax>0</camerax>
      <cameray>0</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="9cb7bf9b-8ba4-45f2-b334-fc34ba39707a" type="Normal" published="False">
    <name>Authentication</name>
    <view>
      <camerax>0</camerax>
      <cameray>-21</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="8e5d6e27-2067-4108-a3de-c6d32385476c" type="Normal" published="True">
    <name>Execute Query</name>
    <view>
      <camerax>-111</camerax>
      <cameray>-9</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="fa900c6f-ef74-4b80-a2e5-f70e341de476" type="Normal" published="True">
    <name>Execute Query - Using Token</name>
    <view>
      <camerax>-111</camerax>
      <cameray>264</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <stage stageid="ea40e4ef-0578-444a-93d6-cf433c09b609" name="Start" type="Start">
    <loginhibit />
    <display x="15" y="-105" />
    <onsuccess>1fba778b-777b-4bb2-8996-1fc62fc8fcd2</onsuccess>
  </stage>
  <stage stageid="1fba778b-777b-4bb2-8996-1fc62fc8fcd2" name="End" type="End">
    <loginhibit />
    <display x="15" y="90" />
  </stage>
  <stage stageid="f34ba72c-ee5c-4e66-9417-d7196dd2134a" name="Stage1" type="ProcessInfo">
    <display x="-195" y="-105" w="150" h="90" />
    <references>
      <reference>System.dll</reference>
      <reference>System.Data.dll</reference>
      <reference>System.Xml.dll</reference>
      <reference>System.Drawing.dll</reference>
      <reference>System.Net.Http.dll</reference>
      <reference>netstandard.dll</reference>
      <reference>System.Core.dll</reference>
      <reference>System.Net.Http.Json.dll</reference>
      <reference>Newtonsoft.Json.dll</reference>
      <reference>System.Windows.Forms.dll</reference>
      <reference>System.Net.Http.dll</reference>
    </references>
    <imports>
      <import>System</import>
      <import>System.Drawing</import>
      <import>System.Data</import>
      <import>System.Net.Http</import>
      <import>System.Text</import>
      <import>System.Collections.Generic</import>
      <import>System.Linq</import>
      <import>System.IO</import>
      <import>Newtonsoft.Json</import>
      <import>Newtonsoft.Json.Linq</import>
      <import>System.Windows.Forms</import>
      <import>System.Net.Http.Headers</import>
    </imports>
    <language>csharp</language>
    <globalcode><![CDATA[]]></globalcode>
    <code><![CDATA[
 
public class Column
{
    public string name { get; set; }
}
 
public class Row
{
    public List<string> values { get; set; }
}
 
public class JsonResponse
{
    public List<Column> columns { get; set; }
    public List<Row> rows { get; set; }
}
 
 
 
public static DataTable GenerateDataTableFromJson(string json)
    {
        JObject jsonObject = JObject.Parse(json);
        JArray columns = (JArray)jsonObject["manifest"]["schema"]["columns"];
        JArray dataArray = (JArray)jsonObject["result"]["data_array"];
 
        DataTable dataTable = new DataTable();
 
        foreach (var column in columns)
        {
            dataTable.Columns.Add(column["name"].ToString(), typeof(string));
        }
 
        foreach (var row in dataArray)
        {
            DataRow dataRow = dataTable.NewRow();
            for (int i = 0; i < columns.Count; i++)
            {
                dataRow[i] = row[i].ToString();
            }
            dataTable.Rows.Add(dataRow);
        }
 
        return dataTable;
    }
 
public static string EscapeForJson(string s)
    {
        if (s == null) return "";
        return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
    }
]]></code>
  </stage>
  <stage stageid="ce38e7ce-fcf1-4e63-bd70-44a2babf6a61" name="Clean Up" type="SubSheetInfo">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="6638295e-5a03-4485-84f4-0be42967f3be" name="Start" type="Start">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <loginhibit />
    <display x="15" y="-105" />
    <onsuccess>9e0c8841-53a6-4607-ad35-b1d9f8b571d7</onsuccess>
  </stage>
  <stage stageid="9e0c8841-53a6-4607-ad35-b1d9f8b571d7" name="End" type="End">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <loginhibit />
    <display x="15" y="90" />
  </stage>
  <stage stageid="a7ffe0ff-6bed-410a-8542-627fac718426" name="Execute Query" type="SubSheetInfo">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="7f49a903-a4c5-4669-b7e3-c8b900154c68" name="Start" type="Start">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <inputs>
      <input type="text" name="baseUrl" stage="baseUrl" />
      <input type="text" name="accessToken" stage="accessToken" />
      <input type="text" name="warehouseId" stage="warehouseId" />
      <input type="text" name="sqlQuery" stage="sqlQuery" />
    </inputs>
    <onsuccess>d0350ab4-99aa-4763-8c78-887721f4a3a5</onsuccess>
  </stage>
  <stage stageid="6a16195e-41c8-4e52-8625-061243db0811" name="End" type="End">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="165" />
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="Error Message" stage="Error Message" />
      <output type="collection" name="Report" stage="Report" />
    </outputs>
  </stage>
  <stage stageid="882d594d-6359-4c08-9c68-adb46b927cc7" name="" type="Note">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <narrative>Clean Up Page
 
This is an optional page where you might choose to perform some finalisation (or "cleanup") tasks as your business object is closed down.
 
The cleanup action will be called automatically immediately after closing your business object at the end of a business process.
 
You will not be able to call this action from a business process, nor will it be called at any other time than before the disposal of the business object.</narrative>
    <display x="-180" y="60" w="180" h="230" />
  </stage>
  <stage stageid="211de8a5-16c7-48bf-bff2-3e8859a8d124" name="" type="Note">
    <narrative>Initialise Page
 
This is an optional page where you might choose to perform some initialisation tasks after your business object is loaded.
 
The initialise action will be called automatically immediately after loading your business object.
 
You will not be able to call this action from a business process, nor will it be called at any other time than after the creation of the object.</narrative>
    <display x="-180" y="60" w="180" h="230" />
  </stage>
  <stage stageid="d0350ab4-99aa-4763-8c78-887721f4a3a5" name="Call API" type="Code">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <inputs>
      <input type="text" name="baseUrl" expr="[baseUrl]" />
      <input type="text" name="accessToken" expr="[accessToken]" />
      <input type="text" name="warehouseId" expr="[warehouseId]" />
      <input type="text" name="sqlQuery" expr="[sqlQuery]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="text" name="JSONOutput" stage="JSONOutput" />
    </outputs>
    <onsuccess>5555a2e6-2d06-4da2-b986-60516c04f8c4</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
JSONOutput = "";
 
 
HttpClient client = new HttpClient();
 
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, baseUrl)
{
Content = new StringContent(
              $"{{\"statement\":\"{sqlQuery}\", \"warehouse_id\":\"{warehouseId}\", \"timeout_seconds\":600}}",
              Encoding.UTF8,
              "application/json"
              )
};
request.Headers.Add("Authorization", $"Bearer {accessToken}");                // Invia la richiesta
HttpResponseMessage response = client.SendAsync(request).Result;
 
 
if (response.IsSuccessStatusCode)
{
string responseBody = response.Content.ReadAsStringAsync().Result;
Success = true;
JSONOutput = responseBody;
}
else
{
 
string responseBody = response.Content.ReadAsStringAsync().Result;
ErrorMessage = responseBody;
 
}
 
 
]]></code>
  </stage>
  <stage stageid="8e8901e4-0469-467c-ba32-ef8872e06bd4" name="baseUrl" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="dccd3340-c59f-4c07-9cf0-6b2158a0b15c" name="accessToken" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="a37d696c-f22a-42d2-926e-972ba737ca40" name="warehouseId" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="bd29c410-6809-46ee-ba90-4913e5c23721" name="sqlQuery" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23e4c765-3cad-483e-b764-22c0b9fa4986" name="Success" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="270" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="c9bef68e-b917-4f91-a8f3-850be2935e93" name="Error Message" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="270" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="a4f71191-c150-4bc5-a71c-1407b88ea104" name="JSONOutput" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="105" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23680203-a356-4f4a-9aa5-11ec8d787164" name="Report" type="Collection">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="315" />
    <datatype>collection</datatype>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="2b95b981-f6a3-46d2-ac2f-c3bf37e109aa" name="Convert JSON" type="Code">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="45" />
    <inputs>
      <input type="text" name="json" expr="[JSONOutput]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="collection" name="dataTable" stage="Report" />
    </outputs>
    <onsuccess>081c9bf2-4828-47ba-9f88-ee45a75f8735</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
dataTable = null;
dataTable = new DataTable();
 
try
{
dataTable = GenerateDataTableFromJson(json);
Success = true;
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
}
 
]]></code>
  </stage>
  <stage stageid="6f826340-a4b0-4a90-a21b-b7e83d2c3b64" name="Inputs" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="fc658c67-57fc-4741-81e3-420dee311aad" name="Local" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="75" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="69e8baa0-51a9-4cb0-8102-628d715d54fd" name="Outputs" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="240" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="aff1fb43-83dd-411b-895e-121a6e9aaa67" name="Anchor1" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="165" w="10" h="10" />
    <onsuccess>6a16195e-41c8-4e52-8625-061243db0811</onsuccess>
  </stage>
  <stage stageid="5555a2e6-2d06-4da2-b986-60516c04f8c4" name="[Success] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <decision expression="[Success] = True" />
    <ontrue>2b95b981-f6a3-46d2-ac2f-c3bf37e109aa</ontrue>
    <onfalse>25ddca11-d42c-4ac1-bfeb-590504da586b</onfalse>
  </stage>
  <stage stageid="081c9bf2-4828-47ba-9f88-ee45a75f8735" name="[Success] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="105" />
    <decision expression="[Success] = True" />
    <ontrue>6a16195e-41c8-4e52-8625-061243db0811</ontrue>
    <onfalse>d9b92af7-75ca-4170-9a32-2981d266cc24</onfalse>
  </stage>
  <stage stageid="25ddca11-d42c-4ac1-bfeb-590504da586b" name="Set Error Message" type="Calculation">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="30" y="-15" />
    <onsuccess>9708dd42-1db3-4ae3-809d-1513db8538f9</onsuccess>
    <calculation expression="&quot;Error in API Call: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="d9b92af7-75ca-4170-9a32-2981d266cc24" name="Set Error Message" type="Calculation">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="30" y="105" />
    <onsuccess>5dc509e2-1dea-4ae3-ae96-918a7a64f060</onsuccess>
    <calculation expression="&quot;Error in JSON Conversion: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="9708dd42-1db3-4ae3-809d-1513db8538f9" name="Anchor3" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="-15" w="10" h="10" />
    <onsuccess>5dc509e2-1dea-4ae3-ae96-918a7a64f060</onsuccess>
  </stage>
  <stage stageid="5dc509e2-1dea-4ae3-ae96-918a7a64f060" name="Anchor4" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="105" w="10" h="10" />
    <onsuccess>aff1fb43-83dd-411b-895e-121a6e9aaa67</onsuccess>
  </stage>
  <stage stageid="8848462a-85fd-4a3a-bc39-15398ec8475f" name="Authentication" type="SubSheetInfo">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="7f4050b6-af0c-44aa-afa5-b7d0fd9f4f1c" name="Start" type="Start">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <onsuccess>8c424f0b-43e3-49bd-9deb-97c9e5ded003</onsuccess>
  </stage>
  <stage stageid="aace0190-ea26-4a19-9ebe-9048a002664e" name="End" type="End">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <outputs>
      <output type="text" name="AccessToken" stage="AccessToken" />
    </outputs>
  </stage>
  <stage stageid="baa4ea76-41aa-46e6-8a66-4920d8f05d99" name="Inputs" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="195" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="0f8f7e0c-8f73-402c-9601-9443b4f6827e" name="Local" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="165" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="7066e6a3-cb35-4561-bc57-fb6785819432" name="Outputs" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="330" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="79280ca6-6232-452b-b7b2-8810f0ff1547" name="TenantID" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="-15" w="120" h="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5e4e3fc9-859a-4eeb-b1bf-3cfedc42bb8a" name="ClientId" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="30" w="120" h="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="3b5f25be-6542-4ee9-aae5-7e8174743bb3" name="ClientSecret" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="75" w="120" h="30" />
    <datatype>password</datatype>
    <initialvalueenc>
    </initialvalueenc>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="8c424f0b-43e3-49bd-9deb-97c9e5ded003" name="Get Token" type="Code">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <inputs>
      <input type="text" name="TenantID" expr="[TenantID]" />
      <input type="text" name="ClientId" expr="[ClientId]" />
      <input type="password" name="ClientSecret" expr="[ClientSecret]" />
    </inputs>
    <outputs>
      <output type="text" name="AccessToken" stage="AccessToken" />
    </outputs>
    <onsuccess>aace0190-ea26-4a19-9ebe-9048a002664e</onsuccess>
    <code><![CDATA[
 
        using (var httpClient = new HttpClient())
        {
            var form = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("client_id", ClientId),
                new KeyValuePair<string, string>("client_secret", ClientSecret),
                new KeyValuePair<string, string>("grant_type", "client_credentials"),
                // Scope corretto per Azure Databricks (AAD resource + /.default)
                new KeyValuePair<string, string>("scope", "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d/.default")
            });
 
            var resp = httpClient.PostAsync(tokenUrl, form).GetAwaiter().GetResult();
            string body = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
            if (!resp.IsSuccessStatusCode)
            {
                throw new Exception($"Token request failed ({resp.StatusCode}): {body}");
            }
 
            var json = JObject.Parse(body);
            string accessToken = json["access_token"]?.ToString();
            if (string.IsNullOrEmpty(accessToken))
                throw new Exception("access_token missing in token response");
 
            AccessToken = accessToken;
}
]]></code>
  </stage>
  <stage stageid="46d709ef-5c75-4394-83b0-78471c8c463a" name="AccessToken" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <display x="-225" y="360" />
    <datatype>text</datatype>
    <initialvalue />
    <alwaysinit />
  </stage>
  <stage stageid="4e76287c-26c7-45de-a165-1f3257d8760f" name="Execute Query - Using Token" type="SubSheetInfo">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="2d3a1314-19e9-4398-9259-4ec602b70f7e" name="Start" type="Start">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <inputs>
      <input type="text" name="databricksUrl" stage="databricksUrl" />
      <input type="text" name="warehouseId" stage="warehouseId" />
      <input type="text" name="sqlQuery" stage="sqlQuery" />
      <input type="flag" name="CreateExcel" stage="CreateExcel" />
      <input type="text" name="FilepathXLSX" stage="FilepathXLSX" />
      <input type="text" name="SheetName" stage="SheetName" />
    </inputs>
    <onsuccess>33ac5062-313a-408b-a962-0eb8eab9b6a4</onsuccess>
  </stage>
  <stage stageid="d1c909a0-c003-41a6-8d16-835dcb19fd7c" name="End" type="End">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="225" />
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="Error Message" stage="Error Message" />
      <output type="collection" name="Report" stage="Report" />
    </outputs>
  </stage>
  <stage stageid="753bbed9-7194-48db-86f7-2047f2dd218f" name="accessToken" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="195" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="74a6a9eb-ee35-4699-84ad-329239d1058f" name="warehouseId" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5e43ad48-cc52-41bb-89c4-6596788a19dd" name="sqlQuery" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="d4f9c706-65f0-4c79-8779-a00d9d9529a3" name="Success" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="360" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="536e4740-8f66-4f28-b651-be23fdef263f" name="Error Message" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="360" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="4b5e9e32-cc4c-468a-b1d1-b96bdfab941f" name="JSONOutput" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="195" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23766df5-bdba-449f-8d36-858278c8d97e" name="Report" type="Collection">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="405" />
    <datatype>collection</datatype>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="767269e4-2267-4297-a3c9-d97409e501de" name="Convert JSON Token" type="Code">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="105" />
    <inputs>
      <input type="text" name="json" expr="[JSONOutput]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="collection" name="dataTable" stage="Report" />
    </outputs>
    <onsuccess>d88bcda0-42b4-4302-ba8c-e642949c5a69</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
dataTable = null;
dataTable = new DataTable();
 
try
{
dataTable = GenerateDataTableFromJson(json);
Success = true;
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
}
 
]]></code>
  </stage>
  <stage stageid="33c5e16f-fa4e-4750-9e61-14e9f79eb243" name="Inputs" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="195" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="ebf2fea7-8b77-44c8-a000-9038213e3e34" name="Local" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="165" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="deb7307c-8680-4762-9dc1-5b7d5600729f" name="Outputs" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="330" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="5529a3ca-016c-44d8-94b1-56377f9b40fd" name="FilepathXLSX" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="75" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5add693a-51ef-4f02-a9c8-633e927ee473" name="CreateExcel" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="75" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="c5eff911-30f7-4e00-bf78-bd2290970310" name="Anchor2" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="225" w="10" h="10" />
    <onsuccess>d1c909a0-c003-41a6-8d16-835dcb19fd7c</onsuccess>
  </stage>
  <stage stageid="d2f2e471-9a3f-4b66-b87a-c3d790bfbcb0" name="SheetName" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="ad69bfb0-e52b-47f0-a648-5f1e094fcf37" name="[Success] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="45" />
    <decision expression="[Success] = True" />
    <ontrue>767269e4-2267-4297-a3c9-d97409e501de</ontrue>
    <onfalse>c0a9ca49-ff4e-441d-9650-da100bcf6bad</onfalse>
  </stage>
  <stage stageid="d88bcda0-42b4-4302-ba8c-e642949c5a69" name="[Success] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="165" />
    <decision expression="[Success] = True" />
    <ontrue>d1c909a0-c003-41a6-8d16-835dcb19fd7c</ontrue>
    <onfalse>04d9f303-e399-4bb3-93fa-3ce536aa55a1</onfalse>
  </stage>
  <stage stageid="c0a9ca49-ff4e-441d-9650-da100bcf6bad" name="Set Error Message" type="Calculation">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="30" y="45" />
    <onsuccess>0f46bfec-9fc3-4b45-9073-b2a2feebbbe3</onsuccess>
    <calculation expression="&quot;Error in API Call: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="04d9f303-e399-4bb3-93fa-3ce536aa55a1" name="Set Error Message" type="Calculation">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="30" y="165" />
    <onsuccess>93dfc5a7-0afe-4188-9a40-fb7de4b3b993</onsuccess>
    <calculation expression="&quot;Error in JSON Conversion: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="0f46bfec-9fc3-4b45-9073-b2a2feebbbe3" name="Anchor3" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="45" w="10" h="10" />
    <onsuccess>93dfc5a7-0afe-4188-9a40-fb7de4b3b993</onsuccess>
  </stage>
  <stage stageid="93dfc5a7-0afe-4188-9a40-fb7de4b3b993" name="Anchor4" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="165" w="10" h="10" />
    <onsuccess>c5eff911-30f7-4e00-bf78-bd2290970310</onsuccess>
  </stage>
  <stage stageid="10d34c6c-df8b-4d15-9a20-6ae62ee2bb83" name="Call API Token" type="Code">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <inputs>
      <input type="text" name="databricksUrl" expr="[databricksUrl]" />
      <input type="text" name="accessToken" expr="[accessToken]" />
      <input type="text" name="warehouseId" expr="[warehouseId]" />
      <input type="text" name="sqlQuery" expr="[sqlQuery]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="text" name="JSONOutput" stage="JSONOutput" />
    </outputs>
    <onsuccess>ad69bfb0-e52b-47f0-a648-5f1e094fcf37</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
JSONOutput = "";
 
try
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
var payload = new
{
statement = sqlQuery,
warehouse_id = warehouseId,
wait_timeout = "30s"
};
 
var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
var resp = httpClient.PostAsync(databricksUrl, content).GetAwaiter().GetResult();
string body = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
if (!resp.IsSuccessStatusCode)
{
throw new Exception($"Query failed ({resp.StatusCode}): {body}");
}
else
{
JSONOutput = body;
Success = true;
}
}
 
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.ToString();
}
]]></code>
  </stage>
  <stage stageid="cb0efb85-278a-4cf1-a80d-b99a7b2439f8" name="databricksUrl" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="33ac5062-313a-408b-a962-0eb8eab9b6a4" name="Authentication" type="SubSheet">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <outputs>
      <output type="text" name="AccessToken" friendlyname="AccessToken" stage="accessToken" />
    </outputs>
    <onsuccess>10d34c6c-df8b-4d15-9a20-6ae62ee2bb83</onsuccess>
    <processid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</processid>
  </stage>
</process>

Hi,

I have added more robust logic for the Using Token part with request verification and better management of the response JSON.

 

<process name="VBO - Databricks" version="1.0" bpversion="7.1.0.62280" narrative="" byrefcollection="true" type="object" runmode="Exclusive" preferredid="a89c02ea-a295-4848-b504-a2afe92a5e7e">
  <appdef>
    <element name="Application Root">
      <id>a1fcd9f9-3d26-4f70-bcc0-8406d53a2f05</id>
      <type>Application</type>
      <basetype>Application</basetype>
      <datatype>unknown</datatype>
      <diagnose>False</diagnose>
    </element>
  </appdef>
  <view>
    <camerax>0</camerax>
    <cameray>0</cameray>
    <zoom version="2">1.25</zoom>
  </view>
  <preconditions />
  <endpoint narrative="" />
  <subsheet subsheetid="7d7495b5-ea2b-424a-aae0-3c029b92f2a9" type="CleanUp" published="True">
    <name>Clean Up</name>
    <view>
      <camerax>0</camerax>
      <cameray>0</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="9cb7bf9b-8ba4-45f2-b334-fc34ba39707a" type="Normal" published="False">
    <name>Authentication</name>
    <view>
      <camerax>0</camerax>
      <cameray>33</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="8e5d6e27-2067-4108-a3de-c6d32385476c" type="Normal" published="True">
    <name>Execute Query</name>
    <view>
      <camerax>-111</camerax>
      <cameray>136</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="fa900c6f-ef74-4b80-a2e5-f70e341de476" type="Normal" published="True">
    <name>Execute Query - Using Token</name>
    <view>
      <camerax>-111</camerax>
      <cameray>18</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <stage stageid="ea40e4ef-0578-444a-93d6-cf433c09b609" name="Start" type="Start">
    <loginhibit />
    <display x="15" y="-105" />
    <onsuccess>1fba778b-777b-4bb2-8996-1fc62fc8fcd2</onsuccess>
  </stage>
  <stage stageid="1fba778b-777b-4bb2-8996-1fc62fc8fcd2" name="End" type="End">
    <loginhibit />
    <display x="15" y="90" />
  </stage>
  <stage stageid="f34ba72c-ee5c-4e66-9417-d7196dd2134a" name="Stage1" type="ProcessInfo">
    <display x="-195" y="-105" w="150" h="90" />
    <references>
      <reference>System.dll</reference>
      <reference>System.Data.dll</reference>
      <reference>System.Xml.dll</reference>
      <reference>System.Drawing.dll</reference>
      <reference>System.Net.Http.dll</reference>
      <reference>EPPlus.dll</reference>
      <reference>ClosedXML.dll</reference>
      <reference>netstandard.dll</reference>
      <reference>System.Core.dll</reference>
      <reference>System.Net.Http.Json.dll</reference>
      <reference>Newtonsoft.Json.dll</reference>
      <reference>System.Windows.Forms.dll</reference>
      <reference>System.Net.Http.dll</reference>
      <reference>System.Threading.dll</reference>
      <reference>System.Net.dll</reference>
    </references>
    <imports>
      <import>System</import>
      <import>System.Drawing</import>
      <import>System.Data</import>
      <import>System.Net.Http</import>
      <import>System.Text</import>
      <import>ClosedXML.Excel</import>
      <import>OfficeOpenXml</import>
      <import>System.Collections.Generic</import>
      <import>System.Linq</import>
      <import>System.IO</import>
      <import>Newtonsoft.Json</import>
      <import>Newtonsoft.Json.Linq</import>
      <import>System.Windows.Forms</import>
      <import>System.Net.Http.Headers</import>
      <import>System.Threading</import>
      <import>System.Net</import>
    </imports>
    <language>csharp</language>
    <globalcode><![CDATA[]]></globalcode>
    <code><![CDATA[public class DataTableToExcel
{
    public void ExportToExcel(DataTable dataTable, string filePath, string sheet)
    {
        // Configura EPPlus per usare la licenza non commerciale
 
        using (var package = new ExcelPackage())
        {
            var worksheet = package.Workbook.Worksheets.Add(sheet);
 
            // Aggiungi le intestazioni delle colonne
            for (int col = 0; col < dataTable.Columns.Count; col++)
            {
                worksheet.Cells[1, col + 1].Value = dataTable.Columns[col].ColumnName;
            }
 
            // Aggiungi i dati delle righe
            worksheet.Cells["A2"].LoadFromDataTable(dataTable, false);
 
            // Salva il file Excel
            var fileInfo = new FileInfo(filePath);
            package.SaveAs(fileInfo);
        }
    }
}
 
 
 
public class Column
{
    public string name { get; set; }
}
 
public class Row
{
    public List<string> values { get; set; }
}
 
public class JsonResponse
{
    public List<Column> columns { get; set; }
    public List<Row> rows { get; set; }
}
 
 
 
 
 public static DataTable GenerateDataTableFromJson(string json)
    {
        var jsonObject = JObject.Parse(json);
 
        // 1) Trova data_array (in result.data_array o alla root)
        var dataArrayToken =
            jsonObject.SelectToken("result.data_array") ??
            jsonObject.SelectToken("data_array");
 
        if (dataArrayToken == null || dataArrayToken.Type != JTokenType.Array)
            throw new InvalidOperationException("Nessun 'data_array' trovato nel JSON.");
 
        var dataArray = (JArray)dataArrayToken;
 
        // 2) Prova a leggere lo schema colonne se presente
        var columnsToken = jsonObject.SelectToken("manifest.schema.columns") as JArray;
        var columnNames = new List<string>();
        int maxCols;
 
        if (columnsToken != null && columnsToken.Count > 0)
        {
            foreach (var col in columnsToken)
            {
                // Cerca proprietà comuni per il nome colonna
                var name = (string)(col["name"] ?? col["title"] ?? col["id"]);
                if (string.IsNullOrWhiteSpace(name))
                    name = $"Col{columnNames.Count + 1}";
                columnNames.Add(name);
            }
            maxCols = columnNames.Count;
        }
        else
        {
            // 3) Nessuno schema: ricava il numero max di colonne dalle righe
            maxCols = dataArray
                .Select(r => (r as JArray)?.Count ?? 0)
                .DefaultIfEmpty(0)
                .Max();
 
            for (int i = 1; i <= maxCols; i++)
                columnNames.Add($"Col{i}");
        }
 
        // 4) Crea il DataTable e popola i dati
        var table = new DataTable();
        foreach (var name in columnNames)
            table.Columns.Add(name, typeof(string));
 
        foreach (var rowToken in dataArray)
        {
            var rowArray = rowToken as JArray;
            if (rowArray == null)
                continue; // oppure: throw, se vuoi essere più rigido
 
            var dr = table.NewRow();
 
            for (int i = 0; i < maxCols; i++)
            {
                if (i < rowArray.Count)
                {
                    var cell = rowArray[i];
                    // Se vuoi stringa vuota al posto del DBNull:
                    // dr[i] = cell.Type == JTokenType.Null ? "" : (string)cell;
                    dr[i] = cell.Type == JTokenType.Null ? (object)DBNull.Value : (string)cell;
                }
                else
                {
                    dr[i] = DBNull.Value;
                }
            }
 
            table.Rows.Add(dr);
        }
 
        return table;
    }
 
 
public static string EscapeForJson(string s)
    {
        if (s == null) return "";
        return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
    }
 
 
 
 
public static (string StatementId, string State) ParseWithNewtonsoft(string json)
{
    var jo = JObject.Parse(json);
 
    string statementId = (string)jo["statement_id"];
    string state = (string)jo["status"]?["state"];
 
    return (statementId, state);
}
]]></code>
  </stage>
  <stage stageid="ce38e7ce-fcf1-4e63-bd70-44a2babf6a61" name="Clean Up" type="SubSheetInfo">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="6638295e-5a03-4485-84f4-0be42967f3be" name="Start" type="Start">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <loginhibit />
    <display x="15" y="-105" />
    <onsuccess>9e0c8841-53a6-4607-ad35-b1d9f8b571d7</onsuccess>
  </stage>
  <stage stageid="9e0c8841-53a6-4607-ad35-b1d9f8b571d7" name="End" type="End">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <loginhibit />
    <display x="15" y="90" />
  </stage>
  <stage stageid="a7ffe0ff-6bed-410a-8542-627fac718426" name="Execute Query" type="SubSheetInfo">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="7f49a903-a4c5-4669-b7e3-c8b900154c68" name="Start" type="Start">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <inputs>
      <input type="text" name="baseUrl" stage="baseUrl" />
      <input type="text" name="accessToken" stage="accessToken" />
      <input type="text" name="warehouseId" stage="warehouseId" />
      <input type="text" name="sqlQuery" stage="sqlQuery" />
      <input type="flag" name="CreateExcel" stage="CreateExcel" />
      <input type="text" name="FilepathXLSX" stage="FilepathXLSX" />
      <input type="text" name="SheetName" stage="SheetName" />
    </inputs>
    <onsuccess>d0350ab4-99aa-4763-8c78-887721f4a3a5</onsuccess>
  </stage>
  <stage stageid="6a16195e-41c8-4e52-8625-061243db0811" name="End" type="End">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="345" />
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="Error Message" stage="Error Message" />
      <output type="collection" name="Report" stage="Report" />
    </outputs>
  </stage>
  <stage stageid="882d594d-6359-4c08-9c68-adb46b927cc7" name="" type="Note">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <narrative>Clean Up Page
 
This is an optional page where you might choose to perform some finalisation (or "cleanup") tasks as your business object is closed down.
 
The cleanup action will be called automatically immediately after closing your business object at the end of a business process.
 
You will not be able to call this action from a business process, nor will it be called at any other time than before the disposal of the business object.</narrative>
    <display x="-180" y="60" w="180" h="230" />
  </stage>
  <stage stageid="211de8a5-16c7-48bf-bff2-3e8859a8d124" name="" type="Note">
    <narrative>Initialise Page
 
This is an optional page where you might choose to perform some initialisation tasks after your business object is loaded.
 
The initialise action will be called automatically immediately after loading your business object.
 
You will not be able to call this action from a business process, nor will it be called at any other time than after the creation of the object.</narrative>
    <display x="-180" y="60" w="180" h="230" />
  </stage>
  <stage stageid="d0350ab4-99aa-4763-8c78-887721f4a3a5" name="Call API" type="Code">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <inputs>
      <input type="text" name="baseUrl" expr="[baseUrl]" />
      <input type="text" name="accessToken" expr="[accessToken]" />
      <input type="text" name="warehouseId" expr="[warehouseId]" />
      <input type="text" name="sqlQuery" expr="[sqlQuery]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="text" name="JSONOutput" stage="JSONOutput" />
    </outputs>
    <onsuccess>5555a2e6-2d06-4da2-b986-60516c04f8c4</onsuccess>
    <code><![CDATA[
Success = false;
ErrorMessage = "";
JSONOutput = "";
 
// Funzioni locali di supporto: estrazione dettagli errore e SQLSTATE
string ExtractDetail(JObject j)
{
    if (j == null) return null;
    return (string)j["status"]?["error"]?["message"]
           ?? (string)j["error"]?["message"]
           ?? (string)j["status"]?["error"]?["code"]
           ?? (string)j["error"]?["code"];
}
 
string ExtractSqlState(JObject j)
{
    if (j == null) return null;
    return (string)j["status"]?["error"]?["sql_state"]
           ?? (string)j["error"]?["sql_state"];
}
 
void BuildShortErrorFromHttp(string forcedState, HttpResponseMessage resp, string body, out string errorMessage)
{
    var stateLabel = string.IsNullOrWhiteSpace(forcedState) ? "FAILED" : forcedState;
 
    string detail = null;
    string sqlState = null;
 
    try
    {
        var j = string.IsNullOrWhiteSpace(body) ? null : JObject.Parse(body);
        detail = ExtractDetail(j);
        sqlState = ExtractSqlState(j);
    }
    catch { /* ignore parse errors */ }
 
    if (string.IsNullOrEmpty(detail))
        detail = $"HTTP {(int)resp.StatusCode} {resp.ReasonPhrase}";
    if (string.IsNullOrEmpty(sqlState))
        sqlState = "N/A";
 
    errorMessage = $"State: {stateLabel} - Detail: {detail} - SQLSTATE: {sqlState}";
}
 
HttpClient client = new HttpClient();
 
try
{
    // ====== POST ======
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, baseUrl)
    {
        Content = new StringContent(
            $"{{\"statement\":\"{sqlQuery}\", \"warehouse_id\":\"{warehouseId}\", \"timeout_seconds\":600}}",
            Encoding.UTF8,
            "application/json"
        )
    };
    request.Headers.Add("Authorization", $"Bearer {accessToken}");
 
    // Invia la richiesta
    HttpResponseMessage response = client.SendAsync(request).Result;
    string responseBody = response.Content.ReadAsStringAsync().Result;
 
    if (response.IsSuccessStatusCode)
    {
        Success = true;
        JSONOutput = responseBody;
    }
    else
    {
        BuildShortErrorFromHttp("FAILED", response, responseBody, out ErrorMessage);
        Success = false;
    }
 
    /* =========================
       POLLING LOGIC (ADD ONLY)
       ========================= */
    // Procedi al polling SOLO se la POST è andata a buon fine e hai un body
    if (Success && !string.IsNullOrWhiteSpace(JSONOutput))
    {
        JObject jo = null;
        try { jo = JObject.Parse(JSONOutput); } catch { jo = null; }
 
        if (jo == null)
        {
            Success = false;
            ErrorMessage = "State: FAILED - Detail: Impossibile interpretare la risposta POST come JSON. - SQLSTATE: N/A";
        }
        else
        {
            string statementId = (string)jo["statement_id"];
            string state = (string)jo["status"]?["state"];
 
            // Stati terminali previsti dall’API
            string[] terminal = { "SUCCEEDED", "FAILED", "CANCELED", "CLOSED" };
 
            bool needsPolling =
                !string.IsNullOrWhiteSpace(statementId) &&
                (string.IsNullOrWhiteSpace(state) || Array.IndexOf(terminal, state.ToUpperInvariant()) < 0);
 
            if (needsPolling)
            {
                // Timeout lato client per il ciclo di polling (regolabile)
                TimeSpan pollingTimeout = TimeSpan.FromMinutes(10);
                DateTime start = DateTime.UtcNow;
 
                // Backoff esponenziale (cap a 5s)
                int delayMs = 500;
 
                // baseUrl è l’endpoint POST /sql/statements → per il GET aggiungo /{statement_id}
                string getUrl = baseUrl.TrimEnd('/') + "/" + statementId;
 
                while (true)
                {
                    // timeout lato client
                    if (DateTime.UtcNow - start > pollingTimeout)
                    {
                        Success = false;
                        ErrorMessage = "State: FAILED - Detail: Polling scaduto lato client. - SQLSTATE: N/A";
                        break;
                    }
 
                    Thread.Sleep(delayMs);
                    delayMs = Math.Min(delayMs * 2, 5000); // 0.5s → 1s → 2s → 4s → 5s ...
 
                    var pollReq = new HttpRequestMessage(HttpMethod.Get, getUrl);
                    pollReq.Headers.Add("Authorization", $"Bearer {accessToken}");
                    var pollResp = client.SendAsync(pollReq).Result;
 
                    string pollBody = pollResp.Content.ReadAsStringAsync().Result;
 
                    if (pollResp.StatusCode == HttpStatusCode.NotFound)
                    {
                        Success = false;
                        ErrorMessage = "State: FAILED - Detail: Statement non trovato (potrebbe essere scaduto dal warehouse). - SQLSTATE: N/A";
                        break;
                    }
 
                    if (!pollResp.IsSuccessStatusCode)
                    {
                        BuildShortErrorFromHttp("FAILED", pollResp, pollBody, out ErrorMessage);
                        Success = false;
                        break;
                    }
 
                    // Aggiorno sempre l’ultimo body positivo
                    JSONOutput = pollBody;
 
                    JObject p = null;
                    try { p = JObject.Parse(pollBody); } catch { p = null; }
 
                    if (p == null)
                    {
                        // Continua a pollare finché non ottieni JSON valido, ma rispetta timeout
                        continue;
                    }
 
                    state = (string)p["status"]?["state"];
                    if (string.IsNullOrWhiteSpace(state)) continue;
 
                    string stateUpper = state.ToUpperInvariant();
 
                    // Uscita su stato terminale
                    if (Array.IndexOf(terminal, stateUpper) >= 0)
                    {
                        if (stateUpper == "SUCCEEDED")
                        {
                            Success = true;
                            ErrorMessage = "";
                        }
                        else
                        {
                            Success = false;
 
                            // Compose errore sintetico come richiesto
                            string detail = ExtractDetail(p) ?? $"State={state}";
                            string sqlState = ExtractSqlState(p) ?? "N/A";
                            ErrorMessage = $"State: {stateUpper} - Detail: {detail} - SQLSTATE: {sqlState}";
                        }
                        break;
                    }
                }
            }
        }
    }
}
catch (Exception ex)
{
    Success = false;
    // Nessuno stacktrace: solo dettaglio sintetico
    ErrorMessage = $"State: FAILED - Detail: {ex.Message} - SQLSTATE: N/A";
}
finally
{
    client?.Dispose();
}
]]></code>
  </stage>
  <stage stageid="8e8901e4-0469-467c-ba32-ef8872e06bd4" name="baseUrl" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="30" />
    <datatype>text</datatype>
    <initialvalue xml:space="preserve">https://westeurope.azuredatabricks.net/api/2.0/sql/statements</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="dccd3340-c59f-4c07-9cf0-6b2158a0b15c" name="accessToken" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="a37d696c-f22a-42d2-926e-972ba737ca40" name="warehouseId" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="bd29c410-6809-46ee-ba90-4913e5c23721" name="sqlQuery" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23e4c765-3cad-483e-b764-22c0b9fa4986" name="Success" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="360" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="c9bef68e-b917-4f91-a8f3-850be2935e93" name="Error Message" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="360" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="a4f71191-c150-4bc5-a71c-1407b88ea104" name="JSONOutput" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="195" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23680203-a356-4f4a-9aa5-11ec8d787164" name="Report" type="Collection">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="405" />
    <datatype>collection</datatype>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="2b95b981-f6a3-46d2-ac2f-c3bf37e109aa" name="Convert JSON" type="Code">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="45" />
    <inputs>
      <input type="text" name="json" expr="[JSONOutput]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="collection" name="dataTable" stage="Report" />
    </outputs>
    <onsuccess>081c9bf2-4828-47ba-9f88-ee45a75f8735</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
dataTable = null;
dataTable = new DataTable();
 
try
{
dataTable = GenerateDataTableFromJson(json);
Success = true;
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
}
 
]]></code>
  </stage>
  <stage stageid="6f826340-a4b0-4a90-a21b-b7e83d2c3b64" name="Inputs" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="195" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="fc658c67-57fc-4741-81e3-420dee311aad" name="Local" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="165" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="69e8baa0-51a9-4cb0-8102-628d715d54fd" name="Outputs" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="330" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="3d0bd6c8-bfea-4f19-b1f0-17af6082fd95" name="FilepathXLSX" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="75" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="6fce1fed-aed7-4be8-98a7-47cd4d387274" name="CreateExcel" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="75" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="4a1251fb-c3ba-49fa-82e5-4364fec79357" name="Create Excel" type="Code">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="225" />
    <inputs>
      <input type="collection" name="DatatableResult" expr="[Report]" />
      <input type="text" name="Filepath" expr="[FilepathXLSX]" />
      <input type="text" name="SheetName" expr="[SheetName]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
    </outputs>
    <onsuccess>943d0920-2cf6-4da3-9b48-9a5af41f847f</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
 
try
{
DataTableToExcel exporter = new DataTableToExcel();
exporter.ExportToExcel(DatatableResult, Filepath, SheetName);
Success = true;
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
}]]></code>
  </stage>
  <stage stageid="3f15a13e-c6d9-4309-b03c-b84634339a44" name="[CreateExcel] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="165" />
    <decision expression="[CreateExcel] = True" />
    <ontrue>4a1251fb-c3ba-49fa-82e5-4364fec79357</ontrue>
    <onfalse>aff1fb43-83dd-411b-895e-121a6e9aaa67</onfalse>
  </stage>
  <stage stageid="aff1fb43-83dd-411b-895e-121a6e9aaa67" name="Anchor1" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="165" w="10" h="10" />
    <onsuccess>de02cbf6-bc09-4036-9c44-61fe6f59330b</onsuccess>
  </stage>
  <stage stageid="7d038089-d0d0-47d6-9567-ff7fbd4e2ad2" name="Anchor2" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="345" w="10" h="10" />
    <onsuccess>6a16195e-41c8-4e52-8625-061243db0811</onsuccess>
  </stage>
  <stage stageid="8d301f0a-94c3-4852-a216-f252b27166c3" name="SheetName" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="120" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5555a2e6-2d06-4da2-b986-60516c04f8c4" name="[Success] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <decision expression="[Success] = True" />
    <ontrue>2b95b981-f6a3-46d2-ac2f-c3bf37e109aa</ontrue>
    <onfalse>25ddca11-d42c-4ac1-bfeb-590504da586b</onfalse>
  </stage>
  <stage stageid="081c9bf2-4828-47ba-9f88-ee45a75f8735" name="[Success] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="105" />
    <decision expression="[Success] = True" />
    <ontrue>3f15a13e-c6d9-4309-b03c-b84634339a44</ontrue>
    <onfalse>d9b92af7-75ca-4170-9a32-2981d266cc24</onfalse>
  </stage>
  <stage stageid="25ddca11-d42c-4ac1-bfeb-590504da586b" name="Set Error Message" type="Calculation">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="30" y="-15" />
    <onsuccess>9708dd42-1db3-4ae3-809d-1513db8538f9</onsuccess>
    <calculation expression="&quot;Error in API Call: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="d9b92af7-75ca-4170-9a32-2981d266cc24" name="Set Error Message" type="Calculation">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="30" y="105" />
    <onsuccess>5dc509e2-1dea-4ae3-ae96-918a7a64f060</onsuccess>
    <calculation expression="&quot;Error in JSON Conversion: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="943d0920-2cf6-4da3-9b48-9a5af41f847f" name="[Success] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="285" />
    <decision expression="[Success] = True" />
    <ontrue>6a16195e-41c8-4e52-8625-061243db0811</ontrue>
    <onfalse>fc1ca4c2-0045-4d9d-ad88-61abf950a75c</onfalse>
  </stage>
  <stage stageid="fc1ca4c2-0045-4d9d-ad88-61abf950a75c" name="Set Error Message" type="Calculation">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="30" y="285" />
    <onsuccess>de02cbf6-bc09-4036-9c44-61fe6f59330b</onsuccess>
    <calculation expression="&quot;Error in Create Excel: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="9708dd42-1db3-4ae3-809d-1513db8538f9" name="Anchor3" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="-15" w="10" h="10" />
    <onsuccess>5dc509e2-1dea-4ae3-ae96-918a7a64f060</onsuccess>
  </stage>
  <stage stageid="5dc509e2-1dea-4ae3-ae96-918a7a64f060" name="Anchor4" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="105" w="10" h="10" />
    <onsuccess>aff1fb43-83dd-411b-895e-121a6e9aaa67</onsuccess>
  </stage>
  <stage stageid="de02cbf6-bc09-4036-9c44-61fe6f59330b" name="Anchor5" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="285" w="10" h="10" />
    <onsuccess>7d038089-d0d0-47d6-9567-ff7fbd4e2ad2</onsuccess>
  </stage>
  <stage stageid="8848462a-85fd-4a3a-bc39-15398ec8475f" name="Authentication" type="SubSheetInfo">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="7f4050b6-af0c-44aa-afa5-b7d0fd9f4f1c" name="Start" type="Start">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <onsuccess>8c424f0b-43e3-49bd-9deb-97c9e5ded003</onsuccess>
  </stage>
  <stage stageid="aace0190-ea26-4a19-9ebe-9048a002664e" name="End" type="End">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <outputs>
      <output type="text" name="AccessToken" stage="AccessToken" />
    </outputs>
  </stage>
  <stage stageid="baa4ea76-41aa-46e6-8a66-4920d8f05d99" name="Inputs" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="195" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="0f8f7e0c-8f73-402c-9601-9443b4f6827e" name="Local" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="165" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="7066e6a3-cb35-4561-bc57-fb6785819432" name="Outputs" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="330" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="79280ca6-6232-452b-b7b2-8810f0ff1547" name="TenantID" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="-15" w="120" h="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5e4e3fc9-859a-4eeb-b1bf-3cfedc42bb8a" name="ClientId" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="30" w="120" h="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="3b5f25be-6542-4ee9-aae5-7e8174743bb3" name="ClientSecret" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="75" w="120" h="30" />
    <datatype>password</datatype>
    <initialvalueenc>
    </initialvalueenc>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="8c424f0b-43e3-49bd-9deb-97c9e5ded003" name="Get Token" type="Code">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <inputs>
      <input type="text" name="TenantID" expr="[TenantID]" />
      <input type="text" name="ClientId" expr="[ClientId]" />
      <input type="password" name="ClientSecret" expr="[ClientSecret]" />
    </inputs>
    <outputs>
      <output type="text" name="AccessToken" stage="AccessToken" />
    </outputs>
    <onsuccess>aace0190-ea26-4a19-9ebe-9048a002664e</onsuccess>
    <code><![CDATA[
 
        using (var httpClient = new HttpClient())
        {
            var form = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("client_id", ClientId),
                new KeyValuePair<string, string>("client_secret", ClientSecret),
                new KeyValuePair<string, string>("grant_type", "client_credentials"),
                // Scope corretto per Azure Databricks (AAD resource + /.default)
                new KeyValuePair<string, string>("scope", "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d/.default")
            });
 
            var resp = httpClient.PostAsync(tokenUrl, form).GetAwaiter().GetResult();
            string body = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
            if (!resp.IsSuccessStatusCode)
            {
                throw new Exception($"Token request failed ({resp.StatusCode}): {body}");
            }
 
            var json = JObject.Parse(body);
            string accessToken = json["access_token"]?.ToString();
            if (string.IsNullOrEmpty(accessToken))
                throw new Exception("access_token missing in token response");
 
            AccessToken = accessToken;
}
]]></code>
  </stage>
  <stage stageid="46d709ef-5c75-4394-83b0-78471c8c463a" name="AccessToken" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <display x="-225" y="360" />
    <datatype>text</datatype>
    <initialvalue />
    <alwaysinit />
  </stage>
  <stage stageid="4e76287c-26c7-45de-a165-1f3257d8760f" name="Execute Query - Using Token" type="SubSheetInfo">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="2d3a1314-19e9-4398-9259-4ec602b70f7e" name="Start" type="Start">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <inputs>
      <input type="text" name="databricksUrl" stage="databricksUrl" />
      <input type="text" name="warehouseId" stage="warehouseId" />
      <input type="text" name="sqlQuery" stage="sqlQuery" />
      <input type="flag" name="CreateExcel" stage="CreateExcel" />
      <input type="text" name="FilepathXLSX" stage="FilepathXLSX" />
      <input type="text" name="SheetName" stage="SheetName" />
    </inputs>
    <onsuccess>33ac5062-313a-408b-a962-0eb8eab9b6a4</onsuccess>
  </stage>
  <stage stageid="d1c909a0-c003-41a6-8d16-835dcb19fd7c" name="End" type="End">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="405" />
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="Error Message" stage="Error Message" />
      <output type="collection" name="Report" stage="Report" />
    </outputs>
  </stage>
  <stage stageid="753bbed9-7194-48db-86f7-2047f2dd218f" name="accessToken" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="195" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="74a6a9eb-ee35-4699-84ad-329239d1058f" name="warehouseId" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5e43ad48-cc52-41bb-89c4-6596788a19dd" name="sqlQuery" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="d4f9c706-65f0-4c79-8779-a00d9d9529a3" name="Success" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="360" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="536e4740-8f66-4f28-b651-be23fdef263f" name="Error Message" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="360" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="4b5e9e32-cc4c-468a-b1d1-b96bdfab941f" name="JSONOutput" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="195" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23766df5-bdba-449f-8d36-858278c8d97e" name="Report" type="Collection">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="405" />
    <datatype>collection</datatype>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="767269e4-2267-4297-a3c9-d97409e501de" name="Convert JSON Token NEW" type="Code">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="105" />
    <inputs>
      <input type="text" name="json" expr="[JSONOutput]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="collection" name="dataTable" stage="Report" />
    </outputs>
    <onsuccess>d88bcda0-42b4-4302-ba8c-e642949c5a69</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
dataTable = null;
dataTable = new DataTable();
 
try
{
dataTable = GenerateDataTableFromJson(json);
Success = true;
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
}
 
]]></code>
  </stage>
  <stage stageid="33c5e16f-fa4e-4750-9e61-14e9f79eb243" name="Inputs" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="195" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="ebf2fea7-8b77-44c8-a000-9038213e3e34" name="Local" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="165" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="deb7307c-8680-4762-9dc1-5b7d5600729f" name="Outputs" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="330" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="5529a3ca-016c-44d8-94b1-56377f9b40fd" name="FilepathXLSX" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="75" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5add693a-51ef-4f02-a9c8-633e927ee473" name="CreateExcel" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="75" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="61a50860-9a73-4cbd-8307-274af6b1cff2" name="Create Excel Token" type="Code">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="285" />
    <inputs>
      <input type="collection" name="DatatableResult" expr="[Report]" />
      <input type="text" name="Filepath" expr="[FilepathXLSX]" />
      <input type="text" name="SheetName" expr="[SheetName]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
    </outputs>
    <onsuccess>d0c941a8-7e1a-4167-b165-ae1bae998acf</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
 
try
{
DataTableToExcel exporter = new DataTableToExcel();
exporter.ExportToExcel(DatatableResult, Filepath, SheetName);
Success = true;
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
}]]></code>
  </stage>
  <stage stageid="e7d0172b-74d8-4261-a310-976261f28845" name="[CreateExcel] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="225" />
    <decision expression="[CreateExcel] = True" />
    <ontrue>61a50860-9a73-4cbd-8307-274af6b1cff2</ontrue>
    <onfalse>e5242eaf-7df1-445a-be16-359ff2906db9</onfalse>
  </stage>
  <stage stageid="e5242eaf-7df1-445a-be16-359ff2906db9" name="Anchor1" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="225" w="10" h="10" />
    <onsuccess>6f7ea0aa-ca84-4bc5-b2cb-b00a3f5a11c1</onsuccess>
  </stage>
  <stage stageid="c5eff911-30f7-4e00-bf78-bd2290970310" name="Anchor2" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="405" w="10" h="10" />
    <onsuccess>d1c909a0-c003-41a6-8d16-835dcb19fd7c</onsuccess>
  </stage>
  <stage stageid="d2f2e471-9a3f-4b66-b87a-c3d790bfbcb0" name="SheetName" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="ad69bfb0-e52b-47f0-a648-5f1e094fcf37" name="[Success] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="45" />
    <decision expression="[Success] = True" />
    <ontrue>767269e4-2267-4297-a3c9-d97409e501de</ontrue>
    <onfalse>c0a9ca49-ff4e-441d-9650-da100bcf6bad</onfalse>
  </stage>
  <stage stageid="d88bcda0-42b4-4302-ba8c-e642949c5a69" name="[Success] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="165" />
    <decision expression="[Success] = True" />
    <ontrue>e7d0172b-74d8-4261-a310-976261f28845</ontrue>
    <onfalse>04d9f303-e399-4bb3-93fa-3ce536aa55a1</onfalse>
  </stage>
  <stage stageid="c0a9ca49-ff4e-441d-9650-da100bcf6bad" name="Set Error Message" type="Calculation">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="30" y="45" />
    <onsuccess>0f46bfec-9fc3-4b45-9073-b2a2feebbbe3</onsuccess>
    <calculation expression="&quot;Error in API Call: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="04d9f303-e399-4bb3-93fa-3ce536aa55a1" name="Set Error Message" type="Calculation">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="30" y="165" />
    <onsuccess>93dfc5a7-0afe-4188-9a40-fb7de4b3b993</onsuccess>
    <calculation expression="&quot;Error in JSON Conversion: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="d0c941a8-7e1a-4167-b165-ae1bae998acf" name="[Success] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="345" />
    <decision expression="[Success] = True" />
    <ontrue>d1c909a0-c003-41a6-8d16-835dcb19fd7c</ontrue>
    <onfalse>33d8e73b-b167-41a9-8606-25057a305ddc</onfalse>
  </stage>
  <stage stageid="33d8e73b-b167-41a9-8606-25057a305ddc" name="Set Error Message" type="Calculation">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="30" y="345" />
    <onsuccess>6f7ea0aa-ca84-4bc5-b2cb-b00a3f5a11c1</onsuccess>
    <calculation expression="&quot;Error in Create Excel: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="0f46bfec-9fc3-4b45-9073-b2a2feebbbe3" name="Anchor3" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="45" w="10" h="10" />
    <onsuccess>93dfc5a7-0afe-4188-9a40-fb7de4b3b993</onsuccess>
  </stage>
  <stage stageid="93dfc5a7-0afe-4188-9a40-fb7de4b3b993" name="Anchor4" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="165" w="10" h="10" />
    <onsuccess>e5242eaf-7df1-445a-be16-359ff2906db9</onsuccess>
  </stage>
  <stage stageid="6f7ea0aa-ca84-4bc5-b2cb-b00a3f5a11c1" name="Anchor5" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="345" w="10" h="10" />
    <onsuccess>c5eff911-30f7-4e00-bf78-bd2290970310</onsuccess>
  </stage>
  <stage stageid="10d34c6c-df8b-4d15-9a20-6ae62ee2bb83" name="Call API Token" type="Code">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <inputs>
      <input type="text" name="databricksUrl" expr="[databricksUrl]" />
      <input type="text" name="accessToken" expr="[accessToken]" />
      <input type="text" name="warehouseId" expr="[warehouseId]" />
      <input type="text" name="sqlQuery" expr="[sqlQuery]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="text" name="JSONOutput" stage="JSONOutput" />
    </outputs>
    <onsuccess>ad69bfb0-e52b-47f0-a648-5f1e094fcf37</onsuccess>
    <code><![CDATA[
// Assumo che tu abbia già queste variabili in scope:
// string databricksUrl;    // es: "https://<workspace>/api/2.0/sql/statements"
// string accessToken;      // Bearer token del Service Principal
// string sqlQuery;
// string warehouseId;
 
Success = false;
ErrorMessage = "";
JSONOutput = "";
 
// == Impostazione opzionale ==
bool fetchAllChunks = true;          // true = unisce tutti i chunk; false = solo chunk 0
TimeSpan overallTimeout = TimeSpan.FromMinutes(10);
int delayMs = 500;
int maxDelayMs = 5000;
 
// --- Helper locali identici ai tuoi ---
string ExtractDetail(JObject j)
{
    if (j == null) return null;
    return (string)j["status"]?["error"]?["message"]
           ?? (string)j["error"]?["message"]
           ?? (string)j["status"]?["error"]?["code"]
           ?? (string)j["error"]?["code"];
}
 
string ExtractSqlState(JObject j)
{
    if (j == null) return null;
    return (string)j["status"]?["error"]?["sql_state"]
           ?? (string)j["error"]?["sql_state"];
}
 
void BuildShortErrorFromHttp(string forcedState, HttpResponseMessage resp, string body, out string errorMessage)
{
    var stateLabel = string.IsNullOrWhiteSpace(forcedState) ? "FAILED" : forcedState;
 
    string detail = null;
    string sqlState = null;
 
    try
    {
        var j = string.IsNullOrWhiteSpace(body) ? null : JObject.Parse(body);
        detail = ExtractDetail(j);
        sqlState = ExtractSqlState(j);
    }
    catch { /* ignore parse errors */ }
 
    if (string.IsNullOrEmpty(detail))
        detail = $"HTTP {(int)resp.StatusCode} {resp.ReasonPhrase}";
    if (string.IsNullOrEmpty(sqlState))
        sqlState = "N/A";
 
    errorMessage = $"State: {stateLabel} - Detail: {detail} - SQLSTATE: {sqlState}";
}
 
// --- Funzioni locali per i chunk ---
(bool ok, JObject resultObj, string err) FetchAndMergeChunks(
    HttpClient httpClient,
    string baseUrl,
    string statementId,
    int totalChunks,
    bool fetchAll)
{
    try
    {
        var allRows = new JArray();
        int chunksToFetch = fetchAll ? totalChunks : Math.Min(1, totalChunks);
 
        for (int i = 0; i < chunksToFetch; i++)
        {
            var url = $"{baseUrl.TrimEnd('/')}/{statementId}/result?chunk_index={i}";
            var resp = httpClient.GetAsync(url).GetAwaiter().GetResult();
            var body = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
            if (!resp.IsSuccessStatusCode)
            {
                BuildShortErrorFromHttp("FAILED", resp, body, out var errMsg);
                return (false, null, errMsg);
            }
 
            JObject chunk = null;
            try { chunk = JObject.Parse(body); } catch { }
 
            if (chunk == null)
            {
                return (false, null, "State: FAILED - Detail: Impossibile interpretare la risposta dei risultati come JSON. - SQLSTATE: N/A");
            }
 
            var rows = (JArray)chunk["data_array"] ?? new JArray();
            foreach (var r in rows) allRows.Add(r);
        }
 
        var resultObj = new JObject
        {
            ["chunk_index"] = 0,
            ["row_offset"] = 0,
            ["row_count"] = allRows.Count,
            ["data_array"] = allRows
        };
 
        return (true, resultObj, null);
    }
    catch (Exception ex)
    {
        return (false, null, $"State: FAILED - Detail: {ex.Message} - SQLSTATE: N/A");
    }
}
 
try
{
    using (var httpClient = new HttpClient())
    {
        httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
 
        // 1) POST: crea statement (senza attendere)
        var payload = new
        {
            statement = sqlQuery,
            warehouse_id = warehouseId,
            wait_timeout = "0s",
            format = "JSON_ARRAY" // così hai data_array coerente col primo metodo
        };
 
        var content = new StringContent(
            JsonConvert.SerializeObject(payload),
            Encoding.UTF8,
            "application/json"
        );
 
        var postResp = httpClient.PostAsync(databricksUrl, content).GetAwaiter().GetResult();
        var postBody = postResp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
        if (!postResp.IsSuccessStatusCode)
        {
            BuildShortErrorFromHttp("FAILED", postResp, postBody, out ErrorMessage);
            Success = false;
            return;
        }
 
        JObject postJson = null;
        try { postJson = JObject.Parse(postBody); } catch { }
 
        if (postJson == null)
        {
            ErrorMessage = "State: FAILED - Detail: Impossibile interpretare la risposta POST come JSON. - SQLSTATE: N/A";
            Success = false;
            return;
        }
 
        var statementId = (string)postJson["statement_id"];
        var stateImmediate = (string)postJson["status"]?["state"];
 
        if (string.IsNullOrEmpty(statementId))
        {
            // Caso finale immediato
            if (!"SUCCEEDED".Equals(stateImmediate, StringComparison.OrdinalIgnoreCase))
            {
                var detail = ExtractDetail(postJson) ?? "N/A";
                var sqlState = ExtractSqlState(postJson) ?? "N/A";
                ErrorMessage = $"State: {stateImmediate ?? "FAILED"} - Detail: {detail} - SQLSTATE: {sqlState}";
                Success = false;
                return;
            }
            else
            {
                // Già completo → restituisco l'oggetto come nel primo metodo
                JSONOutput = postJson.ToString(Formatting.None);
                Success = true;
                return;
            }
        }
 
        // 2) Polling fino a stato terminale
        var start = DateTime.UtcNow;
        string finalState = null;
        JObject finalStatement = null;
 
        string[] terminal = { "SUCCEEDED", "FAILED", "CANCELED", "CLOSED" };
 
        while (true)
        {
            var getUrl = $"{databricksUrl.TrimEnd('/')}/{statementId}";
            var getResp = httpClient.GetAsync(getUrl).GetAwaiter().GetResult();
            var getBody = getResp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
            if (!getResp.IsSuccessStatusCode)
            {
                BuildShortErrorFromHttp("FAILED", getResp, getBody, out ErrorMessage);
                Success = false;
                return;
            }
 
            JObject getJson = null;
            try { getJson = JObject.Parse(getBody); } catch { }
 
            if (getJson == null)
            {
                ErrorMessage = "State: FAILED - Detail: Impossibile interpretare la risposta GET polling come JSON. - SQLSTATE: N/A";
                Success = false;
                return;
            }
 
            finalStatement = getJson;
            var state = (string)getJson["status"]?["state"];
 
            if (!string.IsNullOrWhiteSpace(state) && terminal.Contains(state.ToUpperInvariant()))
            {
                finalState = state;
                break;
            }
 
            if (DateTime.UtcNow - start > overallTimeout)
            {
                ErrorMessage = $"State: FAILED - Detail: Polling scaduto dopo {overallTimeout.TotalSeconds:N0} secondi. - SQLSTATE: N/A";
                Success = false;
                return;
            }
 
            Thread.Sleep(delayMs);
            delayMs = Math.Min(delayMs * 2, maxDelayMs);
        }
 
        // 3) Esito finale
        if (!"SUCCEEDED".Equals(finalState, StringComparison.OrdinalIgnoreCase))
        {
            var detail = ExtractDetail(finalStatement) ?? "N/A";
            var sqlState = ExtractSqlState(finalStatement) ?? "N/A";
            ErrorMessage = $"State: {finalState} - Detail: {detail} - SQLSTATE: {sqlState}";
            Success = false;
            return;
        }
 
        // 4) Recupero risultati e innesto in finalStatement
        JToken resultToken = finalStatement["result"];
 
        // Quanti chunk ci sono?
        int totalChunks = 1;
        try
        {
            totalChunks = (int?)finalStatement["manifest"]?["total_chunk_count"] ?? 1;
        }
        catch { /* ignore */ }
 
        if (resultToken == null)
        {
            // Non c'è result nello statement → scarico i chunk e UNISCO
            var (ok, resultObj, err) = FetchAndMergeChunks(httpClient, databricksUrl, statementId, totalChunks, fetchAllChunks);
            if (!ok)
            {
                ErrorMessage = err;
                Success = false;
                return;
            }
 
            finalStatement["result"] = resultObj;
        }
        else
        {
            // C'è già result nello statement.
            // Se vuoi essere sicuro di avere TUTTO, e ci sono più chunk, li recupero e unisco.
            if (fetchAllChunks && totalChunks > 1)
            {
                // Riparto da zero e unisco tutti i chunk (più semplice e robusto)
                var (ok, resultObj, err) = FetchAndMergeChunks(httpClient, databricksUrl, statementId, totalChunks, fetchAllChunks);
                if (!ok)
                {
                    ErrorMessage = err;
                    Success = false;
                    return;
                }
                finalStatement["result"] = resultObj;
            }
        }
 
        // Allineo counters (opzionale)
        try
        {
            var dataArray = finalStatement["result"]?["data_array"] as JArray;
            if (dataArray != null)
            {
                finalStatement["result"]["row_count"] = dataArray.Count;
                finalStatement["result"]["chunk_index"] = 0;
                finalStatement["result"]["row_offset"] = 0;
            }
        }
        catch { /* ignore */ }
 
        // 5) OUTPUT: restituisco lo statement completo (come nel primo metodo)
        JSONOutput = finalStatement.ToString(Formatting.None);
        Success = true;
    }
}
catch (Exception ex)
{
    Success = false;
    ErrorMessage = $"State: FAILED - Detail: {ex.Message} - SQLSTATE: N/A";
}
]]></code>
  </stage>
  <stage stageid="cb0efb85-278a-4cf1-a80d-b99a7b2439f8" name="databricksUrl" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="33ac5062-313a-408b-a962-0eb8eab9b6a4" name="Authentication" type="SubSheet">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <outputs>
      <output type="text" name="AccessToken" friendlyname="AccessToken" stage="accessToken" />
    </outputs>
    <onsuccess>10d34c6c-df8b-4d15-9a20-6ae62ee2bb83</onsuccess>
    <processid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</processid>
  </stage>
</process>

thank you so much for sharing this, one more thing is , if we have large data set then API gives results in chunks, 

currently i am checking total chunk count and looping based on that to fetch , store data chunk wise

The chunk part was already handled in the Execute Query action with Service Principal. I also updated the execute query part with a personal token and added memory management with garbage collection.

 

<process name="VBO - Databricks" version="1.0" bpversion="7.1.0.62280" narrative="" byrefcollection="true" type="object" runmode="Exclusive" preferredid="a89c02ea-a295-4848-b504-a2afe92a5e7e">
  <appdef>
    <element name="Application Root">
      <id>a1fcd9f9-3d26-4f70-bcc0-8406d53a2f05</id>
      <type>Application</type>
      <basetype>Application</basetype>
      <datatype>unknown</datatype>
      <diagnose>False</diagnose>
    </element>
  </appdef>
  <view>
    <camerax>0</camerax>
    <cameray>0</cameray>
    <zoom version="2">1.25</zoom>
  </view>
  <preconditions />
  <endpoint narrative="" />
  <subsheet subsheetid="7d7495b5-ea2b-424a-aae0-3c029b92f2a9" type="CleanUp" published="True">
    <name>Clean Up</name>
    <view>
      <camerax>0</camerax>
      <cameray>0</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="9cb7bf9b-8ba4-45f2-b334-fc34ba39707a" type="Normal" published="False">
    <name>Authentication</name>
    <view>
      <camerax>0</camerax>
      <cameray>33</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="8e5d6e27-2067-4108-a3de-c6d32385476c" type="Normal" published="True">
    <name>Execute Query</name>
    <view>
      <camerax>-111</camerax>
      <cameray>31</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <subsheet subsheetid="fa900c6f-ef74-4b80-a2e5-f70e341de476" type="Normal" published="True">
    <name>Execute Query - Using Token</name>
    <view>
      <camerax>-111</camerax>
      <cameray>18</cameray>
      <zoom version="2">1.25</zoom>
    </view>
  </subsheet>
  <stage stageid="ea40e4ef-0578-444a-93d6-cf433c09b609" name="Start" type="Start">
    <loginhibit />
    <display x="15" y="-105" />
    <onsuccess>1fba778b-777b-4bb2-8996-1fc62fc8fcd2</onsuccess>
  </stage>
  <stage stageid="1fba778b-777b-4bb2-8996-1fc62fc8fcd2" name="End" type="End">
    <loginhibit />
    <display x="15" y="90" />
  </stage>
  <stage stageid="f34ba72c-ee5c-4e66-9417-d7196dd2134a" name="Stage1" type="ProcessInfo">
    <display x="-195" y="-105" w="150" h="90" />
    <references>
      <reference>System.dll</reference>
      <reference>System.Data.dll</reference>
      <reference>System.Xml.dll</reference>
      <reference>System.Drawing.dll</reference>
      <reference>System.Net.Http.dll</reference>
      <reference>EPPlus.dll</reference>
      <reference>ClosedXML.dll</reference>
      <reference>netstandard.dll</reference>
      <reference>System.Core.dll</reference>
      <reference>System.Net.Http.Json.dll</reference>
      <reference>Newtonsoft.Json.dll</reference>
      <reference>System.Windows.Forms.dll</reference>
      <reference>System.Net.Http.dll</reference>
      <reference>System.Threading.dll</reference>
      <reference>System.Net.dll</reference>
    </references>
    <imports>
      <import>System</import>
      <import>System.Drawing</import>
      <import>System.Data</import>
      <import>System.Net.Http</import>
      <import>System.Text</import>
      <import>ClosedXML.Excel</import>
      <import>OfficeOpenXml</import>
      <import>System.Collections.Generic</import>
      <import>System.Linq</import>
      <import>System.IO</import>
      <import>Newtonsoft.Json</import>
      <import>Newtonsoft.Json.Linq</import>
      <import>System.Windows.Forms</import>
      <import>System.Net.Http.Headers</import>
      <import>System.Threading</import>
      <import>System.Net</import>
    </imports>
    <language>csharp</language>
    <globalcode><![CDATA[]]></globalcode>
    <code><![CDATA[public class DataTableToExcel
{
    public void ExportToExcel(DataTable dataTable, string filePath, string sheet)
    {
        // Configura EPPlus per usare la licenza non commerciale
 
        using (var package = new ExcelPackage())
        {
            var worksheet = package.Workbook.Worksheets.Add(sheet);
 
            // Aggiungi le intestazioni delle colonne
            for (int col = 0; col < dataTable.Columns.Count; col++)
            {
                worksheet.Cells[1, col + 1].Value = dataTable.Columns[col].ColumnName;
            }
 
            // Aggiungi i dati delle righe
            worksheet.Cells["A2"].LoadFromDataTable(dataTable, false);
 
            // Salva il file Excel
            var fileInfo = new FileInfo(filePath);
            package.SaveAs(fileInfo);
        }
    }
}
 
 
 
public class Column
{
    public string name { get; set; }
}
 
public class Row
{
    public List<string> values { get; set; }
}
 
public class JsonResponse
{
    public List<Column> columns { get; set; }
    public List<Row> rows { get; set; }
}
 
 
 
 
 public static DataTable GenerateDataTableFromJson(string json)
    {
        var jsonObject = JObject.Parse(json);
 
        // 1) Trova data_array (in result.data_array o alla root)
        var dataArrayToken =
            jsonObject.SelectToken("result.data_array") ??
            jsonObject.SelectToken("data_array");
 
        if (dataArrayToken == null || dataArrayToken.Type != JTokenType.Array)
            throw new InvalidOperationException("Nessun 'data_array' trovato nel JSON.");
 
        var dataArray = (JArray)dataArrayToken;
 
        // 2) Prova a leggere lo schema colonne se presente
        var columnsToken = jsonObject.SelectToken("manifest.schema.columns") as JArray;
        var columnNames = new List<string>();
        int maxCols;
 
        if (columnsToken != null && columnsToken.Count > 0)
        {
            foreach (var col in columnsToken)
            {
                // Cerca proprietà comuni per il nome colonna
                var name = (string)(col["name"] ?? col["title"] ?? col["id"]);
                if (string.IsNullOrWhiteSpace(name))
                    name = $"Col{columnNames.Count + 1}";
                columnNames.Add(name);
            }
            maxCols = columnNames.Count;
        }
        else
        {
            // 3) Nessuno schema: ricava il numero max di colonne dalle righe
            maxCols = dataArray
                .Select(r => (r as JArray)?.Count ?? 0)
                .DefaultIfEmpty(0)
                .Max();
 
            for (int i = 1; i <= maxCols; i++)
                columnNames.Add($"Col{i}");
        }
 
        // 4) Crea il DataTable e popola i dati
        var table = new DataTable();
        foreach (var name in columnNames)
            table.Columns.Add(name, typeof(string));
 
        foreach (var rowToken in dataArray)
        {
            var rowArray = rowToken as JArray;
            if (rowArray == null)
                continue; // oppure: throw, se vuoi essere più rigido
 
            var dr = table.NewRow();
 
            for (int i = 0; i < maxCols; i++)
            {
                if (i < rowArray.Count)
                {
                    var cell = rowArray[i];
                    // Se vuoi stringa vuota al posto del DBNull:
                    // dr[i] = cell.Type == JTokenType.Null ? "" : (string)cell;
                    dr[i] = cell.Type == JTokenType.Null ? (object)DBNull.Value : (string)cell;
                }
                else
                {
                    dr[i] = DBNull.Value;
                }
            }
 
            table.Rows.Add(dr);
        }
 
        return table;
    }
 
 
public static string EscapeForJson(string s)
    {
        if (s == null) return "";
        return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
    }
 
 
 
 
public static (string StatementId, string State) ParseWithNewtonsoft(string json)
{
    var jo = JObject.Parse(json);
 
    string statementId = (string)jo["statement_id"];
    string state = (string)jo["status"]?["state"];
 
    return (statementId, state);
}
]]></code>
  </stage>
  <stage stageid="ce38e7ce-fcf1-4e63-bd70-44a2babf6a61" name="Clean Up" type="SubSheetInfo">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="6638295e-5a03-4485-84f4-0be42967f3be" name="Start" type="Start">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <loginhibit />
    <display x="15" y="-105" />
    <onsuccess>9e0c8841-53a6-4607-ad35-b1d9f8b571d7</onsuccess>
  </stage>
  <stage stageid="9e0c8841-53a6-4607-ad35-b1d9f8b571d7" name="End" type="End">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <loginhibit />
    <display x="15" y="90" />
  </stage>
  <stage stageid="a7ffe0ff-6bed-410a-8542-627fac718426" name="Execute Query" type="SubSheetInfo">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="7f49a903-a4c5-4669-b7e3-c8b900154c68" name="Start" type="Start">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <inputs>
      <input type="text" name="baseUrl" stage="baseUrl" />
      <input type="text" name="accessToken" stage="accessToken" />
      <input type="text" name="warehouseId" stage="warehouseId" />
      <input type="text" name="sqlQuery" stage="sqlQuery" />
      <input type="flag" name="CreateExcel" stage="CreateExcel" />
      <input type="text" name="FilepathXLSX" stage="FilepathXLSX" />
      <input type="text" name="SheetName" stage="SheetName" />
    </inputs>
    <onsuccess>d0350ab4-99aa-4763-8c78-887721f4a3a5</onsuccess>
  </stage>
  <stage stageid="6a16195e-41c8-4e52-8625-061243db0811" name="End" type="End">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="345" />
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="Error Message" stage="Error Message" />
      <output type="collection" name="Report" stage="Report" />
    </outputs>
  </stage>
  <stage stageid="882d594d-6359-4c08-9c68-adb46b927cc7" name="" type="Note">
    <subsheetid>7d7495b5-ea2b-424a-aae0-3c029b92f2a9</subsheetid>
    <narrative>Clean Up Page
 
This is an optional page where you might choose to perform some finalisation (or "cleanup") tasks as your business object is closed down.
 
The cleanup action will be called automatically immediately after closing your business object at the end of a business process.
 
You will not be able to call this action from a business process, nor will it be called at any other time than before the disposal of the business object.</narrative>
    <display x="-180" y="60" w="180" h="230" />
  </stage>
  <stage stageid="211de8a5-16c7-48bf-bff2-3e8859a8d124" name="" type="Note">
    <narrative>Initialise Page
 
This is an optional page where you might choose to perform some initialisation tasks after your business object is loaded.
 
The initialise action will be called automatically immediately after loading your business object.
 
You will not be able to call this action from a business process, nor will it be called at any other time than after the creation of the object.</narrative>
    <display x="-180" y="60" w="180" h="230" />
  </stage>
  <stage stageid="d0350ab4-99aa-4763-8c78-887721f4a3a5" name="Call API" type="Code">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <inputs>
      <input type="text" name="baseUrl" expr="[baseUrl]" />
      <input type="text" name="accessToken" expr="[accessToken]" />
      <input type="text" name="warehouseId" expr="[warehouseId]" />
      <input type="text" name="sqlQuery" expr="[sqlQuery]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="text" name="JSONOutput" stage="JSONOutput" />
    </outputs>
    <onsuccess>5555a2e6-2d06-4da2-b986-60516c04f8c4</onsuccess>
    <code><![CDATA[
// ======================
// OUTPUT atteso
// ======================
Success = false;
ErrorMessage = "";
JSONOutput = "";
 
// ======================
// Impostazioni
// ======================
 
// Disposition: "INLINE" (data_array) o "EXTERNAL_LINKS" (URL SAS)
string disposition = "INLINE";
// Formato risultati: con INLINE supportato "JSON_ARRAY"
string format = "JSON_ARRAY";
 
// Polling
TimeSpan pollingTimeout = TimeSpan.FromMinutes(10);
int initialDelayMs = 500;
int maxDelayMs = 5000;
 
// ======================
// Helper errori/estrazione
// ======================
string ExtractDetail(JObject j)
{
    if (j == null) return null;
    return (string)j["status"]?["error"]?["message"]
           ?? (string)j["error"]?["message"]
           ?? (string)j["status"]?["error"]?["code"]
           ?? (string)j["error"]?["code"];
}
 
string ExtractSqlState(JObject j)
{
    if (j == null) return null;
    return (string)j["status"]?["error"]?["sql_state"]
           ?? (string)j["error"]?["sql_state"];
}
 
void BuildShortErrorFromHttp(string forcedState, HttpResponseMessage resp, string body, out string errorMessage)
{
    var stateLabel = string.IsNullOrWhiteSpace(forcedState) ? "FAILED" : forcedState;
 
    string detail = null;
    string sqlState = null;
 
    try
    {
        var j = string.IsNullOrWhiteSpace(body) ? null : JObject.Parse(body);
        detail = ExtractDetail(j);
        sqlState = ExtractSqlState(j);
    }
    catch { /* ignore parse errors */ }
 
    if (string.IsNullOrEmpty(detail))
        detail = $"HTTP {(int)resp.StatusCode} {resp.ReasonPhrase}";
    if (string.IsNullOrEmpty(sqlState))
        sqlState = "N/A";
 
    errorMessage = $"State: {stateLabel} - Detail: {detail} - SQLSTATE: {sqlState}";
}
 
// ======================
// Fetch di un CHUNK (INLINE o EXTERNAL_LINKS) → ritorna JArray righe del chunk
// ======================
(bool ok, JArray rows, string err) FetchChunkRows(
    HttpClient clientWithAuth,
    string apiBaseUrl,
    string token,
    string statementId,
    int chunkIndex)
{
    try
    {
        var url = $"{apiBaseUrl.TrimEnd('/')}/{statementId}/result/chunks/{chunkIndex}";
        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.Add("Authorization", $"Bearer {token}");
 
        var resp = clientWithAuth.SendAsync(req).Result;
        var body = resp.Content.ReadAsStringAsync().Result;
 
        if (!resp.IsSuccessStatusCode)
        {
            BuildShortErrorFromHttp("FAILED", resp, body, out var msg);
            return (false, null, msg);
        }
 
        var chunk = JObject.Parse(body);
 
        // Caso INLINE: data_array in root
        if (chunk["data_array"] is JArray inlineRows)
        {
            return (true, inlineRows, null);
        }
 
        // Caso EXTERNAL_LINKS: scarico i link SAS (ognuno è un JSON array)
        if (chunk["external_links"] is JArray links)
        {
            var merged = new JArray();
 
            foreach (var l in links)
            {
                var link = (string)l["external_link"];
                if (string.IsNullOrEmpty(link)) continue;
 
                using (var http = new HttpClient()) // NO Authorization header per SAS
                {
                    var dl = http.GetStringAsync(link).Result; // contenuto JSON_ARRAY
                    var jarr = JArray.Parse(dl);
 
                    foreach (var r in jarr) merged.Add(r);
 
                    // libera presto memoria intermedia
                    jarr.Clear();
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                }
            }
 
            return (true, merged, null);
        }
 
        return (false, null, "State: FAILED - Detail: Nessun 'data_array' o 'external_links' nel chunk. - SQLSTATE: N/A");
    }
    catch (Exception ex)
    {
        return (false, null, $"State: FAILED - Detail: {ex.Message} - SQLSTATE: N/A");
    }
}
 
// ======================
// MAIN FLOW
// ======================
HttpClient client = new HttpClient();
 
try
{
    // ====== POST (asincrono): wait_timeout=0s → ritorna subito statement_id ======
    var payload = new
    {
        statement = sqlQuery,
        warehouse_id = warehouseId,
        wait_timeout = "0s",
        disposition = disposition,
        format = format
    };
 
    var content = new StringContent(
        JsonConvert.SerializeObject(payload),
        Encoding.UTF8,
        "application/json"
    );
 
    var postReq = new HttpRequestMessage(HttpMethod.Post, baseUrl);
    postReq.Headers.Add("Authorization", $"Bearer {accessToken}");
    postReq.Content = content;
 
    var postResp = client.SendAsync(postReq).Result;
    var postBody = postResp.Content.ReadAsStringAsync().Result;
 
    if (!postResp.IsSuccessStatusCode)
    {
        BuildShortErrorFromHttp("FAILED", postResp, postBody, out ErrorMessage);
        Success = false;
        return;
    }
 
    JObject jo = null;
    try { jo = JObject.Parse(postBody); } catch { }
 
    if (jo == null)
    {
        Success = false;
        ErrorMessage = "State: FAILED - Detail: Impossibile interpretare la risposta POST come JSON. - SQLSTATE: N/A";
        return;
    }
 
    var statementId = (string)jo["statement_id"];
    var state = (string)jo["status"]?["state"];
 
    // Stati terminali previsti
    string[] terminal = { "SUCCEEDED", "FAILED", "CANCELED", "CLOSED" };
 
    bool needsPolling =
        !string.IsNullOrWhiteSpace(statementId) &&
        (string.IsNullOrWhiteSpace(state) || Array.IndexOf(terminal, state?.ToUpperInvariant()) < 0);
 
    // ====== POLLING ======
    JObject finalObj = jo;
    if (needsPolling)
    {
        DateTime start = DateTime.UtcNow;
        int delayMs = initialDelayMs;
        string getUrl = baseUrl.TrimEnd('/') + "/" + statementId;
 
        while (true)
        {
            if (DateTime.UtcNow - start > pollingTimeout)
            {
                Success = false;
                ErrorMessage = "State: FAILED - Detail: Polling scaduto lato client. - SQLSTATE: N/A";
                return;
            }
 
            Thread.Sleep(delayMs);
            delayMs = Math.Min(delayMs * 2, maxDelayMs);
 
            var pollReq = new HttpRequestMessage(HttpMethod.Get, getUrl);
            pollReq.Headers.Add("Authorization", $"Bearer {accessToken}");
 
            var pollResp = client.SendAsync(pollReq).Result;
            var pollBody = pollResp.Content.ReadAsStringAsync().Result;
 
            if (pollResp.StatusCode == HttpStatusCode.NotFound)
            {
                Success = false;
                ErrorMessage = "State: FAILED - Detail: Statement non trovato o scaduto. - SQLSTATE: N/A";
                return;
            }
 
            if (!pollResp.IsSuccessStatusCode)
            {
                BuildShortErrorFromHttp("FAILED", pollResp, pollBody, out ErrorMessage);
                Success = false;
                return;
            }
 
            JObject p = null;
            try { p = JObject.Parse(pollBody); } catch { p = null; }
 
            if (p == null) continue;
 
            finalObj = p;
            state = (string)p["status"]?["state"];
            if (string.IsNullOrWhiteSpace(state)) continue;
 
            var upper = state.ToUpperInvariant();
            if (Array.IndexOf(terminal, upper) >= 0)
            {
                if (upper != "SUCCEEDED")
                {
                    Success = false;
                    var detail = ExtractDetail(p) ?? $"State={upper}";
                    var sqlState = ExtractSqlState(p) ?? "N/A";
                    ErrorMessage = $"State: {upper} - Detail: {detail} - SQLSTATE: {sqlState}";
                    return;
                }
                break; // SUCCEEDED
            }
        }
    }
 
    // ====== STATO: SUCCEEDED → costruzione JSONOutput in streaming ======
    int totalChunks = (int?)finalObj["manifest"]?["total_chunk_count"] ?? 1;
 
    // Builder per l'output finale (evita JArray globale in RAM)
    var sb = new StringBuilder(1024 * 1024); // 1MB iniziale; cresce se serve
 
    // Header JSON (mettiamo row_count dopo aver scritto tutte le righe)
    sb.Append("{\"statement_id\":\"")
      .Append((string)finalObj["statement_id"])
      .Append("\",\"status\":\"SUCCEEDED\",\"manifest\":{\"total_chunk_count\":")
      .Append(totalChunks)
      .Append("},\"result\":{\"chunk_index\":0,\"row_offset\":0,\"data_array\":[");
 
    bool firstRow = true;
    long rowCount = 0;
 
    // Funzione locale: appende righe e libera memoria del chunk
    void AppendRows(JArray rows)
    {
        foreach (var r in rows)
        {
            if (!firstRow) sb.Append(',');
            sb.Append(r.ToString(Newtonsoft.Json.Formatting.None));
            firstRow = false;
            rowCount++;
        }
 
        // libera memoria del chunk
        rows.Clear();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
 
    // 1) Se il polling ha fornito già il primo chunk in finalObj.result.data_array → usalo
    if (finalObj["result"]?["data_array"] is JArray chunk0)
    {
        AppendRows(chunk0);
    }
    else
    {
        // Altrimenti: scarica il chunk 0
        var (ok0, rows0, err0) = FetchChunkRows(client, baseUrl, accessToken, statementId, 0);
        if (!ok0)
        {
            Success = false;
            ErrorMessage = err0;
            return;
        }
        AppendRows(rows0);
    }
 
    // 2) Scarica e appende i chunk successivi (1..N-1)
    for (int i = 1; i < totalChunks; i++)
    {
        var (ok, rows, err) = FetchChunkRows(client, baseUrl, accessToken, statementId, i);
        if (!ok)
        {
            Success = false;
            ErrorMessage = err;
            return;
        }
        AppendRows(rows);
    }
 
    // Chiudi array e inserisci row_count
    sb.Append("],\"row_count\":")
      .Append(rowCount)
      .Append("}}");
 
    // Assegna alla variabile di output richiesta
    JSONOutput = sb.ToString();
    Success = true;
}
catch (Exception ex)
{
    Success = false;
    ErrorMessage = $"State: FAILED - Detail: {ex.Message} - SQLSTATE: N/A";
}
finally
{
    client?.Dispose();
}
]]></code>
  </stage>
  <stage stageid="8e8901e4-0469-467c-ba32-ef8872e06bd4" name="baseUrl" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="30" />
    <datatype>text</datatype>
    <initialvalue xml:space="preserve">https://westeurope.azuredatabricks.net/api/2.0/sql/statements</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="dccd3340-c59f-4c07-9cf0-6b2158a0b15c" name="accessToken" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="a37d696c-f22a-42d2-926e-972ba737ca40" name="warehouseId" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="bd29c410-6809-46ee-ba90-4913e5c23721" name="sqlQuery" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23e4c765-3cad-483e-b764-22c0b9fa4986" name="Success" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="360" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="c9bef68e-b917-4f91-a8f3-850be2935e93" name="Error Message" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="360" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="a4f71191-c150-4bc5-a71c-1407b88ea104" name="JSONOutput" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="195" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23680203-a356-4f4a-9aa5-11ec8d787164" name="Report" type="Collection">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="405" />
    <datatype>collection</datatype>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="2b95b981-f6a3-46d2-ac2f-c3bf37e109aa" name="Convert JSON" type="Code">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="45" />
    <inputs>
      <input type="text" name="json" expr="[JSONOutput]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="collection" name="dataTable" stage="Report" />
    </outputs>
    <onsuccess>081c9bf2-4828-47ba-9f88-ee45a75f8735</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
dataTable = null;
dataTable = new DataTable();
 
try
{
dataTable = GenerateDataTableFromJson(json);
Success = true;
GC.Collect();
GC.WaitForPendingFinalizers();
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
GC.Collect();
    GC.WaitForPendingFinalizers();
}
 
]]></code>
  </stage>
  <stage stageid="6f826340-a4b0-4a90-a21b-b7e83d2c3b64" name="Inputs" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="195" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="fc658c67-57fc-4741-81e3-420dee311aad" name="Local" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="165" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="69e8baa0-51a9-4cb0-8102-628d715d54fd" name="Outputs" type="Block">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-270" y="330" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="3d0bd6c8-bfea-4f19-b1f0-17af6082fd95" name="FilepathXLSX" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-165" y="75" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="6fce1fed-aed7-4be8-98a7-47cd4d387274" name="CreateExcel" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="75" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="4a1251fb-c3ba-49fa-82e5-4364fec79357" name="Create Excel" type="Code">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="225" />
    <inputs>
      <input type="collection" name="DatatableResult" expr="[Report]" />
      <input type="text" name="Filepath" expr="[FilepathXLSX]" />
      <input type="text" name="SheetName" expr="[SheetName]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
    </outputs>
    <onsuccess>943d0920-2cf6-4da3-9b48-9a5af41f847f</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
 
try
{
DataTableToExcel exporter = new DataTableToExcel();
exporter.ExportToExcel(DatatableResult, Filepath, SheetName);
Success = true;
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
}]]></code>
  </stage>
  <stage stageid="3f15a13e-c6d9-4309-b03c-b84634339a44" name="[CreateExcel] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="165" />
    <decision expression="[CreateExcel] = True" />
    <ontrue>4a1251fb-c3ba-49fa-82e5-4364fec79357</ontrue>
    <onfalse>aff1fb43-83dd-411b-895e-121a6e9aaa67</onfalse>
  </stage>
  <stage stageid="aff1fb43-83dd-411b-895e-121a6e9aaa67" name="Anchor1" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="165" w="10" h="10" />
    <onsuccess>de02cbf6-bc09-4036-9c44-61fe6f59330b</onsuccess>
  </stage>
  <stage stageid="7d038089-d0d0-47d6-9567-ff7fbd4e2ad2" name="Anchor2" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="345" w="10" h="10" />
    <onsuccess>6a16195e-41c8-4e52-8625-061243db0811</onsuccess>
  </stage>
  <stage stageid="8d301f0a-94c3-4852-a216-f252b27166c3" name="SheetName" type="Data">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <display x="-225" y="120" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5555a2e6-2d06-4da2-b986-60516c04f8c4" name="[Success] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <decision expression="[Success] = True" />
    <ontrue>2b95b981-f6a3-46d2-ac2f-c3bf37e109aa</ontrue>
    <onfalse>25ddca11-d42c-4ac1-bfeb-590504da586b</onfalse>
  </stage>
  <stage stageid="081c9bf2-4828-47ba-9f88-ee45a75f8735" name="[Success] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="105" />
    <decision expression="[Success] = True" />
    <ontrue>3f15a13e-c6d9-4309-b03c-b84634339a44</ontrue>
    <onfalse>d9b92af7-75ca-4170-9a32-2981d266cc24</onfalse>
  </stage>
  <stage stageid="25ddca11-d42c-4ac1-bfeb-590504da586b" name="Set Error Message" type="Calculation">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="30" y="-15" />
    <onsuccess>9708dd42-1db3-4ae3-809d-1513db8538f9</onsuccess>
    <calculation expression="&quot;Error in API Call: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="d9b92af7-75ca-4170-9a32-2981d266cc24" name="Set Error Message" type="Calculation">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="30" y="105" />
    <onsuccess>5dc509e2-1dea-4ae3-ae96-918a7a64f060</onsuccess>
    <calculation expression="&quot;Error in JSON Conversion: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="943d0920-2cf6-4da3-9b48-9a5af41f847f" name="[Success] = True" type="Decision">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="-60" y="285" />
    <decision expression="[Success] = True" />
    <ontrue>6a16195e-41c8-4e52-8625-061243db0811</ontrue>
    <onfalse>fc1ca4c2-0045-4d9d-ad88-61abf950a75c</onfalse>
  </stage>
  <stage stageid="fc1ca4c2-0045-4d9d-ad88-61abf950a75c" name="Set Error Message" type="Calculation">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="30" y="285" />
    <onsuccess>de02cbf6-bc09-4036-9c44-61fe6f59330b</onsuccess>
    <calculation expression="&quot;Error in Create Excel: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="9708dd42-1db3-4ae3-809d-1513db8538f9" name="Anchor3" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="-15" w="10" h="10" />
    <onsuccess>5dc509e2-1dea-4ae3-ae96-918a7a64f060</onsuccess>
  </stage>
  <stage stageid="5dc509e2-1dea-4ae3-ae96-918a7a64f060" name="Anchor4" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="105" w="10" h="10" />
    <onsuccess>aff1fb43-83dd-411b-895e-121a6e9aaa67</onsuccess>
  </stage>
  <stage stageid="de02cbf6-bc09-4036-9c44-61fe6f59330b" name="Anchor5" type="Anchor">
    <subsheetid>8e5d6e27-2067-4108-a3de-c6d32385476c</subsheetid>
    <loginhibit />
    <display x="90" y="285" w="10" h="10" />
    <onsuccess>7d038089-d0d0-47d6-9567-ff7fbd4e2ad2</onsuccess>
  </stage>
  <stage stageid="8848462a-85fd-4a3a-bc39-15398ec8475f" name="Authentication" type="SubSheetInfo">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="7f4050b6-af0c-44aa-afa5-b7d0fd9f4f1c" name="Start" type="Start">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <onsuccess>8c424f0b-43e3-49bd-9deb-97c9e5ded003</onsuccess>
  </stage>
  <stage stageid="aace0190-ea26-4a19-9ebe-9048a002664e" name="End" type="End">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <outputs>
      <output type="text" name="AccessToken" stage="AccessToken" />
    </outputs>
  </stage>
  <stage stageid="baa4ea76-41aa-46e6-8a66-4920d8f05d99" name="Inputs" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="195" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="0f8f7e0c-8f73-402c-9601-9443b4f6827e" name="Local" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="165" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="7066e6a3-cb35-4561-bc57-fb6785819432" name="Outputs" type="Block">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-270" y="330" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="79280ca6-6232-452b-b7b2-8810f0ff1547" name="TenantID" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="-15" w="120" h="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5e4e3fc9-859a-4eeb-b1bf-3cfedc42bb8a" name="ClientId" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="30" w="120" h="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="3b5f25be-6542-4ee9-aae5-7e8174743bb3" name="ClientSecret" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-195" y="75" w="120" h="30" />
    <datatype>password</datatype>
    <initialvalueenc>
    </initialvalueenc>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="8c424f0b-43e3-49bd-9deb-97c9e5ded003" name="Get Token" type="Code">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <inputs>
      <input type="text" name="TenantID" expr="[TenantID]" />
      <input type="text" name="ClientId" expr="[ClientId]" />
      <input type="password" name="ClientSecret" expr="[ClientSecret]" />
    </inputs>
    <outputs>
      <output type="text" name="AccessToken" stage="AccessToken" />
    </outputs>
    <onsuccess>aace0190-ea26-4a19-9ebe-9048a002664e</onsuccess>
    <code><![CDATA[
 
        using (var httpClient = new HttpClient())
        {
            var form = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("client_id", ClientId),
                new KeyValuePair<string, string>("client_secret", ClientSecret),
                new KeyValuePair<string, string>("grant_type", "client_credentials"),
                // Scope corretto per Azure Databricks (AAD resource + /.default)
                new KeyValuePair<string, string>("scope", "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d/.default")
            });
 
            var resp = httpClient.PostAsync(tokenUrl, form).GetAwaiter().GetResult();
            string body = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
            if (!resp.IsSuccessStatusCode)
            {
                throw new Exception($"Token request failed ({resp.StatusCode}): {body}");
            }
 
            var json = JObject.Parse(body);
            string accessToken = json["access_token"]?.ToString();
            if (string.IsNullOrEmpty(accessToken))
                throw new Exception("access_token missing in token response");
 
            AccessToken = accessToken;
}
]]></code>
  </stage>
  <stage stageid="46d709ef-5c75-4394-83b0-78471c8c463a" name="AccessToken" type="Data">
    <subsheetid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</subsheetid>
    <display x="-225" y="360" />
    <datatype>text</datatype>
    <initialvalue />
    <alwaysinit />
  </stage>
  <stage stageid="4e76287c-26c7-45de-a165-1f3257d8760f" name="Execute Query - Using Token" type="SubSheetInfo">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-195" y="-105" w="150" h="90" />
  </stage>
  <stage stageid="2d3a1314-19e9-4398-9259-4ec602b70f7e" name="Start" type="Start">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-135" />
    <inputs>
      <input type="text" name="databricksUrl" stage="databricksUrl" />
      <input type="text" name="warehouseId" stage="warehouseId" />
      <input type="text" name="sqlQuery" stage="sqlQuery" />
      <input type="flag" name="CreateExcel" stage="CreateExcel" />
      <input type="text" name="FilepathXLSX" stage="FilepathXLSX" />
      <input type="text" name="SheetName" stage="SheetName" />
    </inputs>
    <onsuccess>33ac5062-313a-408b-a962-0eb8eab9b6a4</onsuccess>
  </stage>
  <stage stageid="d1c909a0-c003-41a6-8d16-835dcb19fd7c" name="End" type="End">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="405" />
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="Error Message" stage="Error Message" />
      <output type="collection" name="Report" stage="Report" />
    </outputs>
  </stage>
  <stage stageid="753bbed9-7194-48db-86f7-2047f2dd218f" name="accessToken" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="195" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="74a6a9eb-ee35-4699-84ad-329239d1058f" name="warehouseId" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5e43ad48-cc52-41bb-89c4-6596788a19dd" name="sqlQuery" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="-15" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="d4f9c706-65f0-4c79-8779-a00d9d9529a3" name="Success" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="360" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="536e4740-8f66-4f28-b651-be23fdef263f" name="Error Message" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="360" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="4b5e9e32-cc4c-468a-b1d1-b96bdfab941f" name="JSONOutput" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="195" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="23766df5-bdba-449f-8d36-858278c8d97e" name="Report" type="Collection">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="405" />
    <datatype>collection</datatype>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="767269e4-2267-4297-a3c9-d97409e501de" name="Convert JSON Token NEW" type="Code">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="105" />
    <inputs>
      <input type="text" name="json" expr="[JSONOutput]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="collection" name="dataTable" stage="Report" />
    </outputs>
    <onsuccess>d88bcda0-42b4-4302-ba8c-e642949c5a69</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
dataTable = null;
dataTable = new DataTable();
 
try
{
dataTable = GenerateDataTableFromJson(json);
Success = true;
GC.Collect();
GC.WaitForPendingFinalizers();
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
GC.Collect();
    GC.WaitForPendingFinalizers();
}
 
]]></code>
  </stage>
  <stage stageid="33c5e16f-fa4e-4750-9e61-14e9f79eb243" name="Inputs" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="-45" w="150" h="195" />
    <font family="Segoe UI" size="10" style="Regular" color="7FB2E5" />
  </stage>
  <stage stageid="ebf2fea7-8b77-44c8-a000-9038213e3e34" name="Local" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="165" w="150" h="150" />
    <font family="Segoe UI" size="10" style="Regular" color="FFFF00" />
  </stage>
  <stage stageid="deb7307c-8680-4762-9dc1-5b7d5600729f" name="Outputs" type="Block">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-270" y="330" w="150" h="105" />
    <font family="Segoe UI" size="10" style="Regular" color="FF00FF" />
  </stage>
  <stage stageid="5529a3ca-016c-44d8-94b1-56377f9b40fd" name="FilepathXLSX" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="75" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="5add693a-51ef-4f02-a9c8-633e927ee473" name="CreateExcel" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="75" />
    <datatype>flag</datatype>
    <initialvalue>False</initialvalue>
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="61a50860-9a73-4cbd-8307-274af6b1cff2" name="Create Excel Token" type="Code">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="285" />
    <inputs>
      <input type="collection" name="DatatableResult" expr="[Report]" />
      <input type="text" name="Filepath" expr="[FilepathXLSX]" />
      <input type="text" name="SheetName" expr="[SheetName]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
    </outputs>
    <onsuccess>d0c941a8-7e1a-4167-b165-ae1bae998acf</onsuccess>
    <code><![CDATA[Success = false;
ErrorMessage = "";
 
try
{
DataTableToExcel exporter = new DataTableToExcel();
exporter.ExportToExcel(DatatableResult, Filepath, SheetName);
Success = true;
}
catch (Exception ex)
{
Success = false;
ErrorMessage = ex.Message;
}]]></code>
  </stage>
  <stage stageid="e7d0172b-74d8-4261-a310-976261f28845" name="[CreateExcel] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="225" />
    <decision expression="[CreateExcel] = True" />
    <ontrue>61a50860-9a73-4cbd-8307-274af6b1cff2</ontrue>
    <onfalse>e5242eaf-7df1-445a-be16-359ff2906db9</onfalse>
  </stage>
  <stage stageid="e5242eaf-7df1-445a-be16-359ff2906db9" name="Anchor1" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="225" w="10" h="10" />
    <onsuccess>6f7ea0aa-ca84-4bc5-b2cb-b00a3f5a11c1</onsuccess>
  </stage>
  <stage stageid="c5eff911-30f7-4e00-bf78-bd2290970310" name="Anchor2" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="405" w="10" h="10" />
    <onsuccess>d1c909a0-c003-41a6-8d16-835dcb19fd7c</onsuccess>
  </stage>
  <stage stageid="d2f2e471-9a3f-4b66-b87a-c3d790bfbcb0" name="SheetName" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-165" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="ad69bfb0-e52b-47f0-a648-5f1e094fcf37" name="[Success] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="45" />
    <decision expression="[Success] = True" />
    <ontrue>767269e4-2267-4297-a3c9-d97409e501de</ontrue>
    <onfalse>c0a9ca49-ff4e-441d-9650-da100bcf6bad</onfalse>
  </stage>
  <stage stageid="d88bcda0-42b4-4302-ba8c-e642949c5a69" name="[Success] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="165" />
    <decision expression="[Success] = True" />
    <ontrue>e7d0172b-74d8-4261-a310-976261f28845</ontrue>
    <onfalse>04d9f303-e399-4bb3-93fa-3ce536aa55a1</onfalse>
  </stage>
  <stage stageid="c0a9ca49-ff4e-441d-9650-da100bcf6bad" name="Set Error Message" type="Calculation">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="30" y="45" />
    <onsuccess>0f46bfec-9fc3-4b45-9073-b2a2feebbbe3</onsuccess>
    <calculation expression="&quot;Error in API Call: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="04d9f303-e399-4bb3-93fa-3ce536aa55a1" name="Set Error Message" type="Calculation">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="30" y="165" />
    <onsuccess>93dfc5a7-0afe-4188-9a40-fb7de4b3b993</onsuccess>
    <calculation expression="&quot;Error in JSON Conversion: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="d0c941a8-7e1a-4167-b165-ae1bae998acf" name="[Success] = True" type="Decision">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="345" />
    <decision expression="[Success] = True" />
    <ontrue>d1c909a0-c003-41a6-8d16-835dcb19fd7c</ontrue>
    <onfalse>33d8e73b-b167-41a9-8606-25057a305ddc</onfalse>
  </stage>
  <stage stageid="33d8e73b-b167-41a9-8606-25057a305ddc" name="Set Error Message" type="Calculation">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="30" y="345" />
    <onsuccess>6f7ea0aa-ca84-4bc5-b2cb-b00a3f5a11c1</onsuccess>
    <calculation expression="&quot;Error in Create Excel: &quot;&amp;[Error Message]" stage="Error Message" />
  </stage>
  <stage stageid="0f46bfec-9fc3-4b45-9073-b2a2feebbbe3" name="Anchor3" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="45" w="10" h="10" />
    <onsuccess>93dfc5a7-0afe-4188-9a40-fb7de4b3b993</onsuccess>
  </stage>
  <stage stageid="93dfc5a7-0afe-4188-9a40-fb7de4b3b993" name="Anchor4" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="165" w="10" h="10" />
    <onsuccess>e5242eaf-7df1-445a-be16-359ff2906db9</onsuccess>
  </stage>
  <stage stageid="6f7ea0aa-ca84-4bc5-b2cb-b00a3f5a11c1" name="Anchor5" type="Anchor">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="90" y="345" w="10" h="10" />
    <onsuccess>c5eff911-30f7-4e00-bf78-bd2290970310</onsuccess>
  </stage>
  <stage stageid="10d34c6c-df8b-4d15-9a20-6ae62ee2bb83" name="Call API Token" type="Code">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-15" />
    <inputs>
      <input type="text" name="databricksUrl" expr="[databricksUrl]" />
      <input type="text" name="accessToken" expr="[accessToken]" />
      <input type="text" name="warehouseId" expr="[warehouseId]" />
      <input type="text" name="sqlQuery" expr="[sqlQuery]" />
    </inputs>
    <outputs>
      <output type="flag" name="Success" stage="Success" />
      <output type="text" name="ErrorMessage" stage="Error Message" />
      <output type="text" name="JSONOutput" stage="JSONOutput" />
    </outputs>
    <onsuccess>ad69bfb0-e52b-47f0-a648-5f1e094fcf37</onsuccess>
    <code><![CDATA[
// Assumo che tu abbia già queste variabili in scope:
// string databricksUrl;    // es: "https://<workspace>/api/2.0/sql/statements"
// string accessToken;      // Bearer token del Service Principal
// string sqlQuery;
// string warehouseId;
 
Success = false;
ErrorMessage = "";
JSONOutput = "";
 
// == Impostazione opzionale ==
bool fetchAllChunks = true;          // true = unisce tutti i chunk; false = solo chunk 0
TimeSpan overallTimeout = TimeSpan.FromMinutes(10);
int delayMs = 500;
int maxDelayMs = 5000;
 
// --- Helper locali identici ai tuoi ---
string ExtractDetail(JObject j)
{
    if (j == null) return null;
    return (string)j["status"]?["error"]?["message"]
           ?? (string)j["error"]?["message"]
           ?? (string)j["status"]?["error"]?["code"]
           ?? (string)j["error"]?["code"];
}
 
string ExtractSqlState(JObject j)
{
    if (j == null) return null;
    return (string)j["status"]?["error"]?["sql_state"]
           ?? (string)j["error"]?["sql_state"];
}
 
void BuildShortErrorFromHttp(string forcedState, HttpResponseMessage resp, string body, out string errorMessage)
{
    var stateLabel = string.IsNullOrWhiteSpace(forcedState) ? "FAILED" : forcedState;
 
    string detail = null;
    string sqlState = null;
 
    try
    {
        var j = string.IsNullOrWhiteSpace(body) ? null : JObject.Parse(body);
        detail = ExtractDetail(j);
        sqlState = ExtractSqlState(j);
    }
    catch { /* ignore parse errors */ }
 
    if (string.IsNullOrEmpty(detail))
        detail = $"HTTP {(int)resp.StatusCode} {resp.ReasonPhrase}";
    if (string.IsNullOrEmpty(sqlState))
        sqlState = "N/A";
 
    errorMessage = $"State: {stateLabel} - Detail: {detail} - SQLSTATE: {sqlState}";
}
 
// --- Funzioni locali per i chunk ---
(bool ok, JObject resultObj, string err) FetchAndMergeChunks(
    HttpClient httpClient,
    string baseUrl,
    string statementId,
    int totalChunks,
    bool fetchAll)
{
    try
    {
        var allRows = new JArray();
        int chunksToFetch = fetchAll ? totalChunks : Math.Min(1, totalChunks);
 
        for (int i = 0; i < chunksToFetch; i++)
        {
            var url = $"{baseUrl.TrimEnd('/')}/{statementId}/result?chunk_index={i}";
            var resp = httpClient.GetAsync(url).GetAwaiter().GetResult();
            var body = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
            if (!resp.IsSuccessStatusCode)
            {
                BuildShortErrorFromHttp("FAILED", resp, body, out var errMsg);
                return (false, null, errMsg);
            }
 
            JObject chunk = null;
            try { chunk = JObject.Parse(body); } catch { }
 
            if (chunk == null)
            {
                return (false, null, "State: FAILED - Detail: Impossibile interpretare la risposta dei risultati come JSON. - SQLSTATE: N/A");
            }
 
            var rows = (JArray)chunk["data_array"] ?? new JArray();
            foreach (var r in rows) allRows.Add(r);
        }
 
        var resultObj = new JObject
        {
            ["chunk_index"] = 0,
            ["row_offset"] = 0,
            ["row_count"] = allRows.Count,
            ["data_array"] = allRows
        };
 
        return (true, resultObj, null);
    }
    catch (Exception ex)
    {
        return (false, null, $"State: FAILED - Detail: {ex.Message} - SQLSTATE: N/A");
    }
}
 
try
{
    using (var httpClient = new HttpClient())
    {
        httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
 
        // 1) POST: crea statement (senza attendere)
        var payload = new
        {
            statement = sqlQuery,
            warehouse_id = warehouseId,
            wait_timeout = "0s",
            format = "JSON_ARRAY" // così hai data_array coerente col primo metodo
        };
 
        var content = new StringContent(
            JsonConvert.SerializeObject(payload),
            Encoding.UTF8,
            "application/json"
        );
 
        var postResp = httpClient.PostAsync(databricksUrl, content).GetAwaiter().GetResult();
        var postBody = postResp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
        if (!postResp.IsSuccessStatusCode)
        {
            BuildShortErrorFromHttp("FAILED", postResp, postBody, out ErrorMessage);
            Success = false;
            return;
        }
 
        JObject postJson = null;
        try { postJson = JObject.Parse(postBody); } catch { }
 
        if (postJson == null)
        {
            ErrorMessage = "State: FAILED - Detail: Impossibile interpretare la risposta POST come JSON. - SQLSTATE: N/A";
            Success = false;
            return;
        }
 
        var statementId = (string)postJson["statement_id"];
        var stateImmediate = (string)postJson["status"]?["state"];
 
        if (string.IsNullOrEmpty(statementId))
        {
            // Caso finale immediato
            if (!"SUCCEEDED".Equals(stateImmediate, StringComparison.OrdinalIgnoreCase))
            {
                var detail = ExtractDetail(postJson) ?? "N/A";
                var sqlState = ExtractSqlState(postJson) ?? "N/A";
                ErrorMessage = $"State: {stateImmediate ?? "FAILED"} - Detail: {detail} - SQLSTATE: {sqlState}";
                Success = false;
                return;
            }
            else
            {
                // Già completo → restituisco l'oggetto come nel primo metodo
                JSONOutput = postJson.ToString(Formatting.None);
                Success = true;
                return;
            }
        }
 
        // 2) Polling fino a stato terminale
        var start = DateTime.UtcNow;
        string finalState = null;
        JObject finalStatement = null;
 
        string[] terminal = { "SUCCEEDED", "FAILED", "CANCELED", "CLOSED" };
 
        while (true)
        {
            var getUrl = $"{databricksUrl.TrimEnd('/')}/{statementId}";
            var getResp = httpClient.GetAsync(getUrl).GetAwaiter().GetResult();
            var getBody = getResp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 
            if (!getResp.IsSuccessStatusCode)
            {
                BuildShortErrorFromHttp("FAILED", getResp, getBody, out ErrorMessage);
                Success = false;
                return;
            }
 
            JObject getJson = null;
            try { getJson = JObject.Parse(getBody); } catch { }
 
            if (getJson == null)
            {
                ErrorMessage = "State: FAILED - Detail: Impossibile interpretare la risposta GET polling come JSON. - SQLSTATE: N/A";
                Success = false;
                return;
            }
 
            finalStatement = getJson;
            var state = (string)getJson["status"]?["state"];
 
            if (!string.IsNullOrWhiteSpace(state) && terminal.Contains(state.ToUpperInvariant()))
            {
                finalState = state;
                break;
            }
 
            if (DateTime.UtcNow - start > overallTimeout)
            {
                ErrorMessage = $"State: FAILED - Detail: Polling scaduto dopo {overallTimeout.TotalSeconds:N0} secondi. - SQLSTATE: N/A";
                Success = false;
                return;
            }
 
            Thread.Sleep(delayMs);
            delayMs = Math.Min(delayMs * 2, maxDelayMs);
        }
 
        // 3) Esito finale
        if (!"SUCCEEDED".Equals(finalState, StringComparison.OrdinalIgnoreCase))
        {
            var detail = ExtractDetail(finalStatement) ?? "N/A";
            var sqlState = ExtractSqlState(finalStatement) ?? "N/A";
            ErrorMessage = $"State: {finalState} - Detail: {detail} - SQLSTATE: {sqlState}";
            Success = false;
            return;
        }
 
        // 4) Recupero risultati e innesto in finalStatement
        JToken resultToken = finalStatement["result"];
 
        // Quanti chunk ci sono?
        int totalChunks = 1;
        try
        {
            totalChunks = (int?)finalStatement["manifest"]?["total_chunk_count"] ?? 1;
        }
        catch { /* ignore */ }
 
        if (resultToken == null)
        {
            // Non c'è result nello statement → scarico i chunk e UNISCO
            var (ok, resultObj, err) = FetchAndMergeChunks(httpClient, databricksUrl, statementId, totalChunks, fetchAllChunks);
            if (!ok)
            {
                ErrorMessage = err;
                Success = false;
                return;
            }
 
            finalStatement["result"] = resultObj;
        }
        else
        {
            // C'è già result nello statement.
            // Se vuoi essere sicuro di avere TUTTO, e ci sono più chunk, li recupero e unisco.
            if (fetchAllChunks && totalChunks > 1)
            {
                // Riparto da zero e unisco tutti i chunk (più semplice e robusto)
                var (ok, resultObj, err) = FetchAndMergeChunks(httpClient, databricksUrl, statementId, totalChunks, fetchAllChunks);
                if (!ok)
                {
                    ErrorMessage = err;
                    Success = false;
                    return;
                }
                finalStatement["result"] = resultObj;
            }
        }
 
        // Allineo counters (opzionale)
        try
        {
            var dataArray = finalStatement["result"]?["data_array"] as JArray;
            if (dataArray != null)
            {
                finalStatement["result"]["row_count"] = dataArray.Count;
                finalStatement["result"]["chunk_index"] = 0;
                finalStatement["result"]["row_offset"] = 0;
            }
        }
        catch { /* ignore */ }
 
        // 5) OUTPUT: restituisco lo statement completo (come nel primo metodo)
        JSONOutput = finalStatement.ToString(Formatting.None);
        Success = true;
    }
}
catch (Exception ex)
{
    Success = false;
    ErrorMessage = $"State: FAILED - Detail: {ex.Message} - SQLSTATE: N/A";
}
]]></code>
  </stage>
  <stage stageid="cb0efb85-278a-4cf1-a80d-b99a7b2439f8" name="databricksUrl" type="Data">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <display x="-225" y="30" />
    <datatype>text</datatype>
    <initialvalue />
    <private />
    <alwaysinit />
  </stage>
  <stage stageid="33ac5062-313a-408b-a962-0eb8eab9b6a4" name="Authentication" type="SubSheet">
    <subsheetid>fa900c6f-ef74-4b80-a2e5-f70e341de476</subsheetid>
    <loginhibit />
    <display x="-60" y="-75" />
    <outputs>
      <output type="text" name="AccessToken" friendlyname="AccessToken" stage="accessToken" />
    </outputs>
    <onsuccess>10d34c6c-df8b-4d15-9a20-6ae62ee2bb83</onsuccess>
    <processid>9cb7bf9b-8ba4-45f2-b334-fc34ba39707a</processid>
  </stage>
</process>