/********************************************************
* ADO.NET 2.0 Data Provider for SQLite Version 3.X
* Written by Robert Simpson (robert@blackcastlesoft.com)
*
* Released to the public domain, use at your own risk!
********************************************************/
namespace System.Data.SQLite
{
using System;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
///
/// Event data for logging event handlers.
///
public class LogEventArgs : EventArgs
{
///
/// The error code. The type of this object value should be
/// or .
///
public readonly object ErrorCode;
///
/// SQL statement text as the statement first begins executing
///
public readonly string Message;
///
/// Extra data associated with this event, if any.
///
public readonly object Data;
///
/// Constructs the object.
///
/// Should be null.
///
/// The error code. The type of this object value should be
/// or .
///
/// The error message, if any.
/// The extra data, if any.
internal LogEventArgs(
IntPtr pUserData,
object errorCode,
string message,
object data
)
{
ErrorCode = errorCode;
Message = message;
Data = data;
}
}
///////////////////////////////////////////////////////////////////////////
///
/// Raised when a log event occurs.
///
/// The current connection
/// Event arguments of the trace
public delegate void SQLiteLogEventHandler(object sender, LogEventArgs e);
///////////////////////////////////////////////////////////////////////////
///
/// Manages the SQLite custom logging functionality and the associated
/// callback for the whole process.
///
public static class SQLiteLog
{
///
/// Object used to synchronize access to the static instance data
/// for this class.
///
private static object syncRoot = new object();
///////////////////////////////////////////////////////////////////////
#if !PLATFORM_COMPACTFRAMEWORK
///
/// Member variable to store the AppDomain.DomainUnload event handler.
///
private static EventHandler _domainUnload;
#endif
///////////////////////////////////////////////////////////////////////
///
/// Member variable to store the application log handler to call.
///
private static event SQLiteLogEventHandler _handlers;
///////////////////////////////////////////////////////////////////////
///
/// The default log event handler.
///
private static SQLiteLogEventHandler _defaultHandler;
///////////////////////////////////////////////////////////////////////
#if !USE_INTEROP_DLL || !INTEROP_LOG
///
/// The log callback passed to native SQLite engine. This must live
/// as long as the SQLite library has a pointer to it.
///
private static SQLiteLogCallback _callback;
///////////////////////////////////////////////////////////////////////
///
/// The base SQLite object to interop with.
///
private static SQLiteBase _sql;
#endif
///////////////////////////////////////////////////////////////////////
///
/// The number of times that the
/// has been called when the logging subystem was actually eligible
/// to be initialized (i.e. without the "No_SQLiteLog" environment
/// variable being set).
///
private static int _initializeCallCount;
///////////////////////////////////////////////////////////////////////
///
/// This will be non-zero if an attempt was already made to initialize
/// the (managed) logging subsystem.
///
private static int _attemptedInitialize;
///////////////////////////////////////////////////////////////////////
///
/// This will be non-zero if logging is currently enabled.
///
private static bool _enabled;
///////////////////////////////////////////////////////////////////////
///
/// Initializes the SQLite logging facilities.
///
public static void Initialize()
{
Initialize(null);
}
///////////////////////////////////////////////////////////////////////
///
/// Initializes the SQLite logging facilities.
///
///
/// The name of the managed class that called this method. This
/// parameter may be null.
///
internal static void Initialize(
string className
)
{
//
// NOTE: See if the logging subsystem has been totally disabled.
// If so, do nothing.
//
if (UnsafeNativeMethods.GetSettingValue(
"No_SQLiteLog", null) != null)
{
return;
}
///////////////////////////////////////////////////////////////
//
// NOTE: Keep track of exactly how many times this method is
// called (i.e. per-AppDomain, of course).
//
Interlocked.Increment(ref _initializeCallCount);
///////////////////////////////////////////////////////////////
//
// NOTE: First, check if the managed logging subsystem is always
// supposed to at least attempt to initialize itself. In
// order to do this, several fairly complex steps must be
// taken, including calling a P/Invoke (interop) method;
// therefore, by default, attempt to perform these steps
// once.
//
if (UnsafeNativeMethods.GetSettingValue(
"Initialize_SQLiteLog", null) == null)
{
if (Interlocked.Increment(ref _attemptedInitialize) > 1)
{
Interlocked.Decrement(ref _attemptedInitialize);
return;
}
}
///////////////////////////////////////////////////////////////////
//
// BUFXIX: Cannot initialize the logging interface if the SQLite
// core library has already been initialized anywhere in
// the process (see ticket [2ce0870fad]).
//
if (SQLite3.StaticIsInitialized())
return;
///////////////////////////////////////////////////////////////////
#if !PLATFORM_COMPACTFRAMEWORK
//
// BUGFIX: To avoid nasty situations where multiple AppDomains are
// attempting to initialize and/or shutdown what is really
// a shared native resource (i.e. the SQLite core library
// is loaded per-process and has only one logging callback,
// not one per-AppDomain, which it knows nothing about),
// prevent all non-default AppDomains from registering a
// log handler unless the "Force_SQLiteLog" environment
// variable is used to manually override this safety check.
//
if (!AppDomain.CurrentDomain.IsDefaultAppDomain() &&
UnsafeNativeMethods.GetSettingValue("Force_SQLiteLog", null) == null)
{
return;
}
#endif
///////////////////////////////////////////////////////////////////
lock (syncRoot)
{
#if !PLATFORM_COMPACTFRAMEWORK
//
// NOTE: Add an event handler for the DomainUnload event so
// that we can unhook our logging managed function
// pointer from the native SQLite code prior to it
// being invalidated.
//
// BUGFIX: Make sure this event handler is only added one
// time (per-AppDomain).
//
if (_domainUnload == null)
{
_domainUnload = new EventHandler(DomainUnload);
AppDomain.CurrentDomain.DomainUnload += _domainUnload;
}
#endif
///////////////////////////////////////////////////////////////
#if USE_INTEROP_DLL && INTEROP_LOG
//
// NOTE: Attempt to setup interop assembly log callback.
// This may fail, e.g. if the SQLite core library
// has somehow been initialized. An exception will
// be raised in that case.
//
SQLiteErrorCode rc = SQLite3.ConfigureLogForInterop(
className);
if (rc != SQLiteErrorCode.Ok)
{
throw new SQLiteException(rc,
"Failed to configure interop assembly logging.");
}
#else
//
// NOTE: Create an instance of the SQLite wrapper class.
//
if (_sql == null)
{
_sql = new SQLite3(
SQLiteDateFormats.Default, DateTimeKind.Unspecified,
null, IntPtr.Zero, null, false);
}
//
// NOTE: Create a single "global" (i.e. per-process) callback
// to register with SQLite. This callback will pass the
// event on to any registered handler. We only want to
// do this once.
//
if (_callback == null)
{
_callback = new SQLiteLogCallback(LogCallback);
SQLiteErrorCode rc = _sql.SetLogCallback(_callback);
if (rc != SQLiteErrorCode.Ok)
{
_callback = null; /* UNDO */
throw new SQLiteException(rc,
"Failed to configure managed assembly logging.");
}
}
#endif
///////////////////////////////////////////////////////////////
//
// NOTE: Logging is enabled by default unless the configuration
// setting "Disable_SQLiteLog" is present.
//
if (UnsafeNativeMethods.GetSettingValue(
"Disable_SQLiteLog", null) == null)
{
_enabled = true;
}
///////////////////////////////////////////////////////////////
//
// NOTE: For now, always setup the default log event handler.
//
AddDefaultHandler();
}
}
///////////////////////////////////////////////////////////////////////
#if !PLATFORM_COMPACTFRAMEWORK
///
/// Handles the AppDomain being unloaded.
///
/// Should be null.
/// The data associated with this event.
private static void DomainUnload(
object sender,
EventArgs e
)
{
lock (syncRoot)
{
//
// NOTE: Remove the default log event handler.
//
RemoveDefaultHandler();
//
// NOTE: Disable logging. If necessary, it can be re-enabled
// later by the Initialize method.
//
_enabled = false;
#if !USE_INTEROP_DLL || !INTEROP_LOG
//
// BUGBUG: This will cause serious problems if other AppDomains
// have any open SQLite connections; however, there is
// currently no way around this limitation.
//
if (_sql != null)
{
SQLiteErrorCode rc = _sql.Shutdown();
if (rc != SQLiteErrorCode.Ok)
throw new SQLiteException(rc,
"Failed to shutdown interface.");
rc = _sql.SetLogCallback(null);
if (rc != SQLiteErrorCode.Ok)
throw new SQLiteException(rc,
"Failed to shutdown logging.");
}
//
// BUGFIX: Make sure to reset the callback for next time. This
// must be done after it has been succesfully removed
// as logging callback by the SQLite core library as we
// cannot allow native code to refer to a delegate that
// has been garbage collected.
//
if (_callback != null)
{
_callback = null;
}
#endif
//
// NOTE: Remove the event handler for the DomainUnload event
// that we added earlier.
//
if (_domainUnload != null)
{
AppDomain.CurrentDomain.DomainUnload -= _domainUnload;
_domainUnload = null;
}
}
}
#endif
///////////////////////////////////////////////////////////////////////
///
/// This event is raised whenever SQLite raises a logging event.
/// Note that this should be set as one of the first things in the
/// application.
///
public static event SQLiteLogEventHandler Log
{
add
{
lock (syncRoot)
{
// Remove any copies of this event handler from registered
// list. This essentially means that a handler will be
// called only once no matter how many times it is added.
_handlers -= value;
// Add this to the list of event handlers.
_handlers += value;
}
}
remove
{
lock (syncRoot)
{
_handlers -= value;
}
}
}
///////////////////////////////////////////////////////////////////////
///
/// If this property is true, logging is enabled; otherwise, logging is
/// disabled. When logging is disabled, no logging events will fire.
///
public static bool Enabled
{
get { lock (syncRoot) { return _enabled; } }
set { lock (syncRoot) { _enabled = value; } }
}
///////////////////////////////////////////////////////////////////////
///
/// Log a message to all the registered log event handlers without going
/// through the SQLite library.
///
/// The message to be logged.
public static void LogMessage(
string message
)
{
LogMessage(null, message);
}
///////////////////////////////////////////////////////////////////////
///
/// Log a message to all the registered log event handlers without going
/// through the SQLite library.
///
/// The SQLite error code.
/// The message to be logged.
public static void LogMessage(
SQLiteErrorCode errorCode,
string message
)
{
LogMessage((object)errorCode, message);
}
///////////////////////////////////////////////////////////////////////
///
/// Log a message to all the registered log event handlers without going
/// through the SQLite library.
///
/// The integer error code.
/// The message to be logged.
public static void LogMessage(
int errorCode,
string message
)
{
LogMessage((object)errorCode, message);
}
///////////////////////////////////////////////////////////////////////
///
/// Log a message to all the registered log event handlers without going
/// through the SQLite library.
///
///
/// The error code. The type of this object value should be
/// System.Int32 or SQLiteErrorCode.
///
/// The message to be logged.
private static void LogMessage(
object errorCode,
string message
)
{
bool enabled;
SQLiteLogEventHandler handlers;
lock (syncRoot)
{
enabled = _enabled;
if (_handlers != null)
handlers = _handlers.Clone() as SQLiteLogEventHandler;
else
handlers = null;
}
if (enabled && (handlers != null))
handlers(null, new LogEventArgs(
IntPtr.Zero, errorCode, message, null));
}
///////////////////////////////////////////////////////////////////////
///
/// Creates and initializes the default log event handler.
///
private static void InitializeDefaultHandler()
{
lock (syncRoot)
{
if (_defaultHandler == null)
_defaultHandler = new SQLiteLogEventHandler(LogEventHandler);
}
}
///////////////////////////////////////////////////////////////////////
///
/// Adds the default log event handler to the list of handlers.
///
public static void AddDefaultHandler()
{
InitializeDefaultHandler();
Log += _defaultHandler;
}
///////////////////////////////////////////////////////////////////////
///
/// Removes the default log event handler from the list of handlers.
///
public static void RemoveDefaultHandler()
{
InitializeDefaultHandler();
Log -= _defaultHandler;
}
///////////////////////////////////////////////////////////////////////
///
/// Internal proxy function that calls any registered application log
/// event handlers.
///
/// WARNING: This method is used more-or-less directly by native code,
/// do not modify its type signature.
///
///
/// The extra data associated with this message, if any.
///
///
/// The error code associated with this message.
///
///
/// The message string to be logged.
///
private static void LogCallback(
IntPtr pUserData,
int errorCode,
IntPtr pMessage
)
{
bool enabled;
SQLiteLogEventHandler handlers;
lock (syncRoot)
{
enabled = _enabled;
if (_handlers != null)
handlers = _handlers.Clone() as SQLiteLogEventHandler;
else
handlers = null;
}
if (enabled && (handlers != null))
handlers(null, new LogEventArgs(pUserData, errorCode,
SQLiteBase.UTF8ToString(pMessage, -1), null));
}
///////////////////////////////////////////////////////////////////////
///
/// Default logger. Currently, uses the Trace class (i.e. sends events
/// to the current trace listeners, if any).
///
/// Should be null.
/// The data associated with this event.
private static void LogEventHandler(
object sender,
LogEventArgs e
)
{
#if !NET_COMPACT_20
if (e == null)
return;
string message = e.Message;
if (message == null)
{
message = "";
}
else
{
message = message.Trim();
if (message.Length == 0)
message = "";
}
object errorCode = e.ErrorCode;
string type = "error";
if ((errorCode is SQLiteErrorCode) || (errorCode is int))
{
SQLiteErrorCode rc = (SQLiteErrorCode)(int)errorCode;
rc &= SQLiteErrorCode.NonExtendedMask;
if (rc == SQLiteErrorCode.Ok)
{
type = "message";
}
else if (rc == SQLiteErrorCode.Notice)
{
type = "notice";
}
else if (rc == SQLiteErrorCode.Warning)
{
type = "warning";
}
else if ((rc == SQLiteErrorCode.Row) ||
(rc == SQLiteErrorCode.Done))
{
type = "data";
}
}
else if (errorCode == null)
{
type = "trace";
}
if ((errorCode != null) &&
!Object.ReferenceEquals(errorCode, String.Empty))
{
Trace.WriteLine(HelperMethods.StringFormat(
CultureInfo.CurrentCulture, "SQLite {0} ({1}): {2}",
type, errorCode, message));
}
else
{
Trace.WriteLine(HelperMethods.StringFormat(
CultureInfo.CurrentCulture, "SQLite {0}: {1}",
type, message));
}
#endif
}
}
}