1010using System . Data . Common ;
1111using System . Data . SqlClient ;
1212using System . Linq ;
13+ using System . Runtime . CompilerServices ;
1314using System . Text ;
1415using JetBrains . Annotations ;
1516using 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