Skip to main content

SQL CLR string functions, including trim, regex replace and more.

using System;
using System.Data.SqlTypes;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;

namespace SqlClrCommon
{
    public partial class SqlFunctions
    {
        /// <summary>
        /// Removes all control characters (non-printable)
        /// from the specified string.
        /// </summary>
        /// <param name="input">
        /// The string to remove control characters from.
        /// </param>
        /// <returns>
        /// The string with all non-printable
        /// (i.e. control characters) removed.
        /// </returns>
        /// <remarks>
        /// Control characters are those with a Unicode designation
        /// of 'Cc' (control), with values in the range U+0000 to U+001F,
        /// or U+007F to U+009F.
        /// </remarks>
        [SqlFunction(
            Name = "Clean",
            DataAccess = DataAccessKind.None,
            SystemDataAccess = SystemDataAccessKind.None)]
        public static SqlChars Clean(SqlChars input)
        {
            if (input.IsNull)
            {
                return SqlChars.Null;
            }

            char[] chars = input.Value;
            StringBuilder stringBuilder = new StringBuilder(chars.Length);
            for (int index = 0; index < chars.Length; ++index)
            {
                if (!char.IsControl(chars[index]))
                {
                    stringBuilder.Append(chars[index]);
                }
            }

            return new SqlChars(stringBuilder.ToString());
        }

        /// <summary>
        /// Trims leading and trailing whitespace from the
        /// specified string.
        /// </summary>
        /// <param name="input">The string to trim.</param>
        /// <returns>The string with leading and trailing 
        /// whitespace removed.</returns>
        [SqlFunction(
            Name = "Trim",
            DataAccess = DataAccessKind.None,
            SystemDataAccess = SystemDataAccessKind.None)]
        public static SqlChars Trim(SqlChars input)
        {
            if (input.IsNull)
            {
                return SqlChars.Null;
            }

            return new SqlChars(new string(input.Value).Trim());
        }

        /// <summary>
        /// Replaces all matches of the specified regular
        /// expression pattern found in the body with the
        /// specified replacementPattern pattern; starting the search at
        /// the specified start position.
        /// </summary>
        /// <param name="input">The input value that will get replaced.</param>
        /// <param name="pattern">The regular expression pattern.</param>
        /// <param name="replacementPattern">
        /// This must be a syntactically-correct regular expression
        /// replacement pattern that matches pattern in its references
        /// to capture groups, otherwise an exception will be thrown.
        /// </param>
        /// <param name="start"></param>
        /// <returns>The string with the matched pattern replaced.</returns>
        /// <remarks>
        /// For documentation on replacement patterns
        /// see MSDN, NET Framework Regular Expressions, Substitution.
        /// </remarks>
        /// <example>
        ///     <code>
        ///         -- Update a table of names in the format &quot;Lastname, First Names&quot;
        ///         -- to &quot;First Names Lastname&quot;. This illustrates the use of named
        ///         -- capture groups (lastname, forenames).
        ///         UPDATE names
        ///         SET name = dbo.RXREPLACE(name,
        ///             '^(?&lt;lastname&gt;[A-Za-z\-\']+),\s*(?&lt;forenames&gt;.+)$',
        ///             '${forenames} ${lastname}', 1);
        ///     </code>
        /// </example>
        [SqlFunction(
            Name = "RegexReplace",
            DataAccess = DataAccessKind.None,
            SystemDataAccess = SystemDataAccessKind.None)]
        public static SqlChars RegexReplace(
            SqlChars input,
            SqlChars pattern,
            SqlChars replacementPattern,
            SqlInt32 start)
        {
            if (input.IsNull)
            {
                return SqlChars.Null;
            }
            if (pattern.IsNull || replacementPattern.IsNull)
            {
                return input;
            }

            int startIndex = !start.IsNull ? start.Value - 1 : 0;
            if (startIndex >= input.Value.Length)
            {
                throw new ArgumentException(
                    string.Format("RegexReplace: Start expected to be between 1 and {0:G} (got {1:G}",
                        input.Value.Length, startIndex + 1));
            }

            return new SqlChars(Regex.Replace(
                new string(input.Value).Substring(startIndex),
                new string(pattern.Value),
                new string(replacementPattern.Value))
            );
        }
    }
}