Skip to content

Commit

Permalink
Show query even if queryplan times out
Browse files Browse the repository at this point in the history
  • Loading branch information
ElectricVampire committed Oct 5, 2024
1 parent 9047b68 commit 0df6e48
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 34 deletions.
32 changes: 32 additions & 0 deletions src/EFCore.Visualizer/QueryPlanUserControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ private void QueryPlanUserControlUnloaded(object sender, RoutedEventArgs e)
protected override async void OnInitialized(EventArgs e)
#pragma warning restore VSTHRD100 // Avoid async void methods
{
var query = string.Empty;
try
{
base.OnInitialized(e);
Expand All @@ -50,6 +51,8 @@ protected override async void OnInitialized(EventArgs e)
var environment = await CoreWebView2Environment.CreateAsync(userDataFolder: Path.Combine(AssemblyLocation, "WVData"));
await webView.EnsureCoreWebView2Async(environment);

query = await GetQueryAsync();

#if !DEBUG
webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false;
webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
Expand Down Expand Up @@ -84,10 +87,39 @@ protected override async void OnInitialized(EventArgs e)
}
catch (Exception ex)
{
if (!string.IsNullOrEmpty(query))
webView.CoreWebView2.NavigateToString(query);
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

private async Task<string> GetQueryAsync()
{
var query = string.Empty;
var response = await visualizerTarget.ObjectSource.RequestDataAsync(ConvertStringToReadOnlySequence("GetQuery"), CancellationToken.None);
if (response.HasValue)
{
using var stream = response.Value.AsStream();
using var binaryReader = new BinaryReader(stream, Encoding.Default);
var isError = binaryReader.ReadBoolean();
if (!isError)
{
query = binaryReader.ReadString();
}
}
return query;
}
private static ReadOnlySequence<byte> ConvertStringToReadOnlySequence(string input)
{
byte[] byteArray = Encoding.UTF8.GetBytes(input);

ReadOnlyMemory<byte> readOnlyMemory = new ReadOnlyMemory<byte>(byteArray);

ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(readOnlyMemory);

return readOnlySequence;
}

private void ButtonReviewClick(object sender, RoutedEventArgs e)
{
StartProcess("https://marketplace.visualstudio.com/items?itemName=GiorgiDalakishvili.EFCoreVisualizer&ssr=false#review-details");
Expand Down
154 changes: 120 additions & 34 deletions src/IQueryableObjectSource/EFCoreQueryableObjectSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using System.Net;
using Microsoft.EntityFrameworkCore.Storage;

namespace IQueryableObjectSource
{
Expand All @@ -22,55 +25,138 @@ public override void TransferData(object target, Stream incomingData, Stream out

try
{
using var command = queryable.CreateDbCommand();
var provider = GetDatabaseProvider(command);

if (provider == null)
var dbOperation = ConvertStreamToString(incomingData);
switch (dbOperation)
{
return;
case "GetQuery":
HandleGetQuery(queryable, outgoingData);
break;

default:
HandleGetQueryPlan(queryable, incomingData, outgoingData);
break;
}
}
catch (Exception ex)
{
WriteError(outgoingData, ex.Message);
}
}
private void HandleGetQuery(IQueryable queryable, Stream outgoingData)
{
using var queryWriter = new BinaryWriter(outgoingData, Encoding.Default, true);
queryWriter.Write(false); // Indicates no error
queryWriter.Write(GenerateHtml(queryable.ToQueryString()));
}
private void HandleGetQueryPlan(IQueryable queryable, Stream incomingData, Stream outgoingData)
{
using var command = queryable.CreateDbCommand();
var provider = GetDatabaseProvider(command);

var query = queryable.ToQueryString();
var rawPlan = provider.ExtractPlan();
if (provider == null)
{
return;
}

var buffer = new byte[3];
var isBackgroundDarkColor = false;
var query = queryable.ToQueryString();
var rawPlan = provider.ExtractPlan();

var r = 255;
var g = 255;
var b = 255;
var (r, g, b) = ReadBackgroundColor(incomingData);
var isBackgroundDarkColor = r * 0.2126 + g * 0.7152 + b * 0.0722 < 255 / 2.0;

if (incomingData.Read(buffer, 0, buffer.Length) == buffer.Length)
{
r = buffer[0];
g = buffer[1];
b = buffer[2];
}
var planFile = GeneratePlanFile(provider, query, rawPlan, r, g, b, isBackgroundDarkColor);

using var writer = new BinaryWriter(outgoingData, Encoding.Default, true);
writer.Write(false); // Indicates no error
writer.Write(planFile);
}
private (int r, int g, int b) ReadBackgroundColor(Stream incomingData)
{
var buffer = new byte[3];
var r = 255;
var g = 255;
var b = 255;

if (incomingData.Read(buffer, 0, buffer.Length) == buffer.Length)
{
r = buffer[0];
g = buffer[1];
b = buffer[2];
}

isBackgroundDarkColor = r * 0.2126 + g * 0.7152 + b * 0.0722 < 255 / 2.0;
return (r, g, b);
}

var planFile = Path.Combine(provider.GetPlanDirectory(ResourcesLocation), Path.ChangeExtension(Path.GetRandomFileName(), "html"));
private string GeneratePlanFile(DatabaseProvider provider, string query, string rawPlan, int r, int g, int b, bool isBackgroundDarkColor)
{
var planDirectory = provider.GetPlanDirectory(ResourcesLocation);
var planFile = Path.Combine(planDirectory, Path.ChangeExtension(Path.GetRandomFileName(), "html"));

var planPageHtml = File.ReadAllText(Path.Combine(provider.GetPlanDirectory(ResourcesLocation), "template.html"))
.Replace("{backColor}", $"rgb({r} {g} {b})")
.Replace("{textColor}", isBackgroundDarkColor ? "white" : "black")
.Replace("{plan}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(rawPlan).Replace("'", "\\'"))
.Replace("{query}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(query).Replace("'", "\\'"));
var planPageHtml = File.ReadAllText(Path.Combine(planDirectory, "template.html"))
.Replace("{backColor}", $"rgb({r} {g} {b})")
.Replace("{textColor}", isBackgroundDarkColor ? "white" : "black")
.Replace("{plan}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(rawPlan).Replace("'", "\\'"))
.Replace("{query}", JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode(query).Replace("'", "\\'"));

File.WriteAllText(planFile, planPageHtml);
File.WriteAllText(planFile, planPageHtml);

using var writer = new BinaryWriter(outgoingData, Encoding.Default, true);
writer.Write(false);
writer.Write(planFile);
}
catch (Exception ex)
return planFile;
}

private void WriteError(Stream outgoingData, string errorMessage)
{
using var writer = new BinaryWriter(outgoingData, Encoding.Default, true);
writer.Write(true); // Indicates an error occurred
writer.Write(errorMessage);
}

public static string ConvertStreamToString(Stream stream)
{
using (MemoryStream memoryStream = new MemoryStream())
{
using var writer = new BinaryWriter(outgoingData, Encoding.Default, true);
writer.Write(true);
writer.Write(ex.Message);
stream.CopyTo(memoryStream);
byte[] byteArray = memoryStream.ToArray();

//Try to convert byte array to a string using UTF-8 encoding
try
{
string result = Encoding.UTF8.GetString(byteArray);
return result;
}
catch (Exception)
{
return "GetQueryPlan";
}
}
}

private static string GenerateHtml(string query)
{
string escapedQuery = WebUtility.HtmlEncode(query);

// Simple HTML structure to display the query
StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.AppendLine("<html>");
htmlBuilder.AppendLine("<head><title>Query Plan Visualizer</title>");
htmlBuilder.AppendLine("<style>");
htmlBuilder.AppendLine("body { font-family: Arial, sans-serif; margin: 20px; }");
htmlBuilder.AppendLine("h2 { color: #333; }");
htmlBuilder.AppendLine(".query-box { background-color: #f4f4f4; padding: 10px; border-radius: 5px; border: 1px solid #ccc; overflow-x: auto; max-width: 100%; }");
htmlBuilder.AppendLine("pre { white-space: pre-wrap; word-wrap: break-word; }");
htmlBuilder.AppendLine("</style>");
htmlBuilder.AppendLine("</head>");
htmlBuilder.AppendLine("<body>");
htmlBuilder.AppendLine("<h2>SQL Query</h2>");
htmlBuilder.AppendLine("<div class='query-box'>");
htmlBuilder.AppendLine("<pre>" + escapedQuery + "</pre>");
htmlBuilder.AppendLine("</div>");
htmlBuilder.AppendLine("</body>");
htmlBuilder.AppendLine("</html>");

return htmlBuilder.ToString();
}


private static DatabaseProvider GetDatabaseProvider(DbCommand command)
{
return command.GetType().FullName switch
Expand Down

0 comments on commit 0df6e48

Please sign in to comment.