Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/SQLAlchemy-1.3.25.dev0-py3.11-linux-x86_64.egg/sqlalchemy/dialects/sqlite/pysqlite.py: 59%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

88 statements  

1# sqlite/pysqlite.py 

2# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: http://www.opensource.org/licenses/mit-license.php 

7 

8r""" 

9.. dialect:: sqlite+pysqlite 

10 :name: pysqlite 

11 :dbapi: sqlite3 

12 :connectstring: sqlite+pysqlite:///file_path 

13 :url: http://docs.python.org/library/sqlite3.html 

14 

15 Note that ``pysqlite`` is the same driver as the ``sqlite3`` 

16 module included with the Python distribution. 

17 

18Driver 

19------ 

20 

21The ``sqlite3`` Python DBAPI is standard on all modern Python versions; 

22for cPython and Pypy, no additional installation is necessary. 

23 

24 

25Connect Strings 

26--------------- 

27 

28The file specification for the SQLite database is taken as the "database" 

29portion of the URL. Note that the format of a SQLAlchemy url is:: 

30 

31 driver://user:pass@host/database 

32 

33This means that the actual filename to be used starts with the characters to 

34the **right** of the third slash. So connecting to a relative filepath 

35looks like:: 

36 

37 # relative path 

38 e = create_engine('sqlite:///path/to/database.db') 

39 

40An absolute path, which is denoted by starting with a slash, means you 

41need **four** slashes:: 

42 

43 # absolute path 

44 e = create_engine('sqlite:////path/to/database.db') 

45 

46To use a Windows path, regular drive specifications and backslashes can be 

47used. Double backslashes are probably needed:: 

48 

49 # absolute path on Windows 

50 e = create_engine('sqlite:///C:\\path\\to\\database.db') 

51 

52The sqlite ``:memory:`` identifier is the default if no filepath is 

53present. Specify ``sqlite://`` and nothing else:: 

54 

55 # in-memory database 

56 e = create_engine('sqlite://') 

57 

58.. _pysqlite_uri_connections: 

59 

60URI Connections 

61^^^^^^^^^^^^^^^ 

62 

63Modern versions of SQLite support an alternative system of connecting using a 

64`driver level URI <https://www.sqlite.org/uri.html>`_, which has the advantage 

65that additional driver-level arguments can be passed including options such as 

66"read only". The Python sqlite3 driver supports this mode under modern Python 

673 versions. The SQLAlchemy pysqlite driver supports this mode of use by 

68specifing "uri=true" in the URL query string. The SQLite-level "URI" is kept 

69as the "database" portion of the SQLAlchemy url (that is, following a slash):: 

70 

71 e = create_engine("sqlite:///file:path/to/database?mode=ro&uri=true") 

72 

73.. note:: The "uri=true" parameter must appear in the **query string** 

74 of the URL. It will not currently work as expected if it is only 

75 present in the :paramref:`_sa.create_engine.connect_args` 

76 parameter dictionary. 

77 

78The logic reconciles the simultaneous presence of SQLAlchemy's query string and 

79SQLite's query string by separating out the parameters that belong to the 

80Python sqlite3 driver vs. those that belong to the SQLite URI. This is 

81achieved through the use of a fixed list of parameters known to be accepted by 

82the Python side of the driver. For example, to include a URL that indicates 

83the Python sqlite3 "timeout" and "check_same_thread" parameters, along with the 

84SQLite "mode" and "nolock" parameters, they can all be passed together on the 

85query string:: 

86 

87 e = create_engine( 

88 "sqlite:///file:path/to/database?" 

89 "check_same_thread=true&timeout=10&mode=ro&nolock=1&uri=true" 

90 ) 

91 

92Above, the pysqlite / sqlite3 DBAPI would be passed arguments as:: 

93 

94 sqlite3.connect( 

95 "file:path/to/database?mode=ro&nolock=1", 

96 check_same_thread=True, timeout=10, uri=True 

97 ) 

98 

99Regarding future parameters added to either the Python or native drivers. new 

100parameter names added to the SQLite URI scheme should be automatically 

101accommodated by this scheme. New parameter names added to the Python driver 

102side can be accommodated by specifying them in the 

103:paramref:`_sa.create_engine.connect_args` dictionary, 

104until dialect support is 

105added by SQLAlchemy. For the less likely case that the native SQLite driver 

106adds a new parameter name that overlaps with one of the existing, known Python 

107driver parameters (such as "timeout" perhaps), SQLAlchemy's dialect would 

108require adjustment for the URL scheme to continue to support this. 

109 

110As is always the case for all SQLAlchemy dialects, the entire "URL" process 

111can be bypassed in :func:`_sa.create_engine` through the use of the 

112:paramref:`_sa.create_engine.creator` 

113parameter which allows for a custom callable 

114that creates a Python sqlite3 driver level connection directly. 

115 

116.. versionadded:: 1.3.9 

117 

118.. seealso:: 

119 

120 `Uniform Resource Identifiers <https://www.sqlite.org/uri.html>`_ - in 

121 the SQLite documentation 

122 

123Compatibility with sqlite3 "native" date and datetime types 

124----------------------------------------------------------- 

125 

126The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and 

127sqlite3.PARSE_COLNAMES options, which have the effect of any column 

128or expression explicitly cast as "date" or "timestamp" will be converted 

129to a Python date or datetime object. The date and datetime types provided 

130with the pysqlite dialect are not currently compatible with these options, 

131since they render the ISO date/datetime including microseconds, which 

132pysqlite's driver does not. Additionally, SQLAlchemy does not at 

133this time automatically render the "cast" syntax required for the 

134freestanding functions "current_timestamp" and "current_date" to return 

135datetime/date types natively. Unfortunately, pysqlite 

136does not provide the standard DBAPI types in ``cursor.description``, 

137leaving SQLAlchemy with no way to detect these types on the fly 

138without expensive per-row type checks. 

139 

140Keeping in mind that pysqlite's parsing option is not recommended, 

141nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES 

142can be forced if one configures "native_datetime=True" on create_engine():: 

143 

144 engine = create_engine('sqlite://', 

145 connect_args={'detect_types': 

146 sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES}, 

147 native_datetime=True 

148 ) 

149 

150With this flag enabled, the DATE and TIMESTAMP types (but note - not the 

151DATETIME or TIME types...confused yet ?) will not perform any bind parameter 

152or result processing. Execution of "func.current_date()" will return a string. 

153"func.current_timestamp()" is registered as returning a DATETIME type in 

154SQLAlchemy, so this function still receives SQLAlchemy-level result 

155processing. 

156 

157.. _pysqlite_threading_pooling: 

158 

159Threading/Pooling Behavior 

160--------------------------- 

161 

162Pysqlite's default behavior is to prohibit the usage of a single connection 

163in more than one thread. This is originally intended to work with older 

164versions of SQLite that did not support multithreaded operation under 

165various circumstances. In particular, older SQLite versions 

166did not allow a ``:memory:`` database to be used in multiple threads 

167under any circumstances. 

168 

169Pysqlite does include a now-undocumented flag known as 

170``check_same_thread`` which will disable this check, however note that 

171pysqlite connections are still not safe to use in concurrently in multiple 

172threads. In particular, any statement execution calls would need to be 

173externally mutexed, as Pysqlite does not provide for thread-safe propagation 

174of error messages among other things. So while even ``:memory:`` databases 

175can be shared among threads in modern SQLite, Pysqlite doesn't provide enough 

176thread-safety to make this usage worth it. 

177 

178SQLAlchemy sets up pooling to work with Pysqlite's default behavior: 

179 

180* When a ``:memory:`` SQLite database is specified, the dialect by default 

181 will use :class:`.SingletonThreadPool`. This pool maintains a single 

182 connection per thread, so that all access to the engine within the current 

183 thread use the same ``:memory:`` database - other threads would access a 

184 different ``:memory:`` database. 

185* When a file-based database is specified, the dialect will use 

186 :class:`.NullPool` as the source of connections. This pool closes and 

187 discards connections which are returned to the pool immediately. SQLite 

188 file-based connections have extremely low overhead, so pooling is not 

189 necessary. The scheme also prevents a connection from being used again in 

190 a different thread and works best with SQLite's coarse-grained file locking. 

191 

192Using a Memory Database in Multiple Threads 

193^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

194 

195To use a ``:memory:`` database in a multithreaded scenario, the same 

196connection object must be shared among threads, since the database exists 

197only within the scope of that connection. The 

198:class:`.StaticPool` implementation will maintain a single connection 

199globally, and the ``check_same_thread`` flag can be passed to Pysqlite 

200as ``False``:: 

201 

202 from sqlalchemy.pool import StaticPool 

203 engine = create_engine('sqlite://', 

204 connect_args={'check_same_thread':False}, 

205 poolclass=StaticPool) 

206 

207Note that using a ``:memory:`` database in multiple threads requires a recent 

208version of SQLite. 

209 

210Using Temporary Tables with SQLite 

211^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

212 

213Due to the way SQLite deals with temporary tables, if you wish to use a 

214temporary table in a file-based SQLite database across multiple checkouts 

215from the connection pool, such as when using an ORM :class:`.Session` where 

216the temporary table should continue to remain after :meth:`.Session.commit` or 

217:meth:`.Session.rollback` is called, a pool which maintains a single 

218connection must be used. Use :class:`.SingletonThreadPool` if the scope is 

219only needed within the current thread, or :class:`.StaticPool` is scope is 

220needed within multiple threads for this case:: 

221 

222 # maintain the same connection per thread 

223 from sqlalchemy.pool import SingletonThreadPool 

224 engine = create_engine('sqlite:///mydb.db', 

225 poolclass=SingletonThreadPool) 

226 

227 

228 # maintain the same connection across all threads 

229 from sqlalchemy.pool import StaticPool 

230 engine = create_engine('sqlite:///mydb.db', 

231 poolclass=StaticPool) 

232 

233Note that :class:`.SingletonThreadPool` should be configured for the number 

234of threads that are to be used; beyond that number, connections will be 

235closed out in a non deterministic way. 

236 

237Unicode 

238------- 

239 

240The pysqlite driver only returns Python ``unicode`` objects in result sets, 

241never plain strings, and accommodates ``unicode`` objects within bound 

242parameter values in all cases. Regardless of the SQLAlchemy string type in 

243use, string-based result values will by Python ``unicode`` in Python 2. 

244The :class:`.Unicode` type should still be used to indicate those columns that 

245require unicode, however, so that non-``unicode`` values passed inadvertently 

246will emit a warning. Pysqlite will emit an error if a non-``unicode`` string 

247is passed containing non-ASCII characters. 

248 

249Dealing with Mixed String / Binary Columns in Python 3 

250------------------------------------------------------ 

251 

252The SQLite database is weakly typed, and as such it is possible when using 

253binary values, which in Python 3 are represented as ``b'some string'``, that a 

254particular SQLite database can have data values within different rows where 

255some of them will be returned as a ``b''`` value by the Pysqlite driver, and 

256others will be returned as Python strings, e.g. ``''`` values. This situation 

257is not known to occur if the SQLAlchemy :class:`.LargeBinary` datatype is used 

258consistently, however if a particular SQLite database has data that was 

259inserted using the Pysqlite driver directly, or when using the SQLAlchemy 

260:class:`.String` type which was later changed to :class:`.LargeBinary`, the 

261table will not be consistently readable because SQLAlchemy's 

262:class:`.LargeBinary` datatype does not handle strings so it has no way of 

263"encoding" a value that is in string format. 

264 

265To deal with a SQLite table that has mixed string / binary data in the 

266same column, use a custom type that will check each row individually:: 

267 

268 # note this is Python 3 only 

269 

270 from sqlalchemy import String 

271 from sqlalchemy import TypeDecorator 

272 

273 class MixedBinary(TypeDecorator): 

274 impl = String 

275 

276 def process_result_value(self, value, dialect): 

277 if isinstance(value, str): 

278 value = bytes(value, 'utf-8') 

279 elif value is not None: 

280 value = bytes(value) 

281 

282 return value 

283 

284Then use the above ``MixedBinary`` datatype in the place where 

285:class:`.LargeBinary` would normally be used. 

286 

287.. _pysqlite_serializable: 

288 

289Serializable isolation / Savepoints / Transactional DDL 

290------------------------------------------------------- 

291 

292In the section :ref:`sqlite_concurrency`, we refer to the pysqlite 

293driver's assortment of issues that prevent several features of SQLite 

294from working correctly. The pysqlite DBAPI driver has several 

295long-standing bugs which impact the correctness of its transactional 

296behavior. In its default mode of operation, SQLite features such as 

297SERIALIZABLE isolation, transactional DDL, and SAVEPOINT support are 

298non-functional, and in order to use these features, workarounds must 

299be taken. 

300 

301The issue is essentially that the driver attempts to second-guess the user's 

302intent, failing to start transactions and sometimes ending them prematurely, in 

303an effort to minimize the SQLite databases's file locking behavior, even 

304though SQLite itself uses "shared" locks for read-only activities. 

305 

306SQLAlchemy chooses to not alter this behavior by default, as it is the 

307long-expected behavior of the pysqlite driver; if and when the pysqlite 

308driver attempts to repair these issues, that will be more of a driver towards 

309defaults for SQLAlchemy. 

310 

311The good news is that with a few events, we can implement transactional 

312support fully, by disabling pysqlite's feature entirely and emitting BEGIN 

313ourselves. This is achieved using two event listeners:: 

314 

315 from sqlalchemy import create_engine, event 

316 

317 engine = create_engine("sqlite:///myfile.db") 

318 

319 @event.listens_for(engine, "connect") 

320 def do_connect(dbapi_connection, connection_record): 

321 # disable pysqlite's emitting of the BEGIN statement entirely. 

322 # also stops it from emitting COMMIT before any DDL. 

323 dbapi_connection.isolation_level = None 

324 

325 @event.listens_for(engine, "begin") 

326 def do_begin(conn): 

327 # emit our own BEGIN 

328 conn.execute("BEGIN") 

329 

330.. warning:: When using the above recipe, it is advised to not use the 

331 :paramref:`.Connection.execution_options.isolation_level` setting on 

332 :class:`_engine.Connection` and :func:`_sa.create_engine` 

333 with the SQLite driver, 

334 as this function necessarily will also alter the ".isolation_level" setting. 

335 

336 

337Above, we intercept a new pysqlite connection and disable any transactional 

338integration. Then, at the point at which SQLAlchemy knows that transaction 

339scope is to begin, we emit ``"BEGIN"`` ourselves. 

340 

341When we take control of ``"BEGIN"``, we can also control directly SQLite's 

342locking modes, introduced at 

343`BEGIN TRANSACTION <http://sqlite.org/lang_transaction.html>`_, 

344by adding the desired locking mode to our ``"BEGIN"``:: 

345 

346 @event.listens_for(engine, "begin") 

347 def do_begin(conn): 

348 conn.execute("BEGIN EXCLUSIVE") 

349 

350.. seealso:: 

351 

352 `BEGIN TRANSACTION <http://sqlite.org/lang_transaction.html>`_ - 

353 on the SQLite site 

354 

355 `sqlite3 SELECT does not BEGIN a transaction <http://bugs.python.org/issue9924>`_ - 

356 on the Python bug tracker 

357 

358 `sqlite3 module breaks transactions and potentially corrupts data <http://bugs.python.org/issue10740>`_ - 

359 on the Python bug tracker 

360 

361 

362""" # noqa 

363 

364import os 

365 

366from .base import DATE 

367from .base import DATETIME 

368from .base import SQLiteDialect 

369from ... import exc 

370from ... import pool 

371from ... import types as sqltypes 

372from ... import util 

373 

374 

375class _SQLite_pysqliteTimeStamp(DATETIME): 

376 def bind_processor(self, dialect): 

377 if dialect.native_datetime: 

378 return None 

379 else: 

380 return DATETIME.bind_processor(self, dialect) 

381 

382 def result_processor(self, dialect, coltype): 

383 if dialect.native_datetime: 

384 return None 

385 else: 

386 return DATETIME.result_processor(self, dialect, coltype) 

387 

388 

389class _SQLite_pysqliteDate(DATE): 

390 def bind_processor(self, dialect): 

391 if dialect.native_datetime: 

392 return None 

393 else: 

394 return DATE.bind_processor(self, dialect) 

395 

396 def result_processor(self, dialect, coltype): 

397 if dialect.native_datetime: 

398 return None 

399 else: 

400 return DATE.result_processor(self, dialect, coltype) 

401 

402 

403class SQLiteDialect_pysqlite(SQLiteDialect): 

404 default_paramstyle = "qmark" 

405 

406 colspecs = util.update_copy( 

407 SQLiteDialect.colspecs, 

408 { 

409 sqltypes.Date: _SQLite_pysqliteDate, 

410 sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp, 

411 }, 

412 ) 

413 

414 if not util.py2k: 

415 description_encoding = None 

416 

417 driver = "pysqlite" 

418 

419 @classmethod 

420 def dbapi(cls): 

421 if util.py2k: 

422 try: 

423 from pysqlite2 import dbapi2 as sqlite 

424 except ImportError: 

425 try: 

426 from sqlite3 import dbapi2 as sqlite 

427 except ImportError as e: 

428 raise e 

429 else: 

430 from sqlite3 import dbapi2 as sqlite 

431 return sqlite 

432 

433 @classmethod 

434 def _is_url_file_db(cls, url): 

435 if url.database and url.database != ":memory:": 

436 return True 

437 else: 

438 return False 

439 

440 @classmethod 

441 def get_pool_class(cls, url): 

442 if cls._is_url_file_db(url): 

443 return pool.NullPool 

444 else: 

445 return pool.SingletonThreadPool 

446 

447 def _get_server_version_info(self, connection): 

448 return self.dbapi.sqlite_version_info 

449 

450 def set_isolation_level(self, connection, level): 

451 if hasattr(connection, "connection"): 

452 dbapi_connection = connection.connection 

453 else: 

454 dbapi_connection = connection 

455 

456 if level == "AUTOCOMMIT": 

457 dbapi_connection.isolation_level = None 

458 else: 

459 dbapi_connection.isolation_level = "" 

460 return super(SQLiteDialect_pysqlite, self).set_isolation_level( 

461 connection, level 

462 ) 

463 

464 def create_connect_args(self, url): 

465 if url.username or url.password or url.host or url.port: 

466 raise exc.ArgumentError( 

467 "Invalid SQLite URL: %s\n" 

468 "Valid SQLite URL forms are:\n" 

469 " sqlite:///:memory: (or, sqlite://)\n" 

470 " sqlite:///relative/path/to/file.db\n" 

471 " sqlite:////absolute/path/to/file.db" % (url,) 

472 ) 

473 

474 # theoretically, this list can be augmented, at least as far as 

475 # parameter names accepted by sqlite3/pysqlite, using 

476 # inspect.getfullargspec(). for the moment this seems like overkill 

477 # as these parameters don't change very often, and as always, 

478 # parameters passed to connect_args will always go to the 

479 # sqlite3/pysqlite driver. 

480 pysqlite_args = [ 

481 ("uri", bool), 

482 ("timeout", float), 

483 ("isolation_level", str), 

484 ("detect_types", int), 

485 ("check_same_thread", bool), 

486 ("cached_statements", int), 

487 ] 

488 opts = url.query 

489 pysqlite_opts = {} 

490 for key, type_ in pysqlite_args: 

491 util.coerce_kw_type(opts, key, type_, dest=pysqlite_opts) 

492 

493 if pysqlite_opts.get("uri", False): 

494 uri_opts = opts.copy() 

495 # here, we are actually separating the parameters that go to 

496 # sqlite3/pysqlite vs. those that go the SQLite URI. What if 

497 # two names conflict? again, this seems to be not the case right 

498 # now, and in the case that new names are added to 

499 # either side which overlap, again the sqlite3/pysqlite parameters 

500 # can be passed through connect_args instead of in the URL. 

501 # If SQLite native URIs add a parameter like "timeout" that 

502 # we already have listed here for the python driver, then we need 

503 # to adjust for that here. 

504 for key, type_ in pysqlite_args: 

505 uri_opts.pop(key, None) 

506 filename = url.database 

507 if uri_opts: 

508 # sorting of keys is for unit test support 

509 filename += "?" + ( 

510 "&".join( 

511 "%s=%s" % (key, uri_opts[key]) 

512 for key in sorted(uri_opts) 

513 ) 

514 ) 

515 else: 

516 filename = url.database or ":memory:" 

517 if filename != ":memory:": 

518 filename = os.path.abspath(filename) 

519 

520 return ([filename], pysqlite_opts) 

521 

522 def is_disconnect(self, e, connection, cursor): 

523 return isinstance( 

524 e, self.dbapi.ProgrammingError 

525 ) and "Cannot operate on a closed database." in str(e) 

526 

527 

528dialect = SQLiteDialect_pysqlite