1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq.Mapping;
using System.Data.Linq.Provider;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace System.Data.Linq.SqlClient {
internal static class SqlIdentifier
{
private static SqlCommandBuilder builder = new SqlCommandBuilder();
const string ParameterPrefix = "@";
const string QuotePrefix = "[";
const string QuoteSuffix = "]";
const string SchemaSeparator = ".";
const char SchemaSeparatorChar = '.';
private static bool IsQuoted(string s) {
if (s == null) {
throw Error.ArgumentNull("s");
}
if (s.Length < 2) {
return false;
}
return s.StartsWith(QuotePrefix, StringComparison.Ordinal)
&& s.EndsWith(QuoteSuffix, StringComparison.Ordinal);
}
// This is MSSQL-specific quoting.
// If the string begins and ends with [ and ], it will be assumed to already be quoted.
// Otherwise periods are assumed to be namespace delimiters, and the string is split on each.
// Each string from the split is then check to see if it is already quoted, and if
// not, it is replaced with the result of SqlCommandBuilder.QuoteIdentifier.
// Then the set of strings is rejoined with periods.
internal static string QuoteCompoundIdentifier(string s) {
if (s == null) {
throw Error.ArgumentNull("s");
}
// if it starts with @, then return unprocessed
if (s.StartsWith(ParameterPrefix, StringComparison.Ordinal)) {
return s;
} else if (IsQuoted(s)) {
return s;
}
else if (!s.StartsWith(QuotePrefix, StringComparison.Ordinal) && s.EndsWith(QuoteSuffix, StringComparison.Ordinal)) {
//A.[B] => [A].[B]
int splitPosition = s.IndexOf(SchemaSeparatorChar);
if (splitPosition < 0){ //no . in the string
return builder.QuoteIdentifier(s);
}
string left = s.Substring(0, splitPosition);
string right = s.Substring(splitPosition + 1, s.Length - splitPosition - 1);
if (!IsQuoted(right)) {
right = builder.QuoteIdentifier(right);
}
return String.Concat(QuoteCompoundIdentifier(left), SchemaSeparatorChar + right);
}
else if (s.StartsWith(QuotePrefix, StringComparison.Ordinal) && !s.EndsWith(QuoteSuffix, StringComparison.Ordinal)) {
//[A].B => [A].[B]
int splitPosition = s.LastIndexOf(SchemaSeparatorChar);
if (splitPosition < 0){ //no . in the string
return builder.QuoteIdentifier(s);
}
string left = s.Substring(0, splitPosition);
if (!IsQuoted(left)) {
left = builder.QuoteIdentifier(left);
}
string right = s.Substring(splitPosition + 1, s.Length - splitPosition - 1);
return String.Concat(left + SchemaSeparatorChar, QuoteCompoundIdentifier(right));
}
else {
int splitPosition = s.IndexOf(SchemaSeparatorChar);
if (splitPosition < 0) { //no . in the string
//A => [A]
return builder.QuoteIdentifier(s);
}
string left = s.Substring(0, splitPosition);
string right = s.Substring(splitPosition + 1, s.Length - splitPosition - 1);
return String.Concat(QuoteCompoundIdentifier(left) + SchemaSeparatorChar, QuoteCompoundIdentifier(right));
}
}
// This is MSSQL-specific quoting.
// This is the same as above, but it doesn't consider anything compound.
internal static string QuoteIdentifier(string s) {
if (s == null) {
throw Error.ArgumentNull("s");
}
// if it starts with @, then return unprocessed
if (s.StartsWith(ParameterPrefix, StringComparison.Ordinal)) {
return s;
} else if (IsQuoted(s)) {
return s;
} else {
return builder.QuoteIdentifier(s);
}
}
// turns "[ABC].[PQR].[XYZ]" into {"[ABC]", "[PQR]", "[XYZ]"}
internal static IEnumerable<string> GetCompoundIdentifierParts(string s) {
if (s == null) {
throw Error.ArgumentNull("s");
}
// can't do this to parameters
if (s.StartsWith(ParameterPrefix, StringComparison.Ordinal)) {
throw Error.ArgumentWrongValue("s");
}
string quotedS = QuoteCompoundIdentifier(s);
string pattern = @"^(?<component>\[([^\]]|\]\])*\])(\.(?<component>\[([^\]]|\]\])*\]))*$";
// This pattern matches "."-delimited quoted SQL identifiers. Here's how:
//
// 1. It is wrapped in "^" and "$", which match the begining and end of text, so it will match
// only the entire text and not any sub-part.
// 2. The group "(?<component>\[([^\]]|\]\])*\])" captures a single quoted segment of the text.
// It's a literal "[" followed by any number of non-"]" characters or "]]" strings, followed
// by a literal "]". The "?<component>" bit names the capture so we can refer to it.
// 3. After the first component, we will allow any number of groups which consist of a literal
// "." followed by a component (and the component part is a repeat of the part described in 2).
Match m = Regex.Match(quotedS, pattern);
if (!m.Success)
{
throw Error.ArgumentWrongValue("s");
}
foreach (Capture cap in m.Groups["component"].Captures)
{
yield return cap.Value;
}
}
}
}
|