Skip to content

Commit

Permalink
Exposing all EXPLAIN options. Quoting PG documentation in XML docs, s…
Browse files Browse the repository at this point in the history
…o included PG license in repo.

Not all parameters exposed in JSON deserialization, as they need to be dug out from explain.c in PG backend
https://github.com/postgres/postgres/blob/18ce3a4ab22d2984f8540ab480979c851dae5338/src/backend/commands/explain.c (GitHub mirror)
  • Loading branch information
jokokko authored and jeremydmiller committed May 11, 2017
1 parent 9dd0917 commit b7d81ea
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 9 deletions.
14 changes: 14 additions & 0 deletions postgresql.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
PostgreSQL is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses.

PostgreSQL Database Management System
(formerly known as Postgres, then as Postgres95)

Portions Copyright (c) 1996-2017, The PostgreSQL Global Development Group

Portions Copyright (c) 1994, The Regents of the University of California

Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies.

IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
40 changes: 40 additions & 0 deletions src/Marten.Testing/Linq/explain_query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using Marten.Services;
using Marten.Util;
using Shouldly;
using Xunit;

namespace Marten.Testing.Linq
Expand Down Expand Up @@ -61,5 +62,44 @@ public void retrieves_query_plan_with_where()
plan.PlanRows.ShouldBeGreaterThan(0);
plan.TotalCost.ShouldBeGreaterThan(0m);
}

[Fact]
public void retrieves_query_plan_with_where_and_all_options_enabled()
{
var user1 = new SimpleUser
{
UserName = "Mr Fouine",
Number = 5,
Birthdate = new DateTime(1986, 10, 4),
Address = new Address { HouseNumber = "12bis", Street = "rue de la martre" }
};
var user2 = new SimpleUser
{
UserName = "Mrs Fouine",
Number = 6,
Birthdate = new DateTime(1987, 10, 4),
Address = new Address { HouseNumber = "12bis", Street = "rue de la martre" }
};
theSession.Store(user1, user2);
theSession.SaveChanges();

var plan = theSession.Query<SimpleUser>().Where(u => u.Number > 5)
.OrderBy(x => x.Number)
.Explain(c =>
{
c
.Analyze()
.Buffers()
.Costs()
.Timing()
.Verbose();
});
plan.ShouldNotBeNull();
plan.ActualTotalTime.ShouldBeGreaterThan(0m);
plan.PlanningTime.ShouldBeGreaterThan(0m);
plan.ExecutionTime.ShouldBeGreaterThan(0m);
plan.SortKey.ShouldContain("(((d.data ->> 'Number'::text))::integer)");
plan.Plans.ShouldNotBeEmpty();
}
}
}
43 changes: 43 additions & 0 deletions src/Marten/Linq/ConfigureExplainExpressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Text;

namespace Marten.Linq
{
internal sealed class ConfigureExplainExpressions : IConfigureExplainExpressions
{
private readonly StringBuilder toOptions = new StringBuilder();
public IConfigureExplainExpressions Analyze()
{
toOptions.Append("ANALYZE,");
return this;
}

public IConfigureExplainExpressions Verbose()
{
toOptions.Append("VERBOSE,");
return this;
}

public IConfigureExplainExpressions Costs()
{
toOptions.Append("COSTS,");
return this;
}

public IConfigureExplainExpressions Buffers()
{
toOptions.Append("BUFFERS,");
return this;
}

public IConfigureExplainExpressions Timing()
{
toOptions.Append("TIMING,");
return this;
}

public override string ToString()
{
return toOptions.ToString();
}
}
}
32 changes: 32 additions & 0 deletions src/Marten/Linq/IConfigureExplainExpressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Marten.Linq
{
public interface IConfigureExplainExpressions
{
// Borrowing from PG 9.6 documentation https://www.postgresql.org/docs/9.2/static/sql-explain.html

/// <summary>
/// Carry out the command and show actual run times and other statistics.
/// </summary>
IConfigureExplainExpressions Analyze();

/// <summary>
/// Display additional information regarding the plan. Specifically, include the output column list for each node in the plan tree, schema-qualify table and function names, always label variables in expressions with their range table alias, and always print the name of each trigger for which statistics are displayed.
/// </summary>
IConfigureExplainExpressions Verbose();

/// <summary>
/// Include information on the estimated startup and total cost of each plan node, as well as the estimated number of rows and the estimated width of each row.
/// </summary>
IConfigureExplainExpressions Costs();

/// <summary>
/// Include information on buffer usage. Specifically, include the number of shared blocks hit, read, dirtied, and written, the number of local blocks hit, read, dirtied, and written, and the number of temp blocks read and written.
/// </summary>
IConfigureExplainExpressions Buffers();

/// <summary>
/// Include the actual startup time and time spent in the node in the output.
/// </summary>
IConfigureExplainExpressions Timing();
}
}
4 changes: 3 additions & 1 deletion src/Marten/Linq/IMartenQueryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public interface IMartenQueryable
Task<TResult> MinAsync<TResult>(CancellationToken token);
Task<TResult> MaxAsync<TResult>(CancellationToken token);
Task<double> AverageAsync(CancellationToken token);
QueryPlan Explain(FetchType fetchType = FetchType.FetchMany);

/// <param name="configureExplain">Configure EXPLAIN options as documented in <see href="https://www.postgresql.org/docs/9.6/static/sql-explain.html">EXPLAIN documentation</see></param>
QueryPlan Explain(FetchType fetchType = FetchType.FetchMany, Action<IConfigureExplainExpressions> configureExplain = null);

/// <summary>
/// Applies a pre-loaded Javascript transformation to the documents
Expand Down
4 changes: 2 additions & 2 deletions src/Marten/Linq/MartenQueryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ public MartenQueryable(IQueryProvider provider, Expression expression) : base(pr

public MartenQueryExecutor Executor => Provider.As<MartenQueryProvider>().Executor.As<MartenQueryExecutor>();

public QueryPlan Explain(FetchType fetchType = FetchType.FetchMany)
public QueryPlan Explain(FetchType fetchType = FetchType.FetchMany, Action<IConfigureExplainExpressions> configureExplain = null)
{
var handler = toDiagnosticHandler(fetchType);

var cmd = CommandBuilder.ToCommand(handler);

return Executor.As<MartenQueryExecutor>().Connection.ExplainQuery(cmd);
return Executor.As<MartenQueryExecutor>().Connection.ExplainQuery(cmd, configureExplain);
}

public IQueryable<TDoc> TransformTo<TDoc>(string transformName)
Expand Down
50 changes: 49 additions & 1 deletion src/Marten/Linq/QueryPlan.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Marten.Linq
{
// It seems that the output schema is not documented anywhere, so all the possible outputs
// should be deciphered from explain.c in PG backend codebase
// https://github.com/postgres/postgres/blob/18ce3a4ab22d2984f8540ab480979c851dae5338/src/backend/commands/explain.c

public class QueryPlan
{
/// <summary>
Expand Down Expand Up @@ -46,10 +51,53 @@ public class QueryPlan
/// </summary>
[JsonProperty(PropertyName = "Plan Width")]
public int PlanWidth { get; set; }

[JsonProperty(PropertyName = "Parallel Aware")]
public bool ParallelAware { get; set; }

[JsonProperty(PropertyName = "Actual Startup Time")]
public decimal ActualStartupTime { get; set; }

[JsonProperty(PropertyName = "Actual Total Time")]
public decimal ActualTotalTime { get; set; }

[JsonProperty(PropertyName = "Actual Rows")]
public int ActualRows { get; set; }

[JsonProperty(PropertyName = "Actual Loops")]
public int ActualLoops { get; set; }

[JsonProperty(PropertyName = "Output")]
public string[] Output { get; set; }

[JsonProperty(PropertyName = "Sort Key")]
public string[] SortKey { get; set; }

[JsonProperty(PropertyName = "Sort Method")]
public string SortMethod { get; set; }

[JsonProperty(PropertyName = "Sort Space Used")]
public double SortSpaceUsed { get; set; }

[JsonProperty(PropertyName = "Sort Space Type")]
public string SortSpaceType { get; set; }

[JsonProperty(PropertyName = "Plans")]
public QueryPlan[] Plans { get; set; }

// Lifted these from QueryPlanContainer so as not to change the returned type alltogether :|
public decimal PlanningTime { get; set; }
public decimal ExecutionTime { get; set; }
}

class QueryPlanContainer
{
public QueryPlan Plan { get; set; }

[JsonProperty(PropertyName = "Planning Time")]
public decimal PlanningTime { get; set; }

[JsonProperty(PropertyName = "Execution Time")]
public decimal ExecutionTime { get; set; }
}
}
4 changes: 2 additions & 2 deletions src/Marten/QueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ namespace Marten
{
public static class QueryableExtensions
{
public static QueryPlan Explain<T>(this IQueryable<T> queryable)
public static QueryPlan Explain<T>(this IQueryable<T> queryable, Action<IConfigureExplainExpressions> configureExplain = null)
{
return queryable.As<IMartenQueryable<T>>().Explain();
return queryable.As<IMartenQueryable<T>>().Explain(configureExplain: configureExplain);
}

#region ToList
Expand Down
17 changes: 14 additions & 3 deletions src/Marten/Services/CommandRunnerExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
Expand All @@ -17,16 +18,26 @@ public static int Execute(this IManagedConnection runner, string sql)
return runner.Execute(cmd => cmd.WithText(sql).ExecuteNonQuery());
}

public static QueryPlan ExplainQuery(this IManagedConnection runner, NpgsqlCommand cmd)
public static QueryPlan ExplainQuery(this IManagedConnection runner, NpgsqlCommand cmd, Action<IConfigureExplainExpressions> configureExplain = null)
{
var serializer = new JsonNetSerializer();
cmd.CommandText = string.Concat("explain (format json) ", cmd.CommandText);

var config = new ConfigureExplainExpressions();
configureExplain?.Invoke(config);

cmd.CommandText = string.Concat($"explain ({config} format json) ", cmd.CommandText);
return runner.Execute(cmd, c =>
{
using (var reader = cmd.ExecuteReader())
{
var queryPlans = reader.Read() ? serializer.FromJson<QueryPlanContainer[]>(reader.GetTextReader(0)) : null;
return queryPlans?[0].Plan;
var planToReturn = queryPlans?[0].Plan;
if (planToReturn != null)
{
planToReturn.PlanningTime = queryPlans[0].PlanningTime;
planToReturn.ExecutionTime = queryPlans[0].ExecutionTime;
}
return planToReturn;
}
});
}
Expand Down

0 comments on commit b7d81ea

Please sign in to comment.