mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
* Add linting for copyright and unused usings * Add one more + comment * Enforce in build and fix errors * Fix build
271 lines
11 KiB
C#
271 lines
11 KiB
C#
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Kusto.Cloud.Platform.Data;
|
|
using Kusto.Data;
|
|
using Kusto.Data.Common;
|
|
using Kusto.Data.Data;
|
|
using Kusto.Data.Exceptions;
|
|
using Kusto.Data.Net.Client;
|
|
using Kusto.Language;
|
|
using Kusto.Language.Editor;
|
|
using Microsoft.Kusto.ServiceLayer.Connection;
|
|
using Microsoft.Kusto.ServiceLayer.DataSource.Contracts;
|
|
using Microsoft.Kusto.ServiceLayer.Utility;
|
|
|
|
namespace Microsoft.Kusto.ServiceLayer.DataSource.Kusto
|
|
{
|
|
public class KustoClient : IKustoClient
|
|
{
|
|
private readonly string _ownerUri;
|
|
|
|
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
|
private ICslAdminProvider _kustoAdminProvider;
|
|
|
|
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
|
private ICslQueryProvider _kustoQueryProvider;
|
|
|
|
public string ClusterName { get; private set; }
|
|
public string DatabaseName { get; private set; }
|
|
|
|
public KustoClient(DataSourceConnectionDetails connectionDetails, string ownerUri)
|
|
{
|
|
_ownerUri = ownerUri;
|
|
Initialize(connectionDetails);
|
|
}
|
|
|
|
private void Initialize(DataSourceConnectionDetails connectionDetails)
|
|
{
|
|
var stringBuilder = GetKustoConnectionStringBuilder(connectionDetails);
|
|
_kustoQueryProvider = KustoClientFactory.CreateCslQueryProvider(stringBuilder);
|
|
_kustoAdminProvider = KustoClientFactory.CreateCslAdminProvider(stringBuilder);
|
|
|
|
if (DatabaseName != "NetDefaultDB")
|
|
{
|
|
return;
|
|
}
|
|
|
|
var dataReader = ExecuteQuery(".show databases | top 1 by DatabaseName | project DatabaseName", new CancellationToken());
|
|
var databaseName = dataReader.ToEnumerable().Select(row => row["DatabaseName"]).FirstOrDefault();
|
|
DatabaseName = databaseName?.ToString() ?? "";
|
|
}
|
|
|
|
private bool TryRefreshAuthToken()
|
|
{
|
|
if (!ConnectionService.Instance.TryRefreshAuthToken(_ownerUri, out string accountToken))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_kustoQueryProvider.Dispose();
|
|
_kustoAdminProvider.Dispose();
|
|
|
|
var connectionDetails = new DataSourceConnectionDetails
|
|
{
|
|
ServerName = ClusterName,
|
|
DatabaseName = DatabaseName,
|
|
UserToken = accountToken,
|
|
AuthenticationType = "AzureMFA"
|
|
};
|
|
|
|
Initialize(connectionDetails);
|
|
return true;
|
|
}
|
|
|
|
private KustoConnectionStringBuilder GetKustoConnectionStringBuilder(DataSourceConnectionDetails connectionDetails)
|
|
{
|
|
var stringBuilder = string.IsNullOrWhiteSpace(connectionDetails.ConnectionString)
|
|
? new KustoConnectionStringBuilder(connectionDetails.ServerName, connectionDetails.DatabaseName)
|
|
: new KustoConnectionStringBuilder(connectionDetails.ConnectionString);
|
|
|
|
ClusterName = stringBuilder.DataSource;
|
|
var databaseName = KustoQueryUtils.ParseDatabaseName(stringBuilder.InitialCatalog);
|
|
DatabaseName = databaseName;
|
|
stringBuilder.InitialCatalog = databaseName;
|
|
|
|
ValidationUtils.IsNotNull(ClusterName, nameof(ClusterName));
|
|
|
|
switch (connectionDetails.AuthenticationType)
|
|
{
|
|
case "AzureMFA": return stringBuilder.WithAadUserTokenAuthentication(connectionDetails.UserToken);
|
|
case "dstsAuth": return stringBuilder.WithDstsUserTokenAuthentication(connectionDetails.UserToken);
|
|
default:
|
|
return string.IsNullOrWhiteSpace(connectionDetails.UserName) && string.IsNullOrWhiteSpace(connectionDetails.Password)
|
|
? stringBuilder
|
|
: stringBuilder.WithKustoBasicAuthentication(connectionDetails.UserName, connectionDetails.Password);
|
|
}
|
|
}
|
|
|
|
private ClientRequestProperties GetClientRequestProperties(CancellationToken cancellationToken)
|
|
{
|
|
var clientRequestProperties = new ClientRequestProperties
|
|
{
|
|
ClientRequestId = Guid.NewGuid().ToString()
|
|
};
|
|
clientRequestProperties.SetOption(ClientRequestProperties.OptionNoTruncation, true);
|
|
cancellationToken.Register(() => CancelQuery(clientRequestProperties.ClientRequestId));
|
|
|
|
return clientRequestProperties;
|
|
}
|
|
|
|
public IDataReader ExecuteQuery(string query, CancellationToken cancellationToken, string databaseName = null, int retryCount = 1)
|
|
{
|
|
ValidationUtils.IsArgumentNotNullOrWhiteSpace(query, nameof(query));
|
|
|
|
var script = CodeScript.From(query, GlobalState.Default);
|
|
IDataReader[] origReaders = new IDataReader[script.Blocks.Count];
|
|
try
|
|
{
|
|
var numOfQueries = 0;
|
|
Parallel.ForEach(script.Blocks, (codeBlock, state, index) =>
|
|
{
|
|
var minimalQuery =
|
|
codeBlock.Service.GetMinimalText(MinimalTextKind.RemoveLeadingWhitespaceAndComments);
|
|
|
|
if (!string.IsNullOrEmpty(minimalQuery))
|
|
{
|
|
// Query is empty in case of comments
|
|
IDataReader origReader;
|
|
var clientRequestProperties = GetClientRequestProperties(cancellationToken);
|
|
|
|
if (minimalQuery.StartsWith(".") && !minimalQuery.StartsWith(".show"))
|
|
{
|
|
origReader = _kustoAdminProvider.ExecuteControlCommand(
|
|
KustoQueryUtils.IsClusterLevelQuery(minimalQuery) ? "" : databaseName,
|
|
minimalQuery,
|
|
clientRequestProperties);
|
|
}
|
|
else
|
|
{
|
|
origReader = _kustoQueryProvider.ExecuteQuery(
|
|
KustoQueryUtils.IsClusterLevelQuery(minimalQuery) ? "" : databaseName,
|
|
minimalQuery,
|
|
clientRequestProperties);
|
|
}
|
|
|
|
origReaders[index] = origReader;
|
|
numOfQueries++;
|
|
}
|
|
});
|
|
|
|
if (numOfQueries == 0 && origReaders.Length > 0) // Covers the scenario when user tries to run comments.
|
|
{
|
|
var clientRequestProperties = GetClientRequestProperties(cancellationToken);
|
|
origReaders[0] = _kustoQueryProvider.ExecuteQuery(
|
|
KustoQueryUtils.IsClusterLevelQuery(query) ? "" : databaseName,
|
|
query,
|
|
clientRequestProperties);
|
|
}
|
|
|
|
return new KustoResultsReader(origReaders);
|
|
}
|
|
catch (AggregateException exception)
|
|
when (retryCount > 0 &&
|
|
exception.InnerException is KustoRequestException innerException
|
|
&& innerException.FailureCode == 401) // Unauthorized
|
|
{
|
|
if (!TryRefreshAuthToken())
|
|
{
|
|
throw;
|
|
}
|
|
retryCount--;
|
|
return ExecuteQuery(query, cancellationToken, databaseName, retryCount);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a query or command against a kusto cluster and returns a sequence of result row instances.
|
|
/// </summary>
|
|
public async Task ExecuteControlCommandAsync(string command, bool throwOnError, int retryCount = 1)
|
|
{
|
|
ValidationUtils.IsArgumentNotNullOrWhiteSpace(command, nameof(command));
|
|
|
|
try
|
|
{
|
|
using (var adminOutput = await _kustoAdminProvider.ExecuteControlCommandAsync(DatabaseName, command))
|
|
{
|
|
}
|
|
}
|
|
catch (KustoRequestException exception) when (retryCount > 0 && exception.FailureCode == 401) // Unauthorized
|
|
{
|
|
if (!TryRefreshAuthToken())
|
|
{
|
|
throw;
|
|
}
|
|
retryCount--;
|
|
await ExecuteControlCommandAsync(command, throwOnError, retryCount);
|
|
}
|
|
catch (Exception) when (!throwOnError)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a query.
|
|
/// </summary>
|
|
/// <param name="query">The query.</param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <param name="databaseName"></param>
|
|
/// <returns>The results.</returns>
|
|
public async Task<IEnumerable<T>> ExecuteQueryAsync<T>(string query, CancellationToken cancellationToken,
|
|
string databaseName = null)
|
|
{
|
|
var resultReader = ExecuteQuery(query, cancellationToken, databaseName);
|
|
var results = KustoDataReaderParser.ParseV1(resultReader, null);
|
|
var tableReader = results[WellKnownDataSet.PrimaryResult].Single().TableData.CreateDataReader();
|
|
return await Task.FromResult(new ObjectReader<T>(tableReader));
|
|
}
|
|
|
|
private void CancelQuery(string clientRequestId)
|
|
{
|
|
var query = $".cancel query \"{clientRequestId}\"";
|
|
ExecuteControlCommand(query);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a Kusto control command.
|
|
/// </summary>
|
|
/// <param name="command">The command.</param>
|
|
/// <param name="retryCount"></param>
|
|
public void ExecuteControlCommand(string command, int retryCount = 1)
|
|
{
|
|
ValidationUtils.IsArgumentNotNullOrWhiteSpace(command, nameof(command));
|
|
|
|
try
|
|
{
|
|
using (var adminOutput = _kustoAdminProvider.ExecuteControlCommand(command))
|
|
{
|
|
}
|
|
}
|
|
catch (KustoRequestException exception) when (retryCount > 0 && exception.FailureCode == 401) // Unauthorized
|
|
{
|
|
if (!TryRefreshAuthToken())
|
|
{
|
|
throw;
|
|
}
|
|
retryCount--;
|
|
ExecuteControlCommand(command, retryCount);
|
|
}
|
|
}
|
|
|
|
public void UpdateDatabase(string databaseName)
|
|
{
|
|
DatabaseName = databaseName;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_kustoQueryProvider.Dispose();
|
|
_kustoAdminProvider.Dispose();
|
|
}
|
|
}
|
|
} |