U4-8837 Change paging query for SQL server 2012+ to be more efficient
This commit is contained in:
@@ -381,8 +381,6 @@ namespace Umbraco.Core
|
||||
connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString;
|
||||
}
|
||||
Initialize(providerName, connString);
|
||||
|
||||
DetermineSqlServerVersion();
|
||||
}
|
||||
else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false)
|
||||
{
|
||||
@@ -419,8 +417,6 @@ namespace Umbraco.Core
|
||||
|
||||
//Remove the legacy connection string, so we don't end up in a loop if something goes wrong.
|
||||
GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName);
|
||||
|
||||
DetermineSqlServerVersion();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -465,48 +461,6 @@ namespace Umbraco.Core
|
||||
Initialize(providerName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the lazy resolution of determining the SQL server version if that is the db type we're using
|
||||
/// </summary>
|
||||
private void DetermineSqlServerVersion()
|
||||
{
|
||||
|
||||
var sqlServerSyntax = SqlSyntax as SqlServerSyntaxProvider;
|
||||
if (sqlServerSyntax != null)
|
||||
{
|
||||
//this will not execute now, it is lazy so will only execute when we need to actually know
|
||||
// the sql server version.
|
||||
sqlServerSyntax.VersionName = new Lazy<SqlServerVersionName>(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var database = this._factory.CreateDatabase();
|
||||
|
||||
var version = database.ExecuteScalar<string>("SELECT SERVERPROPERTY('productversion')");
|
||||
var firstPart = version.Split('.')[0];
|
||||
switch (firstPart)
|
||||
{
|
||||
case "11":
|
||||
return SqlServerVersionName.V2012;
|
||||
case "10":
|
||||
return SqlServerVersionName.V2008;
|
||||
case "9":
|
||||
return SqlServerVersionName.V2005;
|
||||
case "8":
|
||||
return SqlServerVersionName.V2000;
|
||||
case "7":
|
||||
return SqlServerVersionName.V7;
|
||||
default:
|
||||
return SqlServerVersionName.Other;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return SqlServerVersionName.Invalid;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal DatabaseSchemaResult ValidateDatabaseSchema()
|
||||
{
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace Umbraco.Core.Persistence
|
||||
CommonConstruct();
|
||||
}
|
||||
|
||||
enum DBType
|
||||
internal enum DBType
|
||||
{
|
||||
SqlServer,
|
||||
SqlServerCE,
|
||||
@@ -726,6 +726,42 @@ namespace Umbraco.Core.Persistence
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NOTE: This is a custom mod of PetaPoco!! This builds the paging sql for different db providers
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="sqlSelectRemoved"></param>
|
||||
/// <param name="sqlOrderBy"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <param name="sqlPage"></param>
|
||||
/// <param name="databaseType"></param>
|
||||
/// <param name="skip"></param>
|
||||
/// <param name="take"></param>
|
||||
internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage)
|
||||
{
|
||||
if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle)
|
||||
{
|
||||
sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, "");
|
||||
if (rxDistinct.IsMatch(sqlSelectRemoved))
|
||||
{
|
||||
sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner";
|
||||
}
|
||||
sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}",
|
||||
sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1);
|
||||
args = args.Concat(new object[] { skip, skip + take }).ToArray();
|
||||
}
|
||||
else if (databaseType == DBType.SqlServerCE)
|
||||
{
|
||||
sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1);
|
||||
args = args.Concat(new object[] { skip, take }).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1);
|
||||
args = args.Concat(new object[] { take, skip }).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void BuildPageQueries<T>(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage)
|
||||
{
|
||||
// Add auto select clause
|
||||
@@ -734,34 +770,12 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
// Split the SQL into the bits we need
|
||||
string sqlSelectRemoved, sqlOrderBy;
|
||||
if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy))
|
||||
if (SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy) == false)
|
||||
throw new Exception("Unable to parse SQL statement for paged query");
|
||||
if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*"))
|
||||
throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id");
|
||||
|
||||
// Build the SQL for the actual final result
|
||||
if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle)
|
||||
{
|
||||
sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, "");
|
||||
if (rxDistinct.IsMatch(sqlSelectRemoved))
|
||||
{
|
||||
sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner";
|
||||
}
|
||||
sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}",
|
||||
sqlOrderBy==null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1);
|
||||
args = args.Concat(new object[] { skip, skip+take }).ToArray();
|
||||
}
|
||||
else if (_dbType == DBType.SqlServerCE)
|
||||
{
|
||||
sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1);
|
||||
args = args.Concat(new object[] { skip, take }).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1);
|
||||
args = args.Concat(new object[] { take, skip }).ToArray();
|
||||
}
|
||||
|
||||
|
||||
BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
|
||||
}
|
||||
|
||||
// Fetch a page
|
||||
@@ -2315,8 +2329,8 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
|
||||
// Member variables
|
||||
string _connectionString;
|
||||
string _providerName;
|
||||
readonly string _connectionString;
|
||||
readonly string _providerName;
|
||||
DbProviderFactory _factory;
|
||||
IDbConnection _sharedConnection;
|
||||
IDbTransaction _transaction;
|
||||
|
||||
@@ -203,7 +203,8 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
//if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts.
|
||||
var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider;
|
||||
if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008)
|
||||
|
||||
if ((sqlServerSyntax != null && (int)sqlServerSyntax.GetVersionName(db) < (int)SqlServerVersionName.V2008)
|
||||
|| SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider)
|
||||
{
|
||||
//SqlCe doesn't support bulk insert statements!
|
||||
|
||||
@@ -14,12 +14,57 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
public SqlServerSyntaxProvider()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the version of the current SQL server instance
|
||||
/// </summary>
|
||||
internal Lazy<SqlServerVersionName> VersionName { get; set; }
|
||||
internal SqlServerVersionName GetVersionName(Database database)
|
||||
{
|
||||
if (_versionName.HasValue)
|
||||
return _versionName.Value;
|
||||
|
||||
try
|
||||
{
|
||||
var version = database.ExecuteScalar<string>("SELECT SERVERPROPERTY('productversion')");
|
||||
var firstPart = version.Split('.')[0];
|
||||
switch (firstPart)
|
||||
{
|
||||
case "13":
|
||||
_versionName = SqlServerVersionName.V2014;
|
||||
break;
|
||||
case "12":
|
||||
_versionName = SqlServerVersionName.V2014;
|
||||
break;
|
||||
case "11":
|
||||
_versionName = SqlServerVersionName.V2012;
|
||||
break;
|
||||
case "10":
|
||||
_versionName = SqlServerVersionName.V2008;
|
||||
break;
|
||||
case "9":
|
||||
_versionName = SqlServerVersionName.V2005;
|
||||
break;
|
||||
case "8":
|
||||
_versionName = SqlServerVersionName.V2000;
|
||||
break;
|
||||
case "7":
|
||||
_versionName = SqlServerVersionName.V7;
|
||||
break;
|
||||
default:
|
||||
_versionName = SqlServerVersionName.Other;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_versionName = SqlServerVersionName.Invalid;
|
||||
}
|
||||
|
||||
return _versionName.Value;
|
||||
}
|
||||
|
||||
private SqlServerVersionName? _versionName;
|
||||
|
||||
/// <summary>
|
||||
/// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
/// <summary>
|
||||
/// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// see: https://support.microsoft.com/en-us/kb/321185
|
||||
/// </remarks>
|
||||
internal enum SqlServerVersionName
|
||||
{
|
||||
Invalid = -1,
|
||||
@@ -11,6 +14,8 @@
|
||||
V2005 = 2,
|
||||
V2008 = 3,
|
||||
V2012 = 4,
|
||||
Other = 5
|
||||
V2014 = 5,
|
||||
V2016 = 6,
|
||||
Other = 100
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using StackExchange.Profiling;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
@@ -154,5 +155,41 @@ namespace Umbraco.Core.Persistence
|
||||
}
|
||||
base.OnExecutedCommand(cmd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We are overriding this in the case that we are using SQL Server 2012+ so we can make paging more efficient than the default PetaPoco paging
|
||||
/// see: http://issues.umbraco.org/issue/U4-8837
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="sqlSelectRemoved"></param>
|
||||
/// <param name="sqlOrderBy"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <param name="sqlPage"></param>
|
||||
/// <param name="databaseType"></param>
|
||||
/// <param name="skip"></param>
|
||||
/// <param name="take"></param>
|
||||
internal override void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage)
|
||||
{
|
||||
if (databaseType == DBType.SqlServer)
|
||||
{
|
||||
//we need to check it's version to see what kind of paging format we can use
|
||||
//TODO: This is a hack, but we don't have access to the SqlSyntaxProvider here, we can in v8 but not now otherwise
|
||||
// this would be a breaking change.
|
||||
var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider;
|
||||
if (sqlServerSyntax != null)
|
||||
{
|
||||
if ((int) sqlServerSyntax.GetVersionName(this) >= (int) SqlServerVersionName.V2012)
|
||||
{
|
||||
//we can use the good paging! to do that we are going to change the databaseType to SQLCE since
|
||||
//it also uses the good paging syntax.
|
||||
base.BuildSqlDbSpecificPagingQuery(DBType.SqlServerCE, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//use the defaults
|
||||
base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user