/*
* Olbert.Utilities.Misc
* miscellaneous items for simplifying .NET programming
* Copyright (C) 2011 Mark A. Olbert
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Collections;
using System.Text.RegularExpressions;
using System.IO;
using System.Data.SqlClient;
namespace Olbert.Utilities
{
/// <summary>
/// a class, derived from ResourceTextFile, representing an sql command file in an SourceAssembly.
/// <para>The class is enumerable, with each element representing a single section in the
/// underlying sql command file. A 'section' ends with the sql keyword 'GO' (case-insensitive)
/// or the end of the file.</para>
/// </summary>
public class ResourceSqlFile : ResourceTextFile, IEnumerable<string>
{
private List<string> segments;
private bool loadingFile = false;
/// <summary>
/// Initializes an instance and assigns it to a ResourceDirectory object. Content from the underlying
/// resource is not loaded when the instance is created. Loading from the underlying resource occurs
/// when the content is accessed.
/// </summary>
/// <param name="parent">the ResourceDirectory to which the object belongs</param>
/// <param name="name">the name of the ResourceFile, which must be unique within the
/// owning ResourceDirectory</param>
public ResourceSqlFile( ResourceDirectory parent, string name )
:base(parent, name)
{
}
/// <summary>
/// Initializes an instance and assigns it to a ResourceDirectory object. The Stream containing the contents
/// of the underlying resource will be used when the content is accessed. That Stream does not have to
/// be directly tied to an underlying resource. For example, it could be a MemoryStream derived from
/// decompressing a zipped resource.
/// </summary>
/// <param name="parent">the ResourceDirectory to which the object belongs</param>
/// <param name="name">the name of the ResourceFile, which must be unique within the
/// owning ResourceDirectory</param>
/// <param name="dataStream">a stream containing the contents of the object</param>
public ResourceSqlFile( ResourceDirectory parent, string name, Stream dataStream )
: base(parent, name, dataStream)
{
}
/// <summary>
/// Gets the list of command segments in the underlying sql file. A command segment is defined as
/// a series of lines ended by the sql keyword 'GO' (case-insensitive) or the end of the file.
/// </summary>
public List<string> Segments
{
get
{
// if segments is not defined we haven't loaded the contents yet,
// so do so
if( segments == null )
{
segments = new List<string>();
// avoid infinite recursions...
if( !loadingFile ) LoadFile();
}
return segments;
}
}
/// <summary>
/// Gets the command segment at the specified index location. A command segment is defined as
/// a series of lines ended by the sql keyword 'GO' (case-insensitive) or the end of the file.
/// </summary>
/// <param name="idx">the index location</param>
/// <returns>the command segment at the specified index location.</returns>
public string this[int idx]
{
get
{
if( ( idx < 0 ) || ( idx >= Segments.Count ) ) return String.Empty;
return Segments[idx];
}
}
/// <summary>
/// Executes the contents of the instance on the supplied SqlConnection
/// </summary>
/// <param name="conn">the SqlConnection on which to run the command file</param>
/// <param name="trans">an SqlTransaction object to manage commits and rollbacks, or null
/// if you want the method to handle the commit/rollback process internally.</param>
/// <param name="suppressExceptions">true (default) to hide exceptions and return false, false to
/// allow the exception to propagate</param>
/// <returns>true if the execution succeeded, false if the supplied connection is
/// undefined or if the execution failed</returns>
public bool Execute( SqlConnection conn, SqlTransaction trans = null, bool suppressExceptions = true )
{
if( conn == null ) return false;
return conn.ExecuteNonQuery(TextContent, trans, suppressExceptions);
}
/// <summary>
/// Executes the contents of the instance segment by segment on the supplied SqlConnection.
/// A segment is a sequence of T-SQL statements terminated by a GO command (case-insensitive)
/// </summary>
/// <param name="conn">the SqlConnection on which to run the command file</param>
/// <param name="trans">an SqlTransaction object to manage commits and rollbacks, or null
/// if you want the method to handle the commit/rollback process internally.</param>
/// <param name="suppressExceptions">true (default) to hide exceptions and return false, false to
/// allow the exception to propagate</param>
/// <returns>true if all the segments were executed successfully, false if the supplied
/// connection is undefined or if any segment execution failed (execution stops on the
/// first segment failure).</returns>
public bool ExecuteSegments( SqlConnection conn, SqlTransaction trans = null, bool suppressExceptions = true )
{
if( conn == null ) return false;
for( int idx = 0; idx < Segments.Count; idx++ )
{
if( !conn.ExecuteNonQuery(this[idx], trans, suppressExceptions) )
return false;
}
return true;
}
/// <summary>
/// Executes the contents of the specified segment on the supplied SqlConnection
/// </summary>
/// <param name="conn">the SqlConnection on which to run the command file</param>
/// <param name="segmentNumber">the number of the segment to run</param>
/// <param name="trans">an SqlTransaction object to manage commits and rollbacks, or null
/// if you want the method to handle the commit/rollback process internally.</param>
/// <param name="suppressExceptions">true (default) to hide exceptions and return false, false to
/// allow the exception to propagate</param>
/// <returns>true if the execution succeeded, false if the supplied connection is
/// undefined, the segment number is invalid or the execution failed</returns>
public bool Execute( SqlConnection conn, int segmentNumber, SqlTransaction trans = null, bool suppressExceptions = true )
{
if( conn == null ) return false;
if( ( segmentNumber < 0 ) || ( segmentNumber >= Segments.Count ) )
return false;
return conn.ExecuteNonQuery(this[segmentNumber], trans, suppressExceptions);
}
/// <summary>
/// Overrides the base class implementation to read the contents of the underlying
/// resource and extract whatever command segments it contains.
/// </summary>
protected override void LoadFile()
{
loadingFile = true; // guard flag to avoid infinite recursions
StringBuilder segBuilder = new StringBuilder();
StringBuilder builder = new StringBuilder();
ResetStream();
string curLine;
while( !StreamReader.EndOfStream )
{
// read the line and add it to the segment builder, as well
// as the overall content builder. However, suppress adding
// initial blank lines to a new segment
curLine = StreamReader.ReadLine();
if( ( segBuilder.Length > 0 ) || ( curLine.Trim().Length > 0 ) )
{
// if curLine is GO we've reached the end of the current segment,
// so add it to the list of segments and reset the segment builder
// note that the GO line is not included in the segment. also,
// we have to guard against back to back gos, which have the
// potential for creating blank segments...which are a no no
// when executing command text against a sql server
if( String.Compare(curLine, "go", true) == 0 )
{
if( segBuilder.Length > 0 )
{
Segments.Add(segBuilder.ToString());
segBuilder.Clear();
}
}
else segBuilder.AppendLine(curLine);
}
builder.AppendLine(curLine);
}
// don't forget the last pending segment if it's not empty
if( segBuilder.Length > 0 ) Segments.Add(segBuilder.ToString());
// store the contents of the entire file in the TextContents property
TextContent = builder.ToString();
loadingFile = false; // reset guard flag
}
/// <summary>
/// Gets an enumerator for walking through the command segments
/// </summary>
/// <returns></returns>
public IEnumerator<string> GetEnumerator()
{
return Segments.GetEnumerator();
}
/// <summary>
/// Gets an enumerator for walking through the command segments
/// </summary>
/// <returns></returns>
IEnumerator IEnumerable.GetEnumerator()
{
return Segments.GetEnumerator();
}
}
}