Skip to content

Commit 3bd8695

Browse files
alexyakuninAlexUstinov
authored andcommitted
Speedup SqlHelper.Quote* methods.
1 parent b4d0472 commit 3bd8695

1 file changed

Lines changed: 82 additions & 27 deletions

File tree

Orm/Xtensive.Orm/Sql/SqlHelper.cs

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Data.Common;
1111
using System.Data.SqlClient;
1212
using System.Linq;
13+
using System.Runtime.CompilerServices;
1314
using System.Text;
1415
using JetBrains.Annotations;
1516
using Xtensive.Collections;
@@ -25,6 +26,31 @@ namespace Xtensive.Sql
2526
/// </summary>
2627
public static class SqlHelper
2728
{
29+
public readonly struct EscapeSetup
30+
{
31+
public static readonly EscapeSetup WithQuotes =
32+
new EscapeSetup('.', '\"', '\"', '\"', '\"');
33+
public static readonly EscapeSetup WithBrackets =
34+
new EscapeSetup('.', '[', ']', ']', ']');
35+
public static readonly EscapeSetup WithBackTick =
36+
new EscapeSetup('.', '`', '`', '`', '`');
37+
38+
public readonly char Delimiter;
39+
public readonly char Opener;
40+
public readonly char Closer;
41+
public readonly char EscapeCloser1;
42+
public readonly char EscapeCloser2;
43+
44+
public EscapeSetup(char delimiter, char opener, char closer, char escapeCloser1, char escapeCloser2)
45+
{
46+
Delimiter = delimiter;
47+
Opener = opener;
48+
Closer = closer;
49+
EscapeCloser1 = escapeCloser1;
50+
EscapeCloser2 = escapeCloser2;
51+
}
52+
}
53+
2854
/// <summary>
2955
/// Validates the specified URL againts charactes that usually forbidden inside connection strings.
3056
/// </summary>
@@ -45,44 +71,73 @@ public static void ValidateConnectionUrl(UrlInfo url)
4571
/// Quotes the specified identifier with quotes (i.e. "").
4672
/// </summary>
4773
/// <returns>Quoted identifier.</returns>
48-
public static string QuoteIdentifierWithQuotes(string[] names)
49-
{
50-
return Quote("\"", "\"", ".", "\"\"", names);
51-
}
74+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
75+
public static string QuoteIdentifierWithQuotes(string[] names)
76+
=> Quote(EscapeSetup.WithQuotes, names);
5277

5378
/// <summary>
5479
/// Quotes the specified identifier with square brackets (i.e. []).
5580
/// </summary>
5681
/// <returns>Quoted indentifier.</returns>
57-
public static string QuoteIdentifierWithBrackets(string[] names)
58-
{
59-
return Quote("[", "]", ".", "]]", names);
60-
}
82+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
83+
public static string QuoteIdentifierWithBrackets(string[] names)
84+
=> Quote(EscapeSetup.WithBrackets, names);
6185

6286
/// <summary>
6387
/// Quotes the specified identifier with square brackets (i.e. ``).
6488
/// </summary>
65-
/// <returns>Quoted indentifier.</returns>
66-
public static string QuoteIdentifierWithBackTick(string[] names)
67-
{
68-
return Quote("`", "`", ".", "``", names);
69-
}
89+
/// <returns>Quoted identifier.</returns>
90+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91+
public static string QuoteIdentifierWithBackTick(string[] names)
92+
=> Quote(EscapeSetup.WithBackTick, names);
7093

71-
private static string Quote(string openingBracket, string closingBracket, string delimiter,
72-
string escapedClosingBracket, IEnumerable<string> names)
94+
private static unsafe string Quote(in EscapeSetup setup, string[] names)
7395
{
74-
var tokens = names.Where(name => !string.IsNullOrEmpty(name)).ToArray();
75-
var builder = new StringBuilder();
76-
for (int i = 0; i < tokens.Length - 1; i++) {
77-
builder.Append(openingBracket);
78-
builder.Append(tokens[i].Replace(closingBracket, escapedClosingBracket));
79-
builder.Append(closingBracket);
80-
builder.Append(delimiter);
96+
// That's one of frequently called methods, so it's optimized for speed.
97+
98+
// 1. Find resultLength
99+
var resultLength = 0;
100+
foreach (var name in names) {
101+
if (string.IsNullOrEmpty(name))
102+
continue;
103+
if (resultLength != 0)
104+
resultLength++;
105+
resultLength += 2 + name.Length;
106+
var start = 0;
107+
while (true) {
108+
start = 1 + name.IndexOf(setup.Closer, start);
109+
if (start == 0)
110+
break;
111+
resultLength++;
112+
}
81113
}
82-
builder.Append(openingBracket);
83-
builder.Append(tokens[tokens.Length - 1].Replace(closingBracket, escapedClosingBracket));
84-
builder.Append(closingBracket);
85-
return builder.ToString();
114+
115+
// 2. Create the resulting string at once
116+
var result = new string(setup.Delimiter, resultLength);
117+
fixed (char* pResult = result) {
118+
var p = pResult;
119+
foreach (var name in names) {
120+
if (string.IsNullOrEmpty(name))
121+
continue;
122+
if (p != pResult)
123+
p++; // Skip the delimiter (initial result value is a repeating delimiter)
124+
*p++ = setup.Opener;
125+
fixed (char* pName = name) {
126+
var pNameEnd = pName + name.Length;
127+
for (var pn = pName; pn < pNameEnd; pn++) {
128+
var c = *pn;
129+
if (c == setup.Closer) {
130+
*p++ = setup.EscapeCloser1;
131+
*p++ = setup.EscapeCloser2;
132+
continue;
133+
}
134+
*p++ = c;
135+
}
136+
}
137+
*p++ = setup.Closer;
138+
}
139+
}
140+
return result;
86141
}
87142

88143
/// <summary>
@@ -379,4 +434,4 @@ public static NotSupportedException NotSupported(ServerFeatures feature)
379434
return NotSupported(feature.ToString());
380435
}
381436
}
382-
}
437+
}

0 commit comments

Comments
 (0)