using System; using System.Collections; using System.Collections.Generic; using NHibernate.Util; namespace NHibernate.SqlCommand { /// <summary> /// The SqlStringBuilder is used to construct a SqlString. /// </summary> /// <remarks> /// <para> /// The SqlString is a nonmutable class so it can't have sql parts added /// to it. Instead this class should be used to generate a new SqlString. /// The SqlStringBuilder is to SqlString what the StringBuilder is to /// a String. /// </para> /// <para> /// This is different from the original version of SqlString because this does not /// hold the sql string in the form of "column1=@column1" instead it uses an array to /// build the sql statement such that /// object[0] = "column1=" /// object[1] = ref to column1 parameter /// </para> /// <para> /// What this allows us to do is to delay the generating of the parameter for the sql /// until the very end - making testing dialect indifferent. Right now all of our test /// to make sure the correct sql is getting built are specific to MsSql2000Dialect. /// </para> /// </remarks> public class SqlStringBuilder : ISqlStringBuilder { // this holds the strings and parameters that make up the full sql statement. private List<object> sqlParts; private AddingSqlStringVisitor addingVisitor; private AddingSqlStringVisitor AddingVisitor { get { if (addingVisitor == null) { addingVisitor = new AddingSqlStringVisitor(this); } return addingVisitor; } } /// <summary> /// Create an empty StringBuilder with the default capacity. /// </summary> public SqlStringBuilder() : this(16) { } /// <summary> /// Create a StringBuilder with a specific capacity. /// </summary> /// <param name="partsCapacity">The number of parts expected.</param> public SqlStringBuilder(int partsCapacity) { sqlParts = new List<object>(partsCapacity); } /// <summary> /// Create a StringBuilder to modify the SqlString /// </summary> /// <param name="sqlString">The SqlString to modify.</param> public SqlStringBuilder(SqlString sqlString) { sqlParts = new List<object>(sqlString.Count); Add(sqlString); } /// <summary> /// Adds the preformatted sql to the SqlString that is being built. /// </summary> /// <param name="sql">The string to add.</param> /// <returns>This SqlStringBuilder</returns> public SqlStringBuilder Add(string sql) { if (StringHelper.IsNotEmpty(sql)) { sqlParts.Add(sql); } return this; } /// <summary> /// Adds the Parameter to the SqlString that is being built. /// The correct operator should be added before the Add(Parameter) is called /// because there will be no operator ( such as "=" ) placed between the last Add call /// and this Add call. /// </summary> /// <param name="parameter">The Parameter to add.</param> /// <returns>This SqlStringBuilder</returns> public SqlStringBuilder Add(Parameter parameter) { if (parameter != null) { sqlParts.Add(parameter); } return this; } public SqlStringBuilder AddParameter() { return Add(Parameter.Placeholder); } /// <summary> /// Attempts to discover what type of object this is and calls the appropriate /// method. /// </summary> /// <param name="part">The part to add when it is not known if it is a Parameter, String, or SqlString.</param> /// <returns>This SqlStringBuilder.</returns> /// <exception cref="ArgumentException">Thrown when the part is not a Parameter, String, or SqlString.</exception> public SqlStringBuilder AddObject(object part) { if (part == null) { return this; } Parameter paramPart = part as Parameter; if (paramPart != null) { return Add(paramPart); // EARLY EXIT } string stringPart = part as string; if (StringHelper.IsNotEmpty(stringPart)) { return Add(stringPart); } SqlString sqlPart = part as SqlString; if (SqlStringHelper.IsNotEmpty(sqlPart)) { return Add(sqlPart); } // remarks - we should not get to here - this is a problem with the // SQL being generated. if (paramPart == null && stringPart == null && sqlPart == null) { throw new ArgumentException("Part was not a Parameter, String, or SqlString."); } return this; } /// <summary> /// Adds an existing SqlString to this SqlStringBuilder. It does NOT add any /// prefix, postfix, operator, or wrap around this. It is equivalent to just /// adding a string. /// </summary> /// <param name="sqlString">The SqlString to add to this SqlStringBuilder</param> /// <returns>This SqlStringBuilder</returns> public SqlStringBuilder Add(SqlString sqlString) { sqlString?.Visit(AddingVisitor); return this; } /// <summary> /// Adds an existing SqlString to this SqlStringBuilder /// </summary> /// <param name="sqlString">The SqlString to add to this SqlStringBuilder</param> /// <param name="prefix">String to put at the beginning of the combined SqlString.</param> /// <param name="op">How these Statements should be junctioned "AND" or "OR"</param> /// <param name="postfix">String to put at the end of the combined SqlString.</param> /// <returns>This SqlStringBuilder</returns> /// <remarks> /// This calls the overloaded Add method with an array of SqlStrings and wrapStatement=false /// so it will not be wrapped with a "(" and ")" /// </remarks> public SqlStringBuilder Add(SqlString sqlString, string prefix, string op, string postfix) { return Add(new SqlString[] {sqlString}, prefix, op, postfix, false); } /// <summary> /// Adds existing SqlStrings to this SqlStringBuilder /// </summary> /// <param name="sqlStrings">The SqlStrings to combine.</param> /// <param name="prefix">String to put at the beginning of the combined SqlString.</param> /// <param name="op">How these SqlStrings should be junctioned "AND" or "OR"</param> /// <param name="postfix">String to put at the end of the combined SqlStrings.</param> /// <returns>This SqlStringBuilder</returns> /// <remarks>This calls the overloaded Add method with wrapStatement=true</remarks> public SqlStringBuilder Add(SqlString[] sqlStrings, string prefix, string op, string postfix) { return Add(sqlStrings, prefix, op, postfix, true); } /// <summary> /// Adds existing SqlStrings to this SqlStringBuilder /// </summary> /// <param name="sqlStrings">The SqlStrings to combine.</param> /// <param name="prefix">String to put at the beginning of the combined SqlStrings.</param> /// <param name="op">How these SqlStrings should be junctioned "AND" or "OR"</param> /// <param name="postfix">String to put at the end of the combined SqlStrings.</param> /// <param name="wrapStatement">Wrap each SqlStrings with "(" and ")"</param> /// <returns>This SqlStringBuilder</returns> public SqlStringBuilder Add(SqlString[] sqlStrings, string prefix, string op, string postfix, bool wrapStatement) { if (StringHelper.IsNotEmpty(prefix)) { sqlParts.Add(prefix); } bool opNeeded = false; foreach (SqlString sqlString in sqlStrings) { if (sqlString == null || sqlString.Count == 0) { continue; } if (opNeeded) { sqlParts.Add(" " + op + " "); } opNeeded = true; if (wrapStatement) { sqlParts.Add("("); } Add(sqlString); if (wrapStatement) { sqlParts.Add(")"); } } if (postfix != null) { sqlParts.Add(postfix); } return this; } /// <summary> /// Gets the number of SqlParts in this SqlStringBuilder. /// </summary> /// <returns> /// The number of SqlParts in this SqlStringBuilder. /// </returns> public int Count { get { return sqlParts.Count; } } /// <summary> /// Gets or Sets the element at the index /// </summary> /// <value>Returns a string or Parameter.</value> /// <remarks></remarks> public object this[int index] { get { return sqlParts[index]; } set { sqlParts[index] = value; } } /// <summary> /// Insert a string containing sql into the SqlStringBuilder at the specified index. /// </summary> /// <param name="index">The zero-based index at which the sql should be inserted.</param> /// <param name="sql">The string containing sql to insert.</param> /// <returns>This SqlStringBuilder</returns> public SqlStringBuilder Insert(int index, string sql) { sqlParts.Insert(index, sql); return this; } /// <summary> /// Insert a Parameter into the SqlStringBuilder at the specified index. /// </summary> /// <param name="index">The zero-based index at which the Parameter should be inserted.</param> /// <param name="param">The Parameter to insert.</param> /// <returns>This SqlStringBuilder</returns> public SqlStringBuilder Insert(int index, Parameter param) { sqlParts.Insert(index, param); return this; } /// <summary> /// Removes the string or Parameter at the specified index. /// </summary> /// <param name="index">The zero-based index of the item to remove.</param> /// <returns>This SqlStringBuilder</returns> public SqlStringBuilder RemoveAt(int index) { sqlParts.RemoveAt(index); return this; } /// <summary> /// Converts the mutable SqlStringBuilder into the immutable SqlString. /// </summary> /// <returns>The SqlString that was built.</returns> public SqlString ToSqlString() { return new SqlString(sqlParts); } public override string ToString() { return ToSqlString().ToString(); } private class AddingSqlStringVisitor : ISqlStringVisitor { private SqlStringBuilder parent; public AddingSqlStringVisitor(SqlStringBuilder parent) { this.parent = parent; } public void String(string text) { parent.Add(text); } public void String(SqlString sqlString) { parent.Add(sqlString); } public void Parameter(Parameter parameter) { parent.Add(parameter); } } public void Clear() { sqlParts.Clear(); } } }