Move SQLite backend to new interfaces

This commit is contained in:
UnknownShadow200 2022-08-09 18:43:42 +10:00
parent 81b1617ba1
commit bad7d3443e
2 changed files with 187 additions and 346 deletions

View File

@ -35,18 +35,10 @@ namespace MCGalaxy.SQL
public override bool MultipleSchema { get { return false; } } public override bool MultipleSchema { get { return false; } }
public override string EngineName { get { return "SQLite"; } } public override string EngineName { get { return "SQLite"; } }
internal override IDbConnection CreateConnection() { public override ISqlConnection CreateConnection() {
return new MCGSQLiteConnection(); return new MCGSQLiteConnection();
} }
internal override IDbCommand CreateCommand(string sql, IDbConnection conn) {
return new SQLiteCommand(sql, (SQLiteConnection)conn);
}
internal override IDbDataParameter CreateParameter() {
return new SQLiteParameter();
}
public override void LoadDependencies() { public override void LoadDependencies() {
// on macOS/Linux, use the system provided sqlite3 native library // on macOS/Linux, use the system provided sqlite3 native library
@ -67,7 +59,7 @@ namespace MCGalaxy.SQL
public override void CreateDatabase() { } public override void CreateDatabase() { }
public override string RawGetDateTime(IDataRecord record, int col) { public override string RawGetDateTime(ISqlRecord record, int col) {
return record.GetString(col); // reader.GetDateTime is extremely slow so avoid it return record.GetString(col); // reader.GetDateTime is extremely slow so avoid it
} }
@ -146,7 +138,8 @@ namespace MCGalaxy.SQL
} }
} }
public sealed class MCGSQLiteConnection : SQLiteConnection { sealed class MCGSQLiteConnection : SQLiteConnection
{
protected override bool ConnectionPooling { get { return Server.Config.DatabasePooling; } } protected override bool ConnectionPooling { get { return Server.Config.DatabasePooling; } }
protected override string DBPath { get { return "MCGalaxy.db"; } } protected override string DBPath { get { return "MCGalaxy.db"; } }
} }

View File

@ -5,9 +5,7 @@
* Released to the public domain, use at your own risk! * Released to the public domain, use at your own risk!
********************************************************/ ********************************************************/
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Globalization; using System.Globalization;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security; using System.Security;
@ -15,10 +13,20 @@ using System.Text;
using System.Threading; using System.Threading;
using SQLiteErrorCode = System.Int32; using SQLiteErrorCode = System.Int32;
namespace MCGalaxy.SQL { namespace MCGalaxy.SQL
{
enum SqlType
{
Single, Double, Decimal,
SByte, Int16, Int32, Int64,
Byte, UInt16, UInt32, UInt64,
Boolean, DateTime,
Binary, String, Object,
}
[SuppressUnmanagedCodeSecurity] [SuppressUnmanagedCodeSecurity]
internal static class Interop { static class Interop
{
const string lib = "sqlite3"; const string lib = "sqlite3";
[DllImport(lib, CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, CallingConvention = CallingConvention.Cdecl)]
@ -100,28 +108,21 @@ namespace MCGalaxy.SQL {
internal static extern IntPtr sqlite3_errstr(SQLiteErrorCode rc); /* 3.7.15+ */ internal static extern IntPtr sqlite3_errstr(SQLiteErrorCode rc); /* 3.7.15+ */
} }
public abstract class SQLiteConnection : IDbConnection { public abstract class SQLiteConnection : ISqlConnection
ConnectionState state = ConnectionState.Closed; {
internal int _transactionLevel; internal int _transactionLevel;
IntPtr handle; public IntPtr handle;
protected abstract bool ConnectionPooling { get; } protected abstract bool ConnectionPooling { get; }
protected abstract string DBPath { get; } protected abstract string DBPath { get; }
public IDbTransaction BeginTransaction(IsolationLevel isolationLevel) { public override ISqlTransaction BeginTransaction() {
return new SQLiteTransaction(this); return new SQLiteTransaction(this);
} }
public IDbTransaction BeginTransaction() { public override void ChangeDatabase(string databaseName) { }
return new SQLiteTransaction(this);
}
public void ChangeDatabase(string databaseName) { } public override ISqlCommand CreateCommand(string sql) { return new SQLiteCommand(sql, this); }
public int ConnectionTimeout { get { return SQLiteConvert.Timeout; } }
public string ConnectionString { get { return ""; } set { } }
public IDbCommand CreateCommand() { return new SQLiteCommand(this); }
public string Database { get { return "main"; } }
public long LastInsertRowId { public long LastInsertRowId {
get { get {
@ -144,8 +145,6 @@ namespace MCGalaxy.SQL {
} }
} }
public ConnectionState State { get { return state; } }
public SQLiteErrorCode ResultCode() { public SQLiteErrorCode ResultCode() {
if (handle == IntPtr.Zero) throw new InvalidOperationException("Database connection closed"); if (handle == IntPtr.Zero) throw new InvalidOperationException("Database connection closed");
return Interop.sqlite3_errcode(handle); return Interop.sqlite3_errcode(handle);
@ -182,9 +181,8 @@ namespace MCGalaxy.SQL {
} }
} }
public void Open() { public override void Open() {
if (state != ConnectionState.Closed) throw new InvalidOperationException(); if (handle != IntPtr.Zero) throw new InvalidOperationException();
Close();
try { try {
if (ConnectionPooling) handle = RemoveFromPool(); if (ConnectionPooling) handle = RemoveFromPool();
@ -200,7 +198,6 @@ namespace MCGalaxy.SQL {
} }
SetTimeout(0); SetTimeout(0);
state = ConnectionState.Open;
} catch (SQLiteException) { } catch (SQLiteException) {
Close(); Close();
throw; throw;
@ -216,10 +213,8 @@ namespace MCGalaxy.SQL {
internal static void Check(SQLiteConnection connection) { internal static void Check(SQLiteConnection connection) {
if (connection == null) if (connection == null)
throw new ArgumentNullException("connection"); throw new ArgumentNullException("connection");
if (connection.state != ConnectionState.Open)
throw new InvalidOperationException("The connection is not open.");
if (connection.handle == IntPtr.Zero) if (connection.handle == IntPtr.Zero)
throw new InvalidOperationException("The connection handle is invalid."); throw new InvalidOperationException("The connection is not open.");
} }
internal bool Reset(bool canThrow) { internal bool Reset(bool canThrow) {
@ -242,8 +237,8 @@ namespace MCGalaxy.SQL {
return false; return false;
} }
public void Dispose() { Close(false); } public override void Dispose() { Close(false); }
public void Close() { Close(true); } public override void Close() { Close(true); }
void Close(bool canThrow) { void Close(bool canThrow) {
if (handle == IntPtr.Zero) return; if (handle == IntPtr.Zero) return;
@ -289,16 +284,17 @@ namespace MCGalaxy.SQL {
} }
} }
public sealed class SQLiteCommand : IDbCommand { public sealed class SQLiteCommand : ISqlCommand
string strCmdText, strRemaining; {
SQLiteConnection conn; string sqlCmd;
SQLiteParameterCollection parameters = new SQLiteParameterCollection(); internal SQLiteConnection conn;
SQLiteStatement stmt; SQLiteStatement stmt;
List<string> param_names = new List<string>();
List<object> param_values = new List<object>();
public SQLiteCommand(SQLiteConnection connection) : this(null, connection) { } public SQLiteCommand(string sql, SQLiteConnection connection) {
public SQLiteCommand(string commandText, SQLiteConnection connection) { sqlCmd = sql;
if (commandText != null) CommandText = commandText; conn = connection;
if (connection != null) Connection = connection;
} }
void DisposeStatement() { void DisposeStatement() {
@ -306,78 +302,61 @@ namespace MCGalaxy.SQL {
stmt = null; stmt = null;
} }
public void Dispose() { public override void Dispose() {
conn = null; conn = null;
parameters.Clear(); param_names.Clear();
strCmdText = null; param_values.Clear();
strRemaining = null; sqlCmd = null;
DisposeStatement(); DisposeStatement();
} }
internal SQLiteStatement NextStatement() { internal SQLiteStatement NextStatement() {
if (stmt != null) DisposeStatement(); if (stmt != null) DisposeStatement();
if (String.IsNullOrEmpty(strRemaining)) return null; if (String.IsNullOrEmpty(sqlCmd)) return null;
try { try {
stmt = conn.Prepare(strRemaining, ref strRemaining); stmt = conn.Prepare(sqlCmd, ref sqlCmd);
} catch (Exception) { } catch (Exception) {
DisposeStatement(); DisposeStatement();
// Cannot continue on, so set the remaining text to null. // Cannot continue on, so set the remaining text to null.
strRemaining = null; sqlCmd = null;
throw; throw;
} }
if (stmt != null) stmt.BindAll(parameters); if (stmt != null) stmt.BindAll(param_names, param_values);
return stmt; return stmt;
} }
public void Cancel() { } public override void Prepare() { }
public string CommandText {
get { return strCmdText; } public override void ClearParameters() {
set { strCmdText = value; strRemaining = value; } param_names.Clear();
param_values.Clear();
} }
public int CommandTimeout { public override void AddParameter(string name, object value) {
get { return SQLiteConvert.Timeout; } set { } param_names.Add(name);
param_values.Add(value);
} }
public IDbConnection Connection { public override ISqlReader ExecuteReader() {
get { return conn; } set { conn = (SQLiteConnection)value; }
}
public CommandType CommandType { get { return CommandType.Text; } set { } }
public IDbDataParameter CreateParameter() { return new SQLiteParameter(); }
public IDataParameterCollection Parameters { get { return parameters; } }
public IDbTransaction Transaction { get { return null; } set { } }
public IDataReader ExecuteReader(CommandBehavior behavior) {
SQLiteConnection.Check(conn); SQLiteConnection.Check(conn);
return new SQLiteDataReader(this);
}
public IDataReader ExecuteReader() { return ExecuteReader(0); }
public int ExecuteNonQuery() { SQLiteDataReader reader = new SQLiteDataReader(this);
using (IDataReader reader = ExecuteReader()) { reader.NextResult();
while (reader.NextResult()) { } return reader;
return reader.RecordsAffected; }
public override int ExecuteNonQuery() {
using (ISqlReader reader = ExecuteReader()) {
while (reader.Read()) { }
return reader.RowsAffected;
} }
} }
public object ExecuteScalar() {
using (IDataReader reader = ExecuteReader()) {
if (reader.Read()) return reader[0];
}
return null;
}
public void Prepare() { }
public UpdateRowSource UpdatedRowSource {
get { return UpdateRowSource.None; } set { }
}
} }
static class SQLiteConvert { static class SQLiteConvert
{
static string[] _datetimeFormats = new string[] { static string[] _datetimeFormats = new string[] {
"yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (0). */ "yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (0). */
"yyyy-MM-dd HH:mm:ssK", "yyyy-MM-dd HH:mm:ssK",
@ -450,80 +429,65 @@ namespace MCGalaxy.SQL {
typeof(object), // Null (5) typeof(object), // Null (5)
}; };
internal static DbType TypeToDbType(Type typ) { internal static SqlType TypeToDbType(Type typ) {
TypeCode tc = Type.GetTypeCode(typ); TypeCode tc = Type.GetTypeCode(typ);
if (tc == TypeCode.Object) { if (tc == TypeCode.Object) {
if (typ == typeof(byte[])) return DbType.Binary; if (typ == typeof(byte[])) return SqlType.Binary;
return DbType.String; return SqlType.String;
} }
return type_to_dbtype[(int)tc]; return type_to_dbtype[(int)tc];
} }
static DbType[] type_to_dbtype = { static SqlType[] type_to_dbtype = {
DbType.Object, // Empty (0) SqlType.Object, // Empty (0)
DbType.Binary, // Object (1) SqlType.Binary, // Object (1)
DbType.Object, // DBNull (2) SqlType.Object, // DBNull (2)
DbType.Boolean, // Boolean (3) SqlType.Boolean, // Boolean (3)
DbType.SByte, // Char (4) SqlType.SByte, // Char (4)
DbType.SByte, // SByte (5) SqlType.SByte, // SByte (5)
DbType.Byte, // Byte (6) SqlType.Byte, // Byte (6)
DbType.Int16, // Int16 (7) SqlType.Int16, // Int16 (7)
DbType.UInt16, // UInt16 (8) SqlType.UInt16, // UInt16 (8)
DbType.Int32, // Int32 (9) SqlType.Int32, // Int32 (9)
DbType.UInt32, // UInt32 (10) SqlType.UInt32, // UInt32 (10)
DbType.Int64, // Int64 (11) SqlType.Int64, // Int64 (11)
DbType.UInt64, // UInt64 (12) SqlType.UInt64, // UInt64 (12)
DbType.Single, // Single (13) SqlType.Single, // Single (13)
DbType.Double, // Double (14) SqlType.Double, // Double (14)
DbType.Decimal, // Decimal (15) SqlType.Decimal, // Decimal (15)
DbType.DateTime, // DateTime (16) SqlType.DateTime, // DateTime (16)
DbType.Object, // ?? (17) SqlType.Object, // ?? (17)
DbType.String // String (18) SqlType.String // String (18)
}; };
internal static Type[] dbtype_to_type = { internal static Type[] sqltype_to_type = {
typeof(string), // AnsiString (0) typeof(float), typeof(double), typeof(decimal),
typeof(byte[]), // Binary (1) typeof(sbyte), typeof(Int16), typeof(Int32), typeof(Int64),
typeof(byte), // Byte (2) typeof(byte), typeof(UInt16), typeof(UInt32), typeof(UInt64),
typeof(bool), // Boolean (3) typeof(bool), typeof(DateTime),
typeof(decimal), // Currency (4) typeof(byte[]), typeof(string), typeof(object)
typeof(DateTime), // Date (5)
typeof(DateTime), // DateTime (6)
typeof(decimal), // Decimal (7)
typeof(double), // Double (8)
typeof(Guid), // Guid (9)
typeof(Int16), // Int16 (10)
typeof(Int32), // Int32 (11)
typeof(Int64), // Int64 (12)
typeof(object), // Object (13)
typeof(sbyte), // SByte (14)
typeof(float), // Single (15)
typeof(string), // String (16)
typeof(DateTime), // Time (17)
typeof(UInt16), // UInt16 (18)
typeof(UInt32), // UInt32 (19)
typeof(UInt64), // UInt64 (20)
}; };
static bool TryParseDbType(string typeName, out DbType type) { static bool TryParseDbType(string typeName, out SqlType type) {
string[] names = all_names; string[] names = all_names;
for (int i = 0; i < names.Length; i++) { for (int i = 0; i < names.Length; i++)
{
if (!typeName.Equals(names[i], StringComparison.OrdinalIgnoreCase)) continue; if (!typeName.Equals(names[i], StringComparison.OrdinalIgnoreCase)) continue;
type = all_types[i]; return true; type = all_types[i]; return true;
} }
type = 0; return false; type = 0; return false;
} }
internal static DbType TypeNameToDbType(string typeName) { internal static SqlType TypeNameToDbType(string typeName) {
if (typeName == null) return DbType.Object; if (typeName == null) return SqlType.Object;
DbType value; SqlType value;
if (TryParseDbType(typeName, out value)) return value; if (TryParseDbType(typeName, out value)) return value;
int i = typeName.IndexOf('('); int i = typeName.IndexOf('(');
if (i > 0 && TryParseDbType(typeName.Substring(0, i).TrimEnd(), out value)) return value; if (i > 0 && TryParseDbType(typeName.Substring(0, i).TrimEnd(), out value)) return value;
return DbType.Object; return SqlType.Object;
} }
static string[] all_names = new string[] { static string[] all_names = new string[] {
@ -541,23 +505,24 @@ namespace MCGalaxy.SQL {
"VARCHAR", "VARCHAR",
}; };
static DbType[] all_types = new DbType[] { static SqlType[] all_types = new SqlType[] {
DbType.Int64, DbType.UInt64, DbType.Binary, DbType.Binary, SqlType.Int64, SqlType.UInt64, SqlType.Binary, SqlType.Binary,
DbType.Boolean, DbType.Boolean, DbType.String, DbType.DateTime, SqlType.Boolean, SqlType.Boolean, SqlType.String, SqlType.DateTime,
DbType.DateTime, DbType.Double, DbType.Double, DbType.Int64, SqlType.DateTime, SqlType.Double, SqlType.Double, SqlType.Int64,
DbType.Int32, DbType.SByte, DbType.Int16, DbType.Int32, SqlType.Int32, SqlType.SByte, SqlType.Int16, SqlType.Int32,
DbType.Int64, DbType.Int64, DbType.SByte, DbType.Int16, SqlType.Int64, SqlType.Int64, SqlType.SByte, SqlType.Int16,
DbType.Int32, DbType.Int64, DbType.Int64, DbType.Int32, SqlType.Int32, SqlType.Int64, SqlType.Int64, SqlType.Int32,
DbType.Double, DbType.Single, DbType.Int16, DbType.UInt16, SqlType.Double, SqlType.Single, SqlType.Int16, SqlType.UInt16,
DbType.String, DbType.String, DbType.DateTime, DbType.Byte, SqlType.String, SqlType.String, SqlType.DateTime, SqlType.Byte,
DbType.SByte, DbType.UInt32, DbType.Byte, DbType.UInt16, SqlType.SByte, SqlType.UInt32, SqlType.Byte, SqlType.UInt16,
DbType.UInt32, DbType.UInt64, DbType.UInt64, DbType.UInt64, SqlType.UInt32, SqlType.UInt64, SqlType.UInt64, SqlType.UInt64,
DbType.Byte, DbType.UInt16, DbType.UInt32, DbType.UInt64, SqlType.Byte, SqlType.UInt16, SqlType.UInt32, SqlType.UInt64,
DbType.String, SqlType.String,
}; };
} }
enum TypeAffinity { enum TypeAffinity
{
Uninitialized = 0, Uninitialized = 0,
Int64 = 1, Int64 = 1,
Double = 2, Double = 2,
@ -567,12 +532,14 @@ namespace MCGalaxy.SQL {
DateTime = 10, DateTime = 10,
} }
struct SQLiteType { struct SQLiteType
public DbType Type; {
public SqlType Type;
public TypeAffinity Affinity; public TypeAffinity Affinity;
} }
public sealed class SQLiteDataReader : IDataReader { public sealed class SQLiteDataReader : ISqlReader
{
SQLiteCommand _command; SQLiteCommand _command;
SQLiteStatement stmt; SQLiteStatement stmt;
int readState, rowsAffected, columns; int readState, rowsAffected, columns;
@ -584,8 +551,8 @@ namespace MCGalaxy.SQL {
NextResult(); NextResult();
} }
public void Dispose() { Close(); } public override void Dispose() { Close(); }
public void Close() { public override void Close() {
_command = null; _command = null;
stmt = null; stmt = null;
fieldNames = null; fieldNames = null;
@ -595,81 +562,62 @@ namespace MCGalaxy.SQL {
void CheckClosed() { void CheckClosed() {
if (_command == null) if (_command == null)
throw new InvalidOperationException("DataReader has been closed"); throw new InvalidOperationException("DataReader has been closed");
if (_command.Connection.State != ConnectionState.Open)
throw new InvalidOperationException("Connection was closed, statement was terminated"); SQLiteConnection.Check(_command.conn);
} }
public int Depth { get { return 0; } } public override int FieldCount { get { return columns; } }
public int FieldCount { get { return columns; } }
void VerifyForGet() { void VerifyForGet() {
CheckClosed(); CheckClosed();
if (readState != 0) throw new InvalidOperationException("No current row"); if (readState != 0) throw new InvalidOperationException("No current row");
} }
public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) { public override bool GetBoolean(int i) { return GetInt32(i) != 0; }
throw new NotSupportedException();
}
public IDataReader GetData(int i) { throw new NotSupportedException(); } public override byte[] GetBytes(int i) {
public decimal GetDecimal(int i) { throw new NotSupportedException(); }
public Guid GetGuid(int i) { throw new NotSupportedException(); }
public DataTable GetSchemaTable() { throw new NotSupportedException(); }
public bool GetBoolean(int i) { return GetInt32(i) != 0; }
public byte GetByte(int i) { return (byte)GetInt32(i); }
public char GetChar(int i) { return (char)GetInt32(i); }
public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) {
if (CheckAffinity(i) == TypeAffinity.Blob) if (CheckAffinity(i) == TypeAffinity.Blob)
return stmt.GetBytes(i, (int)fieldOffset, buffer, bufferoffset, length); return stmt.GetBytes(i);
throw new InvalidCastException(); throw new InvalidCastException();
} }
public string GetDataTypeName(int i) { public override DateTime GetDateTime(int i) {
VerifyForGet();
return stmt.ColumnType(i);
}
public DateTime GetDateTime(int i) {
TypeAffinity aff = CheckAffinity(i); TypeAffinity aff = CheckAffinity(i);
if (aff == TypeAffinity.Int64 || aff == TypeAffinity.Double || aff == TypeAffinity.Text) if (aff == TypeAffinity.Int64 || aff == TypeAffinity.Double || aff == TypeAffinity.Text)
return stmt.GetDateTime(i); return stmt.GetDateTime(i);
throw new NotSupportedException(); throw new NotSupportedException();
} }
public double GetDouble(int i) { public override double GetDouble(int i) {
TypeAffinity aff = CheckAffinity(i); TypeAffinity aff = CheckAffinity(i);
if (aff == TypeAffinity.Int64 || aff == TypeAffinity.Double) if (aff == TypeAffinity.Int64 || aff == TypeAffinity.Double)
return stmt.GetDouble(i); return stmt.GetDouble(i);
throw new NotSupportedException(); throw new NotSupportedException();
} }
public Type GetFieldType(int i) { public override Type GetFieldType(int i) {
SQLiteType t = GetSQLiteType(i); SQLiteType t = GetSQLiteType(i);
if (t.Type == DbType.Object) if (t.Type == SqlType.Object)
return SQLiteConvert.affinity_to_type[(int)t.Affinity]; return SQLiteConvert.affinity_to_type[(int)t.Affinity];
else else
return SQLiteConvert.dbtype_to_type[(int)t.Type]; return SQLiteConvert.sqltype_to_type[(int)t.Type];
} }
public float GetFloat(int i) { return (float)GetDouble(i); } public override string GetName(int i) { return stmt.ColumnName(i); }
public short GetInt16(int i) { return (short)GetInt32(i); }
public string GetName(int i) { return stmt.ColumnName(i); }
public int GetInt32(int i) { public override int GetInt32(int i) {
if (CheckAffinity(i) == TypeAffinity.Int64) if (CheckAffinity(i) == TypeAffinity.Int64)
return stmt.GetInt32(i); return stmt.GetInt32(i);
throw new InvalidCastException(); throw new InvalidCastException();
} }
public long GetInt64(int i) { public override long GetInt64(int i) {
if (CheckAffinity(i) == TypeAffinity.Int64) if (CheckAffinity(i) == TypeAffinity.Int64)
return stmt.GetInt64(i); return stmt.GetInt64(i);
throw new InvalidCastException(); throw new InvalidCastException();
} }
public int GetOrdinal(string name) { public override int GetOrdinal(string name) {
VerifyForGet(); VerifyForGet();
if (fieldNames == null) fieldNames = new string[columns]; if (fieldNames == null) fieldNames = new string[columns];
@ -684,23 +632,15 @@ namespace MCGalaxy.SQL {
return -1; return -1;
} }
public string GetString(int i) { return stmt.GetText(i); } public override string GetString(int i) { return stmt.GetText(i); }
public object GetValue(int i) { public override object GetValue(int i) {
VerifyForGet(); VerifyForGet();
SQLiteType t = GetSQLiteType(i); SQLiteType t = GetSQLiteType(i);
return stmt.GetValue(i, t); return stmt.GetValue(i, t);
} }
public int GetValues(object[] values) { public override bool IsDBNull(int i) {
int count = Math.Min(columns, values.Length);
for (int i = 0; i < count; i++) { values[i] = GetValue(i); }
return count;
}
public bool IsClosed { get { return _command == null; } }
public bool IsDBNull(int i) {
VerifyForGet(); VerifyForGet();
return stmt.ColumnAffinity(i) == TypeAffinity.Null; return stmt.ColumnAffinity(i) == TypeAffinity.Null;
} }
@ -753,7 +693,7 @@ namespace MCGalaxy.SQL {
return typ; return typ;
} }
public bool Read() { public override bool Read() {
CheckClosed(); CheckClosed();
// First Row was already read at NextResult() level, so don't step again here // First Row was already read at NextResult() level, so don't step again here
@ -766,12 +706,11 @@ namespace MCGalaxy.SQL {
return false; return false;
} }
public int RecordsAffected { get { return rowsAffected; } } public override int RowsAffected { get { return rowsAffected; } }
public object this[string name] { get { return GetValue(GetOrdinal(name)); } }
public object this[int i] { get { return GetValue(i); } }
} }
sealed class SQLiteException : ExternalException { sealed class SQLiteException : ExternalException
{
SQLiteErrorCode _code; SQLiteErrorCode _code;
public SQLiteException(SQLiteErrorCode code, string message) public SQLiteException(SQLiteErrorCode code, string message)
@ -833,7 +772,8 @@ namespace MCGalaxy.SQL {
} }
} }
static class SQLiteErrorCodes { static class SQLiteErrorCodes
{
public const int Unknown = -1; public const int Unknown = -1;
public const int Ok = 0; public const int Ok = 0;
public const int Error = 1; public const int Error = 1;
@ -843,79 +783,8 @@ namespace MCGalaxy.SQL {
public const int Done = 101; public const int Done = 101;
} }
public sealed class SQLiteParameter : IDbDataParameter { sealed class SQLiteStatement : IDisposable
int type = -1; {
object value;
string name;
public DbType DbType {
get {
if (type == -1) {
if (value != null && value != DBNull.Value) {
return SQLiteConvert.TypeToDbType(value.GetType());
}
return DbType.String; // Unassigned default value is String
}
return (DbType)type;
}
set { type = (int)value; }
}
public string ParameterName { get { return name; } set { name = value; } }
public object Value {
get { return value; }
set {
this.value = value;
// If the DbType has never been assigned, try to glean one from the value's datatype
if (type == -1 && value != null && value != DBNull.Value)
type = (int)SQLiteConvert.TypeToDbType(value.GetType());
}
}
public bool IsNullable { get { return true; } set { } }
public string SourceColumn { get { return ""; } set { } }
public ParameterDirection Direction { get { return 0; } set { } }
public DataRowVersion SourceVersion { get { return 0; } set { } }
public byte Precision { get { return 0; } set { } }
public byte Scale { get { return 0; } set { } }
public int Size { get { return 0; } set { } }
}
public sealed class SQLiteParameterCollection : IDataParameterCollection {
internal List<SQLiteParameter> list = new List<SQLiteParameter>();
public bool IsSynchronized { get { return false; } }
public bool IsFixedSize { get { return false; } }
public bool IsReadOnly { get { return false; } }
public object SyncRoot { get { return null; } }
public IEnumerator GetEnumerator() { return null; }
public int Add(object value) {
list.Add((SQLiteParameter)value);
return list.Count - 1;
}
public void Clear() { list.Clear(); }
public int Count { get { return list.Count; } }
public bool Contains(string name) { return false; }
public bool Contains(object value) { return false; }
public void CopyTo(Array array, int index) { }
public object this[string name] { get {return null; } set { } }
public object this[int index] { get {return null; } set { } }
public int IndexOf(string name) { return -1; }
public int IndexOf(object value) { return -1; }
public void Insert(int index, object value) { }
public void Remove(object value) { }
public void RemoveAt(string name) { }
public void RemoveAt(int index) { }
}
sealed class SQLiteStatement : IDisposable {
IntPtr handle; IntPtr handle;
internal SQLiteConnection conn; internal SQLiteConnection conn;
string[] paramNames; string[] paramNames;
@ -980,14 +849,15 @@ namespace MCGalaxy.SQL {
return SQLiteConvert.FromUTF8(p, -1); return SQLiteConvert.FromUTF8(p, -1);
} }
internal void BindAll(SQLiteParameterCollection args) { internal void BindAll(List<string> names, List<object> values) {
if (paramNames == null || args.list.Count == 0) return; if (paramNames == null || names.Count == 0) return;
foreach (SQLiteParameter arg in args.list) { for (int idx = 0; idx < names.Count; idx++)
int i = FindParameter(arg.ParameterName); {
int i = FindParameter(names[idx]);
if (i == -1) continue; if (i == -1) continue;
SQLiteErrorCode n = BindParameter(i + 1, arg); SQLiteErrorCode n = BindParameter(i + 1, values[idx]);
if (n != SQLiteErrorCodes.Ok) throw new SQLiteException(n, conn.GetLastError()); if (n != SQLiteErrorCodes.Ok) throw new SQLiteException(n, conn.GetLastError());
} }
} }
@ -1000,48 +870,38 @@ namespace MCGalaxy.SQL {
return -1; return -1;
} }
SQLiteErrorCode BindParameter(int i, SQLiteParameter param) { SQLiteErrorCode BindParameter(int i, object obj) {
object obj = param.Value;
DbType type = param.DbType;
if (obj != null && type == DbType.Object)
type = SQLiteConvert.TypeToDbType(obj.GetType());
if (obj == null || obj == DBNull.Value) { if (obj == null || obj == DBNull.Value) {
return Interop.sqlite3_bind_null(handle, i); return Interop.sqlite3_bind_null(handle, i);
} }
SqlType type = SQLiteConvert.TypeToDbType(obj.GetType());
switch (type) { switch (type) {
case DbType.DateTime: case SqlType.DateTime:
// return Bind_DateTime(i, Convert.ToDateTime(obj, CultureInfo.InvariantCulture));
// NOTE: The old method (commented below) does not honor the selected date format case SqlType.Boolean:
// for the connection.
// _sql.Bind_DateTime(this, index, Convert.ToDateTime(obj, cultureInfo));
return Bind_DateTime(i, (obj is string) ?
SQLiteConvert.ToDateTime((string)obj) :
Convert.ToDateTime(obj, CultureInfo.InvariantCulture));
case DbType.Boolean:
return Bind_Int32(i, Convert.ToBoolean(obj) ? 1 : 0); return Bind_Int32(i, Convert.ToBoolean(obj) ? 1 : 0);
case DbType.SByte: case SqlType.SByte:
return Bind_Int32(i, Convert.ToSByte(obj)); return Bind_Int32(i, Convert.ToSByte(obj));
case DbType.Int16: case SqlType.Int16:
return Bind_Int32(i, Convert.ToInt16(obj)); return Bind_Int32(i, Convert.ToInt16(obj));
case DbType.Int32: case SqlType.Int32:
return Bind_Int32(i, Convert.ToInt32(obj)); return Bind_Int32(i, Convert.ToInt32(obj));
case DbType.Int64: case SqlType.Int64:
return Bind_Int64(i, Convert.ToInt64(obj)); return Bind_Int64(i, Convert.ToInt64(obj));
case DbType.Byte: case SqlType.Byte:
return Bind_Int32(i, Convert.ToByte(obj)); return Bind_Int32(i, Convert.ToByte(obj));
case DbType.UInt16: case SqlType.UInt16:
return Bind_Int32(i, Convert.ToUInt16(obj)); return Bind_Int32(i, Convert.ToUInt16(obj));
case DbType.UInt32: case SqlType.UInt32:
return Bind_Int32(i, (int)Convert.ToUInt32(obj)); return Bind_Int32(i, (int)Convert.ToUInt32(obj));
case DbType.UInt64: case SqlType.UInt64:
return Bind_Int64(i, (long)Convert.ToUInt64(obj)); return Bind_Int64(i, (long)Convert.ToUInt64(obj));
case DbType.Single: case SqlType.Single:
case DbType.Double: case SqlType.Double:
case DbType.Decimal: case SqlType.Decimal:
return Interop.sqlite3_bind_double(handle, i, Convert.ToDouble(obj)); return Interop.sqlite3_bind_double(handle, i, Convert.ToDouble(obj));
case DbType.Binary: case SqlType.Binary:
byte[] b = (byte[])obj; byte[] b = (byte[])obj;
return Interop.sqlite3_bind_blob(handle, i, b, b.Length, (IntPtr)(-1)); return Interop.sqlite3_bind_blob(handle, i, b, b.Length, (IntPtr)(-1));
default: default:
@ -1067,14 +927,11 @@ namespace MCGalaxy.SQL {
} }
internal object GetValue(int index, SQLiteType typ) { internal object GetValue(int index, SQLiteType typ) {
if (typ.Type == DbType.DateTime) return GetDateTime(index); if (typ.Type == SqlType.DateTime) return GetDateTime(index);
switch (typ.Affinity) { switch (typ.Affinity) {
case TypeAffinity.Blob: case TypeAffinity.Blob:
int n = Interop.sqlite3_column_bytes(handle, index); return GetBytes(index);
byte[] b = new byte[n];
GetBytes(index, 0, b, 0, n);
return b;
case TypeAffinity.Double: case TypeAffinity.Double:
return GetDouble(index); return GetDouble(index);
case TypeAffinity.Int64: case TypeAffinity.Int64:
@ -1106,30 +963,26 @@ namespace MCGalaxy.SQL {
return SQLiteConvert.ToDateTime(GetText(index)); return SQLiteConvert.ToDateTime(GetText(index));
} }
internal long GetBytes(int index, int srcOffset, byte[] dst, int dstOffset, int dstLen) { internal byte[] GetBytes(int index) {
int srcLen = Interop.sqlite3_column_bytes(handle, index); int srcLen = Interop.sqlite3_column_bytes(handle, index);
if (dst == null) return srcLen; if (srcLen <= 0) return null;
byte[] dst = new byte[srcLen];
int count = dstLen;
if (count + dstOffset > dst.Length) count = dst.Length - dstOffset;
if (count + srcOffset > srcLen) count = srcLen - srcOffset;
if (count <= 0) return 0;
IntPtr src = Interop.sqlite3_column_blob(handle, index); IntPtr src = Interop.sqlite3_column_blob(handle, index);
Marshal.Copy((IntPtr)(src.ToInt64() + srcOffset), dst, dstOffset, count); Marshal.Copy(src, dst, 0, srcLen);
return count; return dst;
} }
} }
public sealed class SQLiteTransaction : IDbTransaction { public sealed class SQLiteTransaction : ISqlTransaction
{
SQLiteConnection conn; SQLiteConnection conn;
internal SQLiteTransaction(SQLiteConnection connection) { internal SQLiteTransaction(SQLiteConnection connection) {
conn = connection; conn = connection;
if (conn._transactionLevel++ == 0) { if (conn._transactionLevel++ == 0) {
try { try {
using (IDbCommand cmd = conn.CreateCommand()) { using (ISqlCommand cmd = conn.CreateCommand("BEGIN IMMEDIATE")) {
cmd.CommandText = "BEGIN IMMEDIATE";
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }
} catch (SQLiteException) { } catch (SQLiteException) {
@ -1141,29 +994,25 @@ namespace MCGalaxy.SQL {
} }
bool disposed; bool disposed;
public void Dispose() { public override void Dispose() {
if (disposed) return; if (disposed) return;
if (IsValid(false)) IssueRollback(false); if (IsValid(false)) IssueRollback(false);
disposed = true; disposed = true;
} }
public void Commit() { public override void Commit() {
SQLiteConnection.Check(conn); SQLiteConnection.Check(conn);
IsValid(true); IsValid(true);
if (--conn._transactionLevel == 0) { if (--conn._transactionLevel == 0) {
using (IDbCommand cmd = conn.CreateCommand()) { using (ISqlCommand cmd = conn.CreateCommand("COMMIT")) {
cmd.CommandText = "COMMIT";
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }
} }
conn = null; conn = null;
} }
public IDbConnection Connection { get { return conn; } } public override void Rollback() {
public IsolationLevel IsolationLevel { get { return IsolationLevel.Serializable; } }
public void Rollback() {
SQLiteConnection.Check(conn); SQLiteConnection.Check(conn);
IsValid(true); IsValid(true);
IssueRollback(true); IssueRollback(true);
@ -1173,8 +1022,7 @@ namespace MCGalaxy.SQL {
if (conn == null) return; if (conn == null) return;
try { try {
using (IDbCommand cmd = conn.CreateCommand()) { using (ISqlCommand cmd = conn.CreateCommand("ROLLBACK")) {
cmd.CommandText = "ROLLBACK";
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }
} catch { } catch {
@ -1189,7 +1037,7 @@ namespace MCGalaxy.SQL {
return false; return false;
} }
if (conn.State != ConnectionState.Open) { if (conn.handle == IntPtr.Zero) {
if (throwError) throw new SQLiteException("Connection was closed"); if (throwError) throw new SQLiteException("Connection was closed");
return false; return false;
} }