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>
<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=""Error in API Call: "&[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=""Error in JSON Conversion: "&[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=""Error in Create Excel: "&[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=""Error in API Call: "&[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=""Error in JSON Conversion: "&[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=""Error in Create Excel: "&[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>