PageRenderTime 54ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/Languages/IronPython/IronPython.SQLite/Connection.cs

http://github.com/IronLanguages/main
C# | 611 lines | 487 code | 103 blank | 21 comment | 92 complexity | 1833f5a84212407836d3a0473070f07b MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Jeff Hardy 2010-2012.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. *
  14. * ***************************************************************************/
  15. using System;
  16. using System.Collections;
  17. using System.Collections.Generic;
  18. using System.Runtime.InteropServices;
  19. using System.Threading;
  20. using Community.CsharpSqlite;
  21. using IronPython.Runtime;
  22. using IronPython.Runtime.Operations;
  23. using IronPython.Runtime.Types;
  24. using Microsoft.Scripting;
  25. using Microsoft.Scripting.Runtime;
  26. using Microsoft.Scripting.Utils;
  27. using sqlite3_stmt = Community.CsharpSqlite.Sqlite3.Vdbe;
  28. using sqlite3_value = Community.CsharpSqlite.Sqlite3.Mem;
  29. namespace IronPython.SQLite
  30. {
  31. public static partial class PythonSQLite
  32. {
  33. [PythonType]
  34. public class Connection
  35. {
  36. public bool autocommit = false;
  37. public int total_changes { get { return Sqlite3.sqlite3_total_changes(this.db); } }
  38. public int detect_types { get; set; }
  39. public bool check_same_thread { get; set; }
  40. private double _timeout;
  41. public double timeout
  42. {
  43. get { return _timeout; }
  44. set { _timeout = value; Sqlite3.sqlite3_busy_timeout(this.db, (int)(_timeout * 1000)); }
  45. }
  46. private string _isolation_level;
  47. public string isolation_level
  48. {
  49. get { return _isolation_level; }
  50. set { setIsolationLevel(value); }
  51. }
  52. public string begin_statement { get; private set; }
  53. public object row_factory { get; set; }
  54. public object text_factory { get; set; }
  55. public IDictionary collations = new PythonDictionary();
  56. private List<WeakReference> statements = new List<WeakReference>();
  57. private int created_statements = 0;
  58. private Dictionary<object, object> function_pinboard = new Dictionary<object, object>();
  59. internal Sqlite3.sqlite3 db;
  60. internal bool inTransaction = false;
  61. internal int thread_ident = Thread.CurrentThread.ManagedThreadId;
  62. private static readonly Dictionary<object, object> emptyKwargs= new Dictionary<object, object>();
  63. public PythonType Warning = PythonSQLite.Warning;
  64. public PythonType Error = PythonSQLite.Error;
  65. public PythonType InterfaceError = PythonSQLite.InterfaceError;
  66. public PythonType DataError = PythonSQLite.DataError;
  67. public PythonType DatabaseError = PythonSQLite.DatabaseError;
  68. public PythonType OperationalError = PythonSQLite.OperationalError;
  69. public PythonType InternalError = PythonSQLite.InternalError;
  70. public PythonType IntegrityError = PythonSQLite.IntegrityError;
  71. public PythonType ProgrammingError = PythonSQLite.ProgrammingError;
  72. public PythonType NotSupportedError = PythonSQLite.NotSupportedError;
  73. private enum AllStatmentsAction
  74. {
  75. Reset, Finalize
  76. }
  77. public Connection(string database,
  78. [Optional][DefaultParameterValue(0.0)]double timeout,
  79. [Optional][DefaultParameterValue(0)]int detect_types,
  80. [Optional][DefaultParameterValue((string)null)]string isolation_level,
  81. [Optional][DefaultParameterValue(true)]bool check_same_thread,
  82. [Optional][DefaultParameterValue(null)]object factory,
  83. [Optional][DefaultParameterValue(0)]int cached_statements)
  84. {
  85. this.text_factory = typeof(string);
  86. int rc = Sqlite3.sqlite3_open(database, out this.db);
  87. if(rc != Sqlite3.SQLITE_OK)
  88. throw GetSqliteError(this.db, null);
  89. setIsolationLevel(isolation_level ?? "");
  90. this.detect_types = detect_types;
  91. this.timeout = timeout;
  92. this.check_same_thread = check_same_thread;
  93. }
  94. ~Connection()
  95. {
  96. if(this.db != null)
  97. Sqlite3.sqlite3_close(this.db);
  98. }
  99. [Documentation("Closes the connection.")]
  100. public void close()
  101. {
  102. checkThread();
  103. doAllStatements(AllStatmentsAction.Finalize);
  104. if(this.db != null)
  105. {
  106. int rc = Sqlite3.SQLITE_OK, i = 0;
  107. do
  108. {
  109. rc = Sqlite3.sqlite3_close(this.db);
  110. if(rc == Sqlite3.SQLITE_BUSY)
  111. {
  112. GC.Collect();
  113. GC.WaitForPendingFinalizers();
  114. }
  115. } while(rc == Sqlite3.SQLITE_BUSY && i++ < 3);
  116. if(rc != Sqlite3.SQLITE_OK)
  117. throw GetSqliteError(this.db, null);
  118. this.db = null;
  119. }
  120. }
  121. internal void begin()
  122. {
  123. sqlite3_stmt statement = null;
  124. string tail = null;
  125. int rc = Sqlite3.sqlite3_prepare(this.db, this.begin_statement, -1, ref statement, ref tail);
  126. if(rc != Sqlite3.SQLITE_OK)
  127. throw GetSqliteError(this.db, statement);
  128. rc = Util.Step(statement);
  129. if(rc == Sqlite3.SQLITE_DONE)
  130. this.inTransaction = true;
  131. else
  132. throw GetSqliteError(this.db, statement);
  133. rc = Sqlite3.sqlite3_finalize(statement);
  134. if(rc != Sqlite3.SQLITE_OK)
  135. GetSqliteError(this.db, null);
  136. }
  137. [Documentation("Commit the current transaction.")]
  138. public void commit()
  139. {
  140. checkThread(); checkConnection();
  141. if(inTransaction)
  142. {
  143. sqlite3_stmt statement = null;
  144. string tail = null;
  145. int rc = Sqlite3.sqlite3_prepare(this.db, "COMMIT", -1, ref statement, ref tail);
  146. if(rc != Sqlite3.SQLITE_OK)
  147. throw GetSqliteError(this.db, null);
  148. rc = Util.Step(statement);
  149. if(rc == Sqlite3.SQLITE_DONE)
  150. this.inTransaction = false;
  151. else
  152. throw GetSqliteError(this.db, statement);
  153. rc = Sqlite3.sqlite3_finalize(statement);
  154. if(rc != Sqlite3.SQLITE_OK)
  155. GetSqliteError(this.db, null);
  156. }
  157. }
  158. [Documentation("Roll back the current transaction.")]
  159. public void rollback()
  160. {
  161. checkThread(); checkConnection();
  162. if(inTransaction)
  163. {
  164. doAllStatements(AllStatmentsAction.Reset);
  165. sqlite3_stmt statement = null;
  166. string tail = null;
  167. int rc = Sqlite3.sqlite3_prepare(this.db, "ROLLBACK", -1, ref statement, ref tail);
  168. if(rc != Sqlite3.SQLITE_OK)
  169. throw GetSqliteError(this.db, null);
  170. rc = Util.Step(statement);
  171. if(rc == Sqlite3.SQLITE_DONE)
  172. this.inTransaction = false;
  173. else
  174. throw GetSqliteError(this.db, statement);
  175. rc = Sqlite3.sqlite3_finalize(statement);
  176. if(rc != Sqlite3.SQLITE_OK)
  177. GetSqliteError(this.db, null);
  178. }
  179. }
  180. [Documentation("Return a cursor for the connection.")]
  181. public object cursor(CodeContext context, [Optional][DefaultParameterValue(null)]object factory)
  182. {
  183. checkThread(); checkConnection();
  184. object cursor = factory == null ? new Cursor(context, this) : PythonCalls.Call(context, factory, this);
  185. if(this.row_factory != null)
  186. context.LanguageContext.Operations.SetMember(cursor, "row_factory", this.row_factory);
  187. return cursor;
  188. }
  189. [Documentation("Executes a SQL statement. Non-standard.")]
  190. public object execute(CodeContext context, [ParamDictionary]IDictionary<object, object> kwargs, params object[] args)
  191. {
  192. object c = cursor(context, null);
  193. object execute = context.LanguageContext.Operations.GetMember(c, "execute");
  194. return PythonCalls.CallWithKeywordArgs(context, execute, args, kwargs);
  195. }
  196. [Documentation("Repeatedly executes a SQL statement. Non-standard.")]
  197. public object executemany(CodeContext context, [ParamDictionary]IDictionary<object, object> kwargs, params object[] args)
  198. {
  199. object c = cursor(context, null);
  200. object executemany = context.LanguageContext.Operations.GetMember(c, "executemany");
  201. return PythonCalls.CallWithKeywordArgs(context, executemany, args, kwargs);
  202. }
  203. [Documentation("Executes a multiple SQL statements at once. Non-standard.")]
  204. public object executescript(CodeContext context, [ParamDictionary]IDictionary<object, object> kwargs, params object[] args)
  205. {
  206. object c = cursor(context, null);
  207. object executescript = context.LanguageContext.Operations.GetMember(c, "executescript");
  208. return PythonCalls.CallWithKeywordArgs(context, executescript, args, kwargs);
  209. }
  210. public object __call__(string sql)
  211. {
  212. dropUnusedStatementReferences();
  213. Statement statement = new Statement(this, sql);
  214. this.statements.Add(new WeakReference(statement));
  215. return statement;
  216. }
  217. private void dropUnusedStatementReferences()
  218. {
  219. if(this.created_statements++ < 200)
  220. return;
  221. this.created_statements = 0;
  222. List<WeakReference> new_list = new List<WeakReference>();
  223. foreach(WeakReference weakref in this.statements)
  224. if(weakref.IsAlive)
  225. new_list.Add(weakref);
  226. this.statements = new_list;
  227. }
  228. #region User functions
  229. [Documentation("Creates a new function. Non-standard.")]
  230. public void create_function(CodeContext context, string name, int narg, object func)
  231. {
  232. int rc = Sqlite3.sqlite3_create_function(this.db,
  233. name, narg,
  234. Sqlite3.SQLITE_ANY,
  235. new object[] { context, func },
  236. callUserFunction,
  237. null, null);
  238. if(rc != Sqlite3.SQLITE_OK)
  239. throw MakeOperationalError("Error creating function");
  240. else
  241. this.function_pinboard[func] = null;
  242. }
  243. private static void callUserFunction(Sqlite3.sqlite3_context ctx, int argc, sqlite3_value[] argv)
  244. {
  245. object[] data = (object[])Sqlite3.sqlite3_user_data(ctx);
  246. CodeContext context = (CodeContext)data[0];
  247. object func = data[1];
  248. object[] args = buildPyParams(context, ctx, argc, argv);
  249. try
  250. {
  251. object result = PythonCalls.CallWithKeywordArgs(context, func, args, emptyKwargs);
  252. setResult(ctx, result);
  253. }
  254. catch(Exception)
  255. {
  256. Sqlite3.sqlite3_result_error(ctx, "user-defined function raised exception", -1);
  257. }
  258. }
  259. private static object[] buildPyParams(CodeContext context, Sqlite3.sqlite3_context ctx, int argc, sqlite3_value[] argv)
  260. {
  261. object[] args = new object[argc];
  262. for(int i = 0; i < argc; ++i)
  263. {
  264. sqlite3_value cur_value = argv[i];
  265. object cur_py_value = null;
  266. switch(Sqlite3.sqlite3_value_type(cur_value))
  267. {
  268. case Sqlite3.SQLITE_INTEGER:
  269. cur_py_value = (int)Sqlite3.sqlite3_value_int64(cur_value);
  270. break;
  271. case Sqlite3.SQLITE_FLOAT:
  272. cur_py_value = Sqlite3.sqlite3_value_double(cur_value);
  273. break;
  274. case Sqlite3.SQLITE_TEXT:
  275. cur_py_value = Sqlite3.sqlite3_value_text(cur_value);
  276. break;
  277. case Sqlite3.SQLITE_BLOB:
  278. byte[] result = Sqlite3.sqlite3_value_blob(cur_value);
  279. PythonBuffer buffer = new PythonBuffer(context, result);
  280. cur_py_value = buffer;
  281. break;
  282. case Sqlite3.SQLITE_NULL:
  283. default:
  284. cur_py_value = null;
  285. break;
  286. }
  287. args[i] = cur_py_value;
  288. }
  289. return args;
  290. }
  291. private static void setResult(Sqlite3.sqlite3_context ctx, object result)
  292. {
  293. if(result == null)
  294. Sqlite3.sqlite3_result_null(ctx);
  295. else if(result is bool)
  296. Sqlite3.sqlite3_result_int64(ctx, ((bool)result) ? 1 : 0);
  297. else if(result is int)
  298. Sqlite3.sqlite3_result_int64(ctx, (int)result);
  299. else if(result is long)
  300. Sqlite3.sqlite3_result_int64(ctx, (long)result);
  301. else if(result is Microsoft.Scripting.Math.BigInteger)
  302. Sqlite3.sqlite3_result_int64(ctx, ((Microsoft.Scripting.Math.BigInteger)result).ToInt64());
  303. else if(result is float)
  304. Sqlite3.sqlite3_result_double(ctx, (float)result);
  305. else if(result is double)
  306. Sqlite3.sqlite3_result_double(ctx, (double)result);
  307. else if(result is string)
  308. Sqlite3.sqlite3_result_text(ctx, (string)result, -1, Sqlite3.SQLITE_TRANSIENT);
  309. else if(result is byte[])
  310. {
  311. byte[] b = (byte[])result;
  312. string s = Latin1.GetString(b, 0, b.Length);
  313. Sqlite3.sqlite3_result_blob(ctx, s, s.Length, Sqlite3.SQLITE_TRANSIENT);
  314. }
  315. else if(result is PythonBuffer)
  316. {
  317. PythonBuffer buffer = (PythonBuffer)result;
  318. string s = buffer.__getslice__(0, null).ToString();
  319. Sqlite3.sqlite3_result_blob(ctx, s, s.Length, Sqlite3.SQLITE_TRANSIENT);
  320. }
  321. else
  322. {
  323. // TODO raise error
  324. }
  325. }
  326. #endregion
  327. #region User aggregates
  328. private class UserAggregateThunk
  329. {
  330. public UserAggregateThunk(CodeContext context, string name, object aggregate_class)
  331. {
  332. this.context = context;
  333. this.aggregate_class = aggregate_class;
  334. this.name = name;
  335. }
  336. public void stepCallback(Sqlite3.sqlite3_context ctx, int argc, sqlite3_value[] param)
  337. {
  338. if(instance == null)
  339. {
  340. try
  341. {
  342. instance = PythonCalls.Call(context, aggregate_class);
  343. }
  344. catch(Exception)
  345. {
  346. Sqlite3.sqlite3_result_error(ctx, "user-defined aggregate's '__init__' method raised error", -1);
  347. return;
  348. }
  349. }
  350. try
  351. {
  352. object step = context.LanguageContext.Operations.GetMember(instance, "step");
  353. object[] args = buildPyParams(context, ctx, argc, param);
  354. PythonCalls.CallWithKeywordArgs(context, step, args, new Dictionary<object, object>());
  355. }
  356. catch(Exception e)
  357. {
  358. if(e is MissingMemberException)
  359. throw;
  360. Sqlite3.sqlite3_result_error(ctx, "user-defined aggregate's 'step' method raised error", -1);
  361. }
  362. }
  363. public void finalCallback(Sqlite3.sqlite3_context ctx)
  364. {
  365. if(instance == null)
  366. return;
  367. try
  368. {
  369. object function_result = context.LanguageContext.Operations.InvokeMember(instance, "finalize");
  370. setResult(ctx, function_result);
  371. }
  372. catch(Exception)
  373. {
  374. Sqlite3.sqlite3_result_error(ctx, "user-defined aggregate's 'finalize' method raised error", -1);
  375. }
  376. }
  377. CodeContext context;
  378. string name;
  379. object aggregate_class;
  380. object instance;
  381. }
  382. [Documentation("Creates a new aggregate. Non-standard.")]
  383. public void create_aggregate(CodeContext context, string name, int n_arg, object aggregate_class)
  384. {
  385. UserAggregateThunk thunk = new UserAggregateThunk(context, name, aggregate_class);
  386. int rc = Sqlite3.sqlite3_create_function(this.db,
  387. name, n_arg,
  388. Sqlite3.SQLITE_ANY,
  389. thunk,
  390. null,
  391. thunk.stepCallback,
  392. thunk.finalCallback);
  393. if(rc != Sqlite3.SQLITE_OK)
  394. throw MakeOperationalError("Error creating aggregate");
  395. else
  396. this.function_pinboard[aggregate_class] = null;
  397. }
  398. #endregion
  399. [Documentation("Creates a collation function. Non-standard.")]
  400. public void create_collation(params object[] args)
  401. {
  402. throw new NotImplementedException();
  403. }
  404. [Documentation("Sets progress handler callback. Non-standard.")]
  405. public void set_progress_handler(params object[] args)
  406. {
  407. throw new NotImplementedException();
  408. }
  409. [Documentation("Sets authorizer callback. Non-standard.")]
  410. public void set_authorizer(params object[] args)
  411. {
  412. throw new NotImplementedException();
  413. }
  414. [Documentation("For context manager. Non-standard.")]
  415. public object __enter__()
  416. {
  417. return this;
  418. }
  419. [Documentation("For context manager. Non-standard.")]
  420. public object __exit__(CodeContext context, object exc_type, object exc_value, object exc_tb)
  421. {
  422. DynamicOperations ops = context.LanguageContext.Operations;
  423. if(exc_type == null && exc_value == null && exc_tb == null)
  424. {
  425. object commitfn;
  426. if(ops.TryGetMember(this, "commit", out commitfn))
  427. ops.Invoke(commitfn);
  428. else
  429. commit();
  430. }
  431. else
  432. {
  433. object rollbackfn;
  434. if(ops.TryGetMember(this, "rollback", out rollbackfn))
  435. ops.Invoke(rollbackfn);
  436. else
  437. rollback();
  438. }
  439. return false;
  440. }
  441. public object iterdump(CodeContext context)
  442. {
  443. throw new NotImplementedException("Not supported with C#-sqlite for unknown reasons.");
  444. //var ops = context.LanguageContext.Operations;
  445. //PythonModule sqlite3 = Importer.ImportModule(context, context.GlobalScope, "sqlite3", false, -1) as PythonModule;
  446. //PythonModule dump = Importer.ImportFrom(context, sqlite3, "dump") as PythonModule;
  447. //object _iterdump = ops.GetMember(dump, "_iterdump");
  448. //object result = ops.Invoke(_iterdump, this);
  449. //return result;
  450. }
  451. internal void checkConnection()
  452. {
  453. if(this.db == null)
  454. throw MakeProgrammingError("Cannot operate on a closed database.");
  455. }
  456. internal void checkThread()
  457. {
  458. if(this.check_same_thread)
  459. if(this.thread_ident != System.Threading.Thread.CurrentThread.ManagedThreadId)
  460. throw MakeProgrammingError("SQLite objects created in a thread can only be used in that same thread." +
  461. "The object was created in thread id {0} and this is thread id {1}".Format(
  462. this.thread_ident, System.Threading.Thread.CurrentThread.ManagedThreadId));
  463. }
  464. internal static void verify(Connection connection)
  465. {
  466. verify(connection, false);
  467. }
  468. internal static void verify(Connection connection, bool closed)
  469. {
  470. if(!closed && (connection == null || connection.db == null))
  471. throw MakeProgrammingError("Cannot operate on a closed database.");
  472. connection.checkThread();
  473. }
  474. private void setIsolationLevel(string isolation_level)
  475. {
  476. this.begin_statement = null;
  477. if(isolation_level == null)
  478. {
  479. this._isolation_level = null;
  480. this.commit();
  481. this.inTransaction = false;
  482. }
  483. else
  484. {
  485. this._isolation_level = isolation_level;
  486. this.begin_statement = "BEGIN " + isolation_level;
  487. }
  488. }
  489. private void doAllStatements(AllStatmentsAction action)
  490. {
  491. foreach(WeakReference weakref in this.statements)
  492. {
  493. if(weakref.IsAlive)
  494. {
  495. Statement statement = weakref.Target as Statement;
  496. if(statement != null)
  497. {
  498. if(action == AllStatmentsAction.Reset)
  499. statement.Reset();
  500. else
  501. statement.SqliteFinalize();
  502. }
  503. }
  504. }
  505. }
  506. }
  507. }
  508. }