SQLiteCommand.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. using System;
  2. using System.Data;
  3. using System.Collections;
  4. using System.Collections.Specialized;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using System.Globalization;
  8. using System.Data.SQLiteClient.Native;
  9. using System.Windows.Forms;
  10. namespace System.Data.SQLiteClient
  11. {
  12. sealed public class SQLiteCommand : IDbCommand
  13. {
  14. private const char Quote = '\'';
  15. private const char DoubleQuote = '"';
  16. private const char StatementTerminator = ';';
  17. private const char NamedParameterBeginChar = '@';
  18. private const char CreateTriggerBeginChar1 = 'C';
  19. private const char CreateTriggerBeginChar2 = 'c';
  20. //private const char UnnamedParameterBeginChar = '?';
  21. private String _CommandText;
  22. private UpdateRowSource _UpdatedRowSource;
  23. private SQLiteParameterCollection _Parameters;
  24. private SQLiteStatementCollection _Statements;
  25. private int _Timeout;
  26. private SQLiteConnection _Connection;
  27. private bool _ServingDataReader;
  28. private SQLiteTransaction _Transaction;
  29. private static Regex _CreateTriggerRegEx = new Regex(@"(?i)CREATE\s+(?:TEMP\w*\s+)*TRIGGER", RegexOptions.Compiled);
  30. public SQLiteCommand()
  31. {
  32. _CommandText = String.Empty;
  33. _UpdatedRowSource = UpdateRowSource.Both;
  34. _Timeout = 30;
  35. _ServingDataReader = false;
  36. _Transaction = null;
  37. _Parameters = new SQLiteParameterCollection();
  38. _Statements = new SQLiteStatementCollection();
  39. }
  40. public SQLiteCommand(String commandText) : this()
  41. {
  42. CommandText = commandText;
  43. }
  44. public SQLiteCommand(String commandText, SQLiteConnection connection) : this(commandText)
  45. {
  46. if (connection == null) throw new ArgumentNullException("Connection is null.");
  47. Connection = connection;
  48. }
  49. #region IDisposable members
  50. public void Dispose()
  51. {
  52. _Statements.Dispose();
  53. if (_Connection != null) _Connection.DetachCommand(this);
  54. }
  55. #endregion
  56. #region IDbCommand members
  57. public String CommandText
  58. {
  59. get { return _CommandText; }
  60. set
  61. {
  62. if (value == null) throw new ArgumentNullException("CommandText cannot be null.");
  63. if (_Connection != null && _ServingDataReader) throw new InvalidOperationException("Can not set CommandText when the connection is busy serving data readers");
  64. _Statements.Dispose();
  65. _CommandText = value;
  66. ParseCommand(_CommandText);
  67. }
  68. }
  69. public int CommandTimeout
  70. {
  71. get { return _Timeout; }
  72. set
  73. {
  74. if (value < 0) throw new SQLiteException("CommandTimeout must be greater 0.");
  75. _Timeout = value;
  76. }
  77. }
  78. public CommandType CommandType
  79. {
  80. get
  81. {
  82. return CommandType.Text;
  83. }
  84. set
  85. {
  86. if (value != CommandType.Text) throw new SQLiteException("Only CommandType.Text is supported.");
  87. }
  88. }
  89. IDbConnection IDbCommand.Connection
  90. {
  91. get { return Connection; }
  92. set
  93. {
  94. SQLiteConnection connection = value as SQLiteConnection;
  95. if (connection == null) throw new ArgumentNullException("Connection is null.");
  96. Connection = value as SQLiteConnection;
  97. }
  98. }
  99. public SQLiteConnection Connection
  100. {
  101. get { return _Connection; }
  102. set
  103. {
  104. if (_Connection != null)
  105. {
  106. if (_Connection.State == ConnectionState.Executing)
  107. {
  108. throw new InvalidOperationException("Connection is currently executing an operation.");
  109. }
  110. _Statements.Dispose();
  111. _Connection.DetachCommand(this);
  112. }
  113. if (value == null) _Connection = null;
  114. else
  115. {
  116. _Connection = value;
  117. _Connection.AttachCommand(this);
  118. }
  119. }
  120. }
  121. IDataParameterCollection IDbCommand.Parameters
  122. {
  123. get { return _Parameters; }
  124. }
  125. public SQLiteParameterCollection Parameters
  126. {
  127. get { return _Parameters; }
  128. }
  129. IDbTransaction IDbCommand.Transaction
  130. {
  131. get { return Transaction; }
  132. set { Transaction = value as SQLiteTransaction; }
  133. }
  134. public SQLiteTransaction Transaction
  135. {
  136. get { return _Transaction; }
  137. set { _Transaction = value; }
  138. }
  139. public UpdateRowSource UpdatedRowSource
  140. {
  141. get
  142. {
  143. return _UpdatedRowSource;
  144. }
  145. set
  146. {
  147. if (_Connection != null && _Connection.State == ConnectionState.Executing)
  148. {
  149. throw new InvalidOperationException("Connection is currently executing an operation.");
  150. }
  151. _UpdatedRowSource = value;
  152. }
  153. }
  154. public void Cancel()
  155. {
  156. throw new NotSupportedException();
  157. }
  158. IDbDataParameter IDbCommand.CreateParameter()
  159. {
  160. return CreateParameter();
  161. }
  162. public SQLiteParameter CreateParameter()
  163. {
  164. return new SQLiteParameter(null, DbType.AnsiString);
  165. }
  166. public int ExecuteNonQuery ()
  167. {
  168. int result = 0;
  169. for(int i=0 ; i < _Statements.Count; ++i)
  170. {
  171. SQLiteStatement statement = _Statements[i];
  172. statement.Compile();
  173. _Connection.NativeMethods.busy_timeout(_Timeout);
  174. SQLiteCode res = statement.Step();
  175. if (res == SQLiteCode.Done || res == SQLiteCode.RowReady)
  176. {
  177. statement.Reset();
  178. result += _Connection.LastChangesCount;
  179. }
  180. else if (res == SQLiteCode.Error)
  181. {
  182. statement.Dispose(); // UnCompile will throw exception with appropriate msg.
  183. }
  184. else
  185. {
  186. throw new SQLiteException("Unknown SQLite error");
  187. }
  188. }
  189. return result;
  190. }
  191. IDataReader IDbCommand.ExecuteReader()
  192. {
  193. return ExecuteReader();
  194. }
  195. IDataReader IDbCommand.ExecuteReader(CommandBehavior cmdBehavior)
  196. {
  197. return ExecuteReader(cmdBehavior);
  198. }
  199. public SQLiteDataReader ExecuteReader()
  200. {
  201. return ExecuteReader(CommandBehavior.Default);
  202. }
  203. public SQLiteDataReader ExecuteReader(CommandBehavior cmdBehavior)
  204. {
  205. if (_Connection == null || !(_Connection.State == ConnectionState.Open || _Connection.State == ConnectionState.Executing))
  206. throw new InvalidOperationException("The connection must be open to call ExecuteReader");
  207. return new SQLiteDataReader(this, cmdBehavior);
  208. }
  209. public Object ExecuteScalar()
  210. {
  211. // ExecuteReader and get value of first column of first row
  212. IDataReader reader = ExecuteReader();
  213. Object obj = null;
  214. try
  215. {
  216. if (reader.Read() && reader.FieldCount > 0)
  217. {
  218. obj = reader.GetValue(0);
  219. }
  220. }
  221. finally
  222. {
  223. reader.Close();
  224. }
  225. return obj;
  226. }
  227. public void Prepare()
  228. {
  229. }
  230. #endregion
  231. private static void AppendUpToFoundIndex(StringBuilder sb, string cmd, int foundIndex, ref int index)
  232. {
  233. if( foundIndex < 0 ) foundIndex = cmd.Length;
  234. sb.Append( cmd, index, foundIndex-index );
  235. index = foundIndex;
  236. }
  237. private static void AppendUpToSameChar(StringBuilder sb, string cmd, int foundIndex, out int nextIndex)
  238. {
  239. int i = cmd.IndexOf(cmd[foundIndex], foundIndex+1);
  240. if( i < 0 )
  241. {
  242. sb.Append(cmd,foundIndex,cmd.Length-foundIndex);
  243. nextIndex = cmd.Length;
  244. }
  245. else
  246. {
  247. sb.Append(cmd,foundIndex,i-foundIndex+1);
  248. nextIndex = i+1;
  249. }
  250. }
  251. private static void AppendNamedParameter(StringBuilder sb, string cmd, int foundIndex, ArrayList paramNames, out int index)
  252. {
  253. index = foundIndex+1;
  254. do
  255. {
  256. char ch = cmd[index];
  257. if( !(Char.IsLetterOrDigit(ch) || ch == '_') )
  258. break;
  259. index++;
  260. }
  261. while( index < cmd.Length );
  262. sb.Append('?');
  263. paramNames.Add(cmd.Substring(foundIndex,index-foundIndex));
  264. }
  265. private void TerminateStatement(StringBuilder sb, ref ArrayList paramNames)
  266. {
  267. // One SQL statement is terminated.
  268. // Add this statement to the collection and
  269. // reset all string builders
  270. string cmd = sb.ToString().Trim();
  271. if( cmd.Length > 0 ) _Statements.Add(new SQLiteStatement(this, cmd, paramNames));
  272. sb.Length = 0;
  273. paramNames = new ArrayList();
  274. }
  275. private static void AppendTrigger(StringBuilder sb, string cmd, int foundIndex, out int index)
  276. {
  277. if( !_CreateTriggerRegEx.IsMatch(cmd,foundIndex) )
  278. {
  279. sb.Append(cmd[foundIndex]);
  280. index = foundIndex+1;
  281. return;
  282. }
  283. const string END = "END";
  284. // search the keyword END
  285. char[] Terminators =
  286. {
  287. Quote,
  288. DoubleQuote,
  289. 'e',
  290. 'E'
  291. };
  292. index = foundIndex;
  293. while( index < cmd.Length )
  294. {
  295. foundIndex = cmd.IndexOfAny(Terminators,index);
  296. AppendUpToFoundIndex(sb,cmd,foundIndex,ref index);
  297. if( foundIndex < 0 )
  298. break;
  299. switch( cmd[foundIndex] )
  300. {
  301. case Quote:
  302. case DoubleQuote:
  303. AppendUpToSameChar(sb,cmd,foundIndex,out index);
  304. break;
  305. case 'e':
  306. case 'E':
  307. if(string.Compare(cmd,foundIndex,END,0,END.Length,true,CultureInfo.InvariantCulture) == 0)
  308. {
  309. sb.Append(cmd,foundIndex,END.Length);
  310. index = foundIndex+END.Length+1;
  311. return;
  312. }
  313. else
  314. {
  315. sb.Append(cmd[foundIndex]);
  316. index = foundIndex+1;
  317. }
  318. break;
  319. }
  320. }
  321. }
  322. /// <summary>
  323. /// parse the command text and split it into separate SQL commands
  324. /// </summary>
  325. private void ParseCommand(string cmd)
  326. {
  327. _Statements.Clear();
  328. if(cmd.Length == 0) return;
  329. char[] Terminators =
  330. { StatementTerminator,
  331. NamedParameterBeginChar,
  332. Quote,
  333. DoubleQuote,
  334. // UnnamedParameterBeginChar,
  335. CreateTriggerBeginChar1,
  336. CreateTriggerBeginChar2
  337. };
  338. int index = 0;
  339. ArrayList paramNames = new ArrayList();
  340. StringBuilder sb = new StringBuilder(cmd.Length);
  341. while( index < cmd.Length )
  342. {
  343. int foundIndex = cmd.IndexOfAny(Terminators,index);
  344. AppendUpToFoundIndex(sb,cmd,foundIndex,ref index);
  345. if( foundIndex < 0 ) break;
  346. switch( cmd[foundIndex] )
  347. {
  348. case Quote:
  349. case DoubleQuote:
  350. AppendUpToSameChar(sb,cmd,foundIndex,out index);
  351. break;
  352. case NamedParameterBeginChar:
  353. AppendNamedParameter(sb,cmd,foundIndex,paramNames,out index);
  354. break;
  355. //case UnnamedParameterBeginChar:
  356. // paramNames.Add(null);
  357. // sb.Append(UnnamedParameterBeginChar);
  358. // index = foundIndex+1;
  359. // break;
  360. case StatementTerminator:
  361. TerminateStatement(sb,ref paramNames);
  362. index = foundIndex+1;
  363. break;
  364. case CreateTriggerBeginChar1:
  365. case CreateTriggerBeginChar2:
  366. AppendTrigger(sb,cmd,foundIndex,out index);
  367. break;
  368. default:
  369. throw new SQLiteException(string.Format("Found the unexpected terminator '{0}' at the position {1}.", cmd[foundIndex], foundIndex));
  370. }
  371. }
  372. TerminateStatement(sb,ref paramNames);
  373. // now iterate all SQL statements and assign
  374. // the starting index of unnamed parameters inside Parameters collection
  375. int unnamedParameterCount = 0;
  376. for( int i=0 ; i < _Statements.Count ; ++i )
  377. {
  378. SQLiteStatement statement = _Statements[i];
  379. statement.SetUnnamedParametersStartIndex(unnamedParameterCount);
  380. unnamedParameterCount += statement.GetUnnamedParameterCount();
  381. }
  382. }
  383. internal void AttachDataReader(SQLiteDataReader reader)
  384. {
  385. _ServingDataReader = true;
  386. if(_Connection != null) _Connection.AttachDataReader(reader);
  387. }
  388. internal void DetachDataReader(SQLiteDataReader reader)
  389. {
  390. if(_Connection != null) _Connection.DetachDataReader(reader);
  391. _Statements.Dispose();
  392. _ServingDataReader = false;
  393. }
  394. internal SQLiteStatementCollection Statements
  395. {
  396. get { return _Statements; }
  397. }
  398. }
  399. }