SQLiteStatement.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. using System;
  2. using System.Data;
  3. using System.Collections;
  4. using System.Text;
  5. using System.Data.SQLiteClient.Native;
  6. using System.Globalization;
  7. using System.Runtime.InteropServices;
  8. namespace System.Data.SQLiteClient
  9. {
  10. sealed internal class SQLiteStatement : IDisposable
  11. {
  12. /**
  13. * Using Universable Sortable format "s"
  14. * - see Help documentation on DateTimeFormatInfo Class for more info.
  15. * In actual fact we may want to use a more compact pattern like
  16. * yyyyMMdd'T'HHmmss.fffffff
  17. * See also http://www.cl.cam.ac.uk/~mgk25/iso-time.html
  18. */
  19. private const string _ISO8601SQLiteDateTimeFormat = "yyyy-MM-dd HH:mm:ss";
  20. private static string[] _ISO8601DateFormats = new string[]
  21. { _ISO8601SQLiteDateTimeFormat,
  22. "yyyyMMddHHmmss",
  23. "yyyyMMddTHHmmssfffffff",
  24. "yyyy-MM-dd",
  25. "yy-MM-dd",
  26. "yyyyMMdd",
  27. "HH:mm:ss",
  28. "THHmmss"
  29. };
  30. private SQLiteCommand _Command;
  31. private String _CommandText;
  32. private ArrayList _ParameterNames;
  33. private int _UnnamedParametersStartIndex;
  34. private bool _FirstStep;
  35. private int _PreviousTotalChangesCount;
  36. private IntPtr _StatementHandle;
  37. private NativeMethods _NativeMethods;
  38. public SQLiteStatement(SQLiteCommand command, String commandText, ArrayList paramNames)
  39. {
  40. if (command == null) throw new ArgumentNullException("Command is null.");
  41. if (commandText == null) throw new ArgumentNullException("Command text is null.");
  42. if (commandText.Length == 0) throw new ArgumentException("The command text cannot be empty.");
  43. _Command = command;
  44. _UnnamedParametersStartIndex = 0;
  45. _CommandText = commandText;
  46. _ParameterNames = paramNames;
  47. _FirstStep = true;
  48. }
  49. public string CommandText
  50. {
  51. get { return _CommandText; }
  52. }
  53. public void Compile()
  54. {
  55. if (_Command.Connection == null || _Command.Connection.State == ConnectionState.Closed || _Command.Connection.State == ConnectionState.Broken)
  56. {
  57. throw new InvalidOperationException("Connection is currently executing an operation.");
  58. }
  59. if (_NativeMethods == null) _NativeMethods = _Command.Connection.NativeMethods;
  60. if (!_FirstStep)
  61. {
  62. Reset();
  63. }
  64. else
  65. {
  66. if (_CommandText.Length == 0) throw new InvalidOperationException("The command text cannot be empty.");
  67. IntPtr pTail;
  68. IntPtr pVM;
  69. _NativeMethods.prepare(_CommandText, out pVM, out pTail);
  70. if (_NativeMethods.ErrorCode() != SQLiteCode.Ok)
  71. {
  72. throw new SQLiteException(string.Format("Error while prepare statement {0}.\r\n {1}", _CommandText, _NativeMethods.ErrorMessage()));
  73. }
  74. _StatementHandle = pVM;
  75. }
  76. BindParameters();
  77. }
  78. public int GetUnnamedParameterCount()
  79. {
  80. int count = 0;
  81. for(int i=0 ; i < _ParameterNames.Count ; ++i)
  82. {
  83. if(_ParameterNames[i] == null) ++count;
  84. }
  85. return count;
  86. }
  87. private SQLiteParameter FindUnnamedParameter(int index)
  88. {
  89. if (index >= 0)
  90. {
  91. for (int i = 0; i < _Command.Parameters.Count; ++i)
  92. {
  93. SQLiteParameter param = _Command.Parameters[i];
  94. if (param.ParameterName == null)
  95. {
  96. if (index-- == 0) return param;
  97. }
  98. }
  99. }
  100. throw new SQLiteException(String.Format("Can not find unnamed parameter with index = {0}.", index));
  101. }
  102. private SQLiteParameter FindNamedParameter(String parameterName)
  103. {
  104. int index = _Command.Parameters.IndexOf(parameterName);
  105. if (index < 0) throw new SQLiteException(String.Format("Can not find the parameter with name '{0}'.", parameterName));
  106. return _Command.Parameters[index];
  107. }
  108. private void BindParameters()
  109. {
  110. int count = _ParameterNames.Count;
  111. int unnamedParamCount = 0;
  112. for(int i=0 ; i < count ; ++i)
  113. {
  114. SQLiteParameter param = null;
  115. string paramName = (string)_ParameterNames[i];
  116. if (paramName == null)
  117. {
  118. param = FindUnnamedParameter(unnamedParamCount + _UnnamedParametersStartIndex);
  119. ++unnamedParamCount;
  120. }
  121. else
  122. {
  123. param = FindNamedParameter(paramName);
  124. }
  125. bind(i+1, param);
  126. }
  127. }
  128. public void SetUnnamedParametersStartIndex(int index)
  129. {
  130. _UnnamedParametersStartIndex = index;
  131. }
  132. public SQLiteCode Step()
  133. {
  134. if (_FirstStep)
  135. {
  136. _FirstStep = false;
  137. _PreviousTotalChangesCount = _NativeMethods.total_changes();
  138. }
  139. SQLiteCode res = _NativeMethods.step(_StatementHandle);
  140. if (res == SQLiteCode.LibraryUsedIncorrectly || res == SQLiteCode.Busy)
  141. {
  142. throw new SQLiteException(string.Format("Step failed for {0}, ErrorCode {1}. ", _CommandText, _NativeMethods.ErrorCode()));
  143. }
  144. return res;
  145. }
  146. public void Reset()
  147. {
  148. _NativeMethods.reset(_StatementHandle);
  149. if (_NativeMethods.ErrorCode() != SQLiteCode.Ok)
  150. {
  151. throw new SQLiteException(string.Format("Error while reset statement {0}.\r\n {1}", _CommandText, _NativeMethods.ErrorMessage()));
  152. }
  153. _FirstStep = true;
  154. _Command.Connection.LastChangesCount = _NativeMethods.total_changes() - _PreviousTotalChangesCount;
  155. }
  156. private void bind(int index, IDbDataParameter parameter)
  157. {
  158. object value = parameter.Value;
  159. if (value == null || value == DBNull.Value)
  160. {
  161. _NativeMethods.bind_null(_StatementHandle, index);
  162. }
  163. else
  164. {
  165. switch (parameter.DbType)
  166. {
  167. case DbType.Boolean:
  168. _NativeMethods.bind_int(_StatementHandle, index, Convert.ToBoolean(value) ? 1 : 0);
  169. break;
  170. case DbType.Byte:
  171. case DbType.Int16:
  172. case DbType.Int32:
  173. case DbType.SByte:
  174. case DbType.UInt16:
  175. case DbType.UInt32:
  176. _NativeMethods.bind_int(_StatementHandle, index, Convert.ToInt32(value));
  177. break;
  178. case DbType.Double:
  179. case DbType.Single:
  180. _NativeMethods.bind_double(_StatementHandle, index, Convert.ToDouble(value));
  181. break;
  182. case DbType.Date:
  183. case DbType.DateTime:
  184. case DbType.Time:
  185. if (_Command.Connection.DateTimeFormat.Equals(DateTimeFormat.CurrentCulture))
  186. {
  187. value = Convert.ToDateTime(value).ToString(DateTimeFormatInfo.CurrentInfo);
  188. goto default;
  189. }
  190. else if (_Command.Connection.DateTimeFormat.Equals(DateTimeFormat.Ticks))
  191. {
  192. _NativeMethods.bind_int64(_StatementHandle, index, Convert.ToDateTime(value).Ticks);
  193. }
  194. else
  195. {
  196. value = Convert.ToDateTime(value).ToString(_ISO8601SQLiteDateTimeFormat);
  197. goto default;
  198. }
  199. break;
  200. case DbType.Int64:
  201. case DbType.UInt64:
  202. _NativeMethods.bind_int64(_StatementHandle, index, Convert.ToInt64(value));
  203. break;
  204. case DbType.Binary:
  205. byte[] bytes = (byte[])value;
  206. _NativeMethods.bind_blob(_StatementHandle, index, bytes, SQLiteDestructor.Transient);
  207. break;
  208. default:
  209. // cache was purged, fill it again
  210. string str = null;
  211. switch (parameter.DbType)
  212. {
  213. case DbType.Currency:
  214. case DbType.Decimal:
  215. str = Convert.ToDecimal(value).ToString(_Command.Connection.UniversalFormatProvider);
  216. break;
  217. default:
  218. str = Convert.ToString(value);
  219. break;
  220. }
  221. _NativeMethods.bind_text(_StatementHandle, index, str, SQLiteDestructor.Static);
  222. break;
  223. }
  224. }
  225. if (_NativeMethods.ErrorCode() != SQLiteCode.Ok)
  226. {
  227. throw new SQLiteException(string.Format("Bind parameter {0} (data type {1}) failed.\r\nSQLite message: {2}",
  228. parameter.ParameterName, parameter.DbType, _NativeMethods.ErrorMessage()));
  229. }
  230. }
  231. public int GetColumnCount()
  232. {
  233. return _NativeMethods.column_count(_StatementHandle);
  234. }
  235. public string GetColumnName(int iCol)
  236. {
  237. return _NativeMethods.ColumnName(_StatementHandle, iCol);
  238. }
  239. public string GetColumnType(int iCol)
  240. {
  241. string type = _NativeMethods.ColumnDeclarationType(_StatementHandle, iCol);
  242. if (type == null)
  243. {
  244. // Try a different method for determining the column type.
  245. switch (_NativeMethods.column_type(_StatementHandle, iCol))
  246. {
  247. case SQLiteType.Integer:
  248. type = "INT64";
  249. break;
  250. case SQLiteType.Float:
  251. type = "FLOAT";
  252. break;
  253. case SQLiteType.Text:
  254. type = "TEXT";
  255. break;
  256. case SQLiteType.Blob:
  257. type = "BLOB";
  258. break;
  259. }
  260. }
  261. return type;
  262. }
  263. public bool IsColumnNull(int iCol)
  264. {
  265. return _NativeMethods.column_type(_StatementHandle, iCol) == SQLiteType.Null;
  266. }
  267. public bool IsColumnEmptyString(int iCol)
  268. {
  269. if (_NativeMethods.column_type(_StatementHandle, iCol) == SQLiteType.Text)
  270. {
  271. int bytes = _NativeMethods.ColumnBytes(_StatementHandle, iCol);
  272. return bytes <= 1;
  273. }
  274. return false;
  275. }
  276. public string GetColumnValue(int iCol)
  277. {
  278. return _NativeMethods.ColumnText(_StatementHandle, iCol);
  279. }
  280. public int GetColumnInt(int iCol)
  281. {
  282. return _NativeMethods.column_int(_StatementHandle, iCol);
  283. }
  284. public long GetColumnLong(int iCol)
  285. {
  286. return _NativeMethods.column_int64(_StatementHandle, iCol);
  287. }
  288. public double GetColumnDouble(int iCol)
  289. {
  290. return _NativeMethods.column_double(_StatementHandle, iCol);
  291. }
  292. public decimal GetColumnDecimal(int iCol)
  293. {
  294. return decimal.Parse(GetColumnValue(iCol), _Command.Connection.UniversalFormatProvider);
  295. }
  296. public DateTime GetColumnDateTime(int iCol)
  297. {
  298. if (_Command.Connection.DateTimeFormat.Equals(DateTimeFormat.CurrentCulture))
  299. {
  300. return DateTime.Parse(GetColumnValue(iCol));
  301. }
  302. else if (_Command.Connection.DateTimeFormat.Equals(DateTimeFormat.Ticks))
  303. {
  304. return new DateTime(GetColumnLong(iCol));
  305. }
  306. else
  307. {
  308. string val = GetColumnValue(iCol);
  309. object retVal = null;
  310. try
  311. {
  312. retVal = DateTime.ParseExact(val, _ISO8601DateFormats,
  313. DateTimeFormatInfo.InvariantInfo,
  314. DateTimeStyles.None);
  315. }
  316. catch { /* nothing to do */ }
  317. if (retVal == null)
  318. {
  319. try
  320. {
  321. long iVal = Int64.Parse(val);
  322. // Try the old Ticks format.
  323. retVal = new DateTime(iVal);
  324. }
  325. catch
  326. {
  327. throw new SQLiteException(string.Format("Invalid DateTime Field Format: {0}", val));
  328. }
  329. }
  330. return (DateTime)retVal;
  331. }
  332. }
  333. public long GetColumnBytes(int iCol, long fieldOffset, Byte[] buffer, int bufferoffset, int length)
  334. {
  335. int fieldLen = _NativeMethods.ColumnBytes(_StatementHandle, iCol);
  336. if (buffer == null) return fieldLen;
  337. IntPtr blob = _NativeMethods.column_blob(_StatementHandle, iCol);
  338. if (blob == IntPtr.Zero) return 0;
  339. IntPtr src = (IntPtr)(blob.ToInt32() + fieldOffset);
  340. if ((fieldOffset + length) > fieldLen) length = fieldLen - (int)fieldOffset;
  341. if ((bufferoffset + length) > buffer.Length) length = buffer.Length - bufferoffset;
  342. Marshal.Copy(src, buffer, bufferoffset, length);
  343. return length;
  344. }
  345. #region IDisposable Members
  346. public void Dispose()
  347. {
  348. if (_StatementHandle != IntPtr.Zero)
  349. {
  350. SQLiteCode res = _NativeMethods.finalize(_StatementHandle);
  351. _StatementHandle = IntPtr.Zero;
  352. if (res != SQLiteCode.Ok && res != SQLiteCode.CallbackAbort)
  353. {
  354. throw new SQLiteException(string.Format("Error while finalize statement {0}.\r\n {1}", _CommandText, _NativeMethods.ErrorMessage()));
  355. }
  356. _Command.Connection.LastChangesCount = _NativeMethods.total_changes() - _PreviousTotalChangesCount;
  357. }
  358. }
  359. #endregion
  360. }
  361. }