pg8000.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. # dialects/postgresql/pg8000.py
  2. # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors <see AUTHORS
  3. # file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. # mypy: ignore-errors
  8. r"""
  9. .. dialect:: postgresql+pg8000
  10. :name: pg8000
  11. :dbapi: pg8000
  12. :connectstring: postgresql+pg8000://user:password@host:port/dbname[?key=value&key=value...]
  13. :url: https://pypi.org/project/pg8000/
  14. .. versionchanged:: 1.4 The pg8000 dialect has been updated for version
  15. 1.16.6 and higher, and is again part of SQLAlchemy's continuous integration
  16. with full feature support.
  17. .. _pg8000_unicode:
  18. Unicode
  19. -------
  20. pg8000 will encode / decode string values between it and the server using the
  21. PostgreSQL ``client_encoding`` parameter; by default this is the value in
  22. the ``postgresql.conf`` file, which often defaults to ``SQL_ASCII``.
  23. Typically, this can be changed to ``utf-8``, as a more useful default::
  24. # client_encoding = sql_ascii # actually, defaults to database encoding
  25. client_encoding = utf8
  26. The ``client_encoding`` can be overridden for a session by executing the SQL:
  27. .. sourcecode:: sql
  28. SET CLIENT_ENCODING TO 'utf8';
  29. SQLAlchemy will execute this SQL on all new connections based on the value
  30. passed to :func:`_sa.create_engine` using the ``client_encoding`` parameter::
  31. engine = create_engine(
  32. "postgresql+pg8000://user:pass@host/dbname", client_encoding="utf8"
  33. )
  34. .. _pg8000_ssl:
  35. SSL Connections
  36. ---------------
  37. pg8000 accepts a Python ``SSLContext`` object which may be specified using the
  38. :paramref:`_sa.create_engine.connect_args` dictionary::
  39. import ssl
  40. ssl_context = ssl.create_default_context()
  41. engine = sa.create_engine(
  42. "postgresql+pg8000://scott:tiger@192.168.0.199/test",
  43. connect_args={"ssl_context": ssl_context},
  44. )
  45. If the server uses an automatically-generated certificate that is self-signed
  46. or does not match the host name (as seen from the client), it may also be
  47. necessary to disable hostname checking::
  48. import ssl
  49. ssl_context = ssl.create_default_context()
  50. ssl_context.check_hostname = False
  51. ssl_context.verify_mode = ssl.CERT_NONE
  52. engine = sa.create_engine(
  53. "postgresql+pg8000://scott:tiger@192.168.0.199/test",
  54. connect_args={"ssl_context": ssl_context},
  55. )
  56. .. _pg8000_isolation_level:
  57. pg8000 Transaction Isolation Level
  58. -------------------------------------
  59. The pg8000 dialect offers the same isolation level settings as that
  60. of the :ref:`psycopg2 <psycopg2_isolation_level>` dialect:
  61. * ``READ COMMITTED``
  62. * ``READ UNCOMMITTED``
  63. * ``REPEATABLE READ``
  64. * ``SERIALIZABLE``
  65. * ``AUTOCOMMIT``
  66. .. seealso::
  67. :ref:`postgresql_isolation_level`
  68. :ref:`psycopg2_isolation_level`
  69. """ # noqa
  70. import decimal
  71. import re
  72. from . import ranges
  73. from .array import ARRAY as PGARRAY
  74. from .base import _DECIMAL_TYPES
  75. from .base import _FLOAT_TYPES
  76. from .base import _INT_TYPES
  77. from .base import ENUM
  78. from .base import INTERVAL
  79. from .base import PGCompiler
  80. from .base import PGDialect
  81. from .base import PGExecutionContext
  82. from .base import PGIdentifierPreparer
  83. from .json import JSON
  84. from .json import JSONB
  85. from .json import JSONPathType
  86. from .pg_catalog import _SpaceVector
  87. from .pg_catalog import OIDVECTOR
  88. from .types import CITEXT
  89. from ... import exc
  90. from ... import util
  91. from ...engine import processors
  92. from ...sql import sqltypes
  93. from ...sql.elements import quoted_name
  94. class _PGString(sqltypes.String):
  95. render_bind_cast = True
  96. class _PGNumeric(sqltypes.Numeric):
  97. render_bind_cast = True
  98. def result_processor(self, dialect, coltype):
  99. if self.asdecimal:
  100. if coltype in _FLOAT_TYPES:
  101. return processors.to_decimal_processor_factory(
  102. decimal.Decimal, self._effective_decimal_return_scale
  103. )
  104. elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
  105. # pg8000 returns Decimal natively for 1700
  106. return None
  107. else:
  108. raise exc.InvalidRequestError(
  109. "Unknown PG numeric type: %d" % coltype
  110. )
  111. else:
  112. if coltype in _FLOAT_TYPES:
  113. # pg8000 returns float natively for 701
  114. return None
  115. elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
  116. return processors.to_float
  117. else:
  118. raise exc.InvalidRequestError(
  119. "Unknown PG numeric type: %d" % coltype
  120. )
  121. class _PGFloat(_PGNumeric, sqltypes.Float):
  122. __visit_name__ = "float"
  123. render_bind_cast = True
  124. class _PGNumericNoBind(_PGNumeric):
  125. def bind_processor(self, dialect):
  126. return None
  127. class _PGJSON(JSON):
  128. render_bind_cast = True
  129. def result_processor(self, dialect, coltype):
  130. return None
  131. class _PGJSONB(JSONB):
  132. render_bind_cast = True
  133. def result_processor(self, dialect, coltype):
  134. return None
  135. class _PGJSONIndexType(sqltypes.JSON.JSONIndexType):
  136. def get_dbapi_type(self, dbapi):
  137. raise NotImplementedError("should not be here")
  138. class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType):
  139. __visit_name__ = "json_int_index"
  140. render_bind_cast = True
  141. class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType):
  142. __visit_name__ = "json_str_index"
  143. render_bind_cast = True
  144. class _PGJSONPathType(JSONPathType):
  145. pass
  146. # DBAPI type 1009
  147. class _PGEnum(ENUM):
  148. def get_dbapi_type(self, dbapi):
  149. return dbapi.UNKNOWN
  150. class _PGInterval(INTERVAL):
  151. render_bind_cast = True
  152. def get_dbapi_type(self, dbapi):
  153. return dbapi.INTERVAL
  154. @classmethod
  155. def adapt_emulated_to_native(cls, interval, **kw):
  156. return _PGInterval(precision=interval.second_precision)
  157. class _PGTimeStamp(sqltypes.DateTime):
  158. render_bind_cast = True
  159. class _PGDate(sqltypes.Date):
  160. render_bind_cast = True
  161. class _PGTime(sqltypes.Time):
  162. render_bind_cast = True
  163. class _PGInteger(sqltypes.Integer):
  164. render_bind_cast = True
  165. class _PGSmallInteger(sqltypes.SmallInteger):
  166. render_bind_cast = True
  167. class _PGNullType(sqltypes.NullType):
  168. pass
  169. class _PGBigInteger(sqltypes.BigInteger):
  170. render_bind_cast = True
  171. class _PGBoolean(sqltypes.Boolean):
  172. render_bind_cast = True
  173. class _PGARRAY(PGARRAY):
  174. render_bind_cast = True
  175. class _PGOIDVECTOR(_SpaceVector, OIDVECTOR):
  176. pass
  177. class _Pg8000Range(ranges.AbstractSingleRangeImpl):
  178. def bind_processor(self, dialect):
  179. pg8000_Range = dialect.dbapi.Range
  180. def to_range(value):
  181. if isinstance(value, ranges.Range):
  182. value = pg8000_Range(
  183. value.lower, value.upper, value.bounds, value.empty
  184. )
  185. return value
  186. return to_range
  187. def result_processor(self, dialect, coltype):
  188. def to_range(value):
  189. if value is not None:
  190. value = ranges.Range(
  191. value.lower,
  192. value.upper,
  193. bounds=value.bounds,
  194. empty=value.is_empty,
  195. )
  196. return value
  197. return to_range
  198. class _Pg8000MultiRange(ranges.AbstractMultiRangeImpl):
  199. def bind_processor(self, dialect):
  200. pg8000_Range = dialect.dbapi.Range
  201. def to_multirange(value):
  202. if isinstance(value, list):
  203. mr = []
  204. for v in value:
  205. if isinstance(v, ranges.Range):
  206. mr.append(
  207. pg8000_Range(v.lower, v.upper, v.bounds, v.empty)
  208. )
  209. else:
  210. mr.append(v)
  211. return mr
  212. else:
  213. return value
  214. return to_multirange
  215. def result_processor(self, dialect, coltype):
  216. def to_multirange(value):
  217. if value is None:
  218. return None
  219. else:
  220. return ranges.MultiRange(
  221. ranges.Range(
  222. v.lower, v.upper, bounds=v.bounds, empty=v.is_empty
  223. )
  224. for v in value
  225. )
  226. return to_multirange
  227. _server_side_id = util.counter()
  228. class PGExecutionContext_pg8000(PGExecutionContext):
  229. def create_server_side_cursor(self):
  230. ident = "c_%s_%s" % (hex(id(self))[2:], hex(_server_side_id())[2:])
  231. return ServerSideCursor(self._dbapi_connection.cursor(), ident)
  232. def pre_exec(self):
  233. if not self.compiled:
  234. return
  235. class ServerSideCursor:
  236. server_side = True
  237. def __init__(self, cursor, ident):
  238. self.ident = ident
  239. self.cursor = cursor
  240. @property
  241. def connection(self):
  242. return self.cursor.connection
  243. @property
  244. def rowcount(self):
  245. return self.cursor.rowcount
  246. @property
  247. def description(self):
  248. return self.cursor.description
  249. def execute(self, operation, args=(), stream=None):
  250. op = "DECLARE " + self.ident + " NO SCROLL CURSOR FOR " + operation
  251. self.cursor.execute(op, args, stream=stream)
  252. return self
  253. def executemany(self, operation, param_sets):
  254. self.cursor.executemany(operation, param_sets)
  255. return self
  256. def fetchone(self):
  257. self.cursor.execute("FETCH FORWARD 1 FROM " + self.ident)
  258. return self.cursor.fetchone()
  259. def fetchmany(self, num=None):
  260. if num is None:
  261. return self.fetchall()
  262. else:
  263. self.cursor.execute(
  264. "FETCH FORWARD " + str(int(num)) + " FROM " + self.ident
  265. )
  266. return self.cursor.fetchall()
  267. def fetchall(self):
  268. self.cursor.execute("FETCH FORWARD ALL FROM " + self.ident)
  269. return self.cursor.fetchall()
  270. def close(self):
  271. self.cursor.execute("CLOSE " + self.ident)
  272. self.cursor.close()
  273. def setinputsizes(self, *sizes):
  274. self.cursor.setinputsizes(*sizes)
  275. def setoutputsize(self, size, column=None):
  276. pass
  277. class PGCompiler_pg8000(PGCompiler):
  278. def visit_mod_binary(self, binary, operator, **kw):
  279. return (
  280. self.process(binary.left, **kw)
  281. + " %% "
  282. + self.process(binary.right, **kw)
  283. )
  284. class PGIdentifierPreparer_pg8000(PGIdentifierPreparer):
  285. def __init__(self, *args, **kwargs):
  286. PGIdentifierPreparer.__init__(self, *args, **kwargs)
  287. self._double_percents = False
  288. class PGDialect_pg8000(PGDialect):
  289. driver = "pg8000"
  290. supports_statement_cache = True
  291. supports_unicode_statements = True
  292. supports_unicode_binds = True
  293. default_paramstyle = "format"
  294. supports_sane_multi_rowcount = True
  295. execution_ctx_cls = PGExecutionContext_pg8000
  296. statement_compiler = PGCompiler_pg8000
  297. preparer = PGIdentifierPreparer_pg8000
  298. supports_server_side_cursors = True
  299. render_bind_cast = True
  300. # reversed as of pg8000 1.16.6. 1.16.5 and lower
  301. # are no longer compatible
  302. description_encoding = None
  303. # description_encoding = "use_encoding"
  304. colspecs = util.update_copy(
  305. PGDialect.colspecs,
  306. {
  307. sqltypes.String: _PGString,
  308. sqltypes.Numeric: _PGNumericNoBind,
  309. sqltypes.Float: _PGFloat,
  310. sqltypes.JSON: _PGJSON,
  311. sqltypes.Boolean: _PGBoolean,
  312. sqltypes.NullType: _PGNullType,
  313. JSONB: _PGJSONB,
  314. CITEXT: CITEXT,
  315. sqltypes.JSON.JSONPathType: _PGJSONPathType,
  316. sqltypes.JSON.JSONIndexType: _PGJSONIndexType,
  317. sqltypes.JSON.JSONIntIndexType: _PGJSONIntIndexType,
  318. sqltypes.JSON.JSONStrIndexType: _PGJSONStrIndexType,
  319. sqltypes.Interval: _PGInterval,
  320. INTERVAL: _PGInterval,
  321. sqltypes.DateTime: _PGTimeStamp,
  322. sqltypes.DateTime: _PGTimeStamp,
  323. sqltypes.Date: _PGDate,
  324. sqltypes.Time: _PGTime,
  325. sqltypes.Integer: _PGInteger,
  326. sqltypes.SmallInteger: _PGSmallInteger,
  327. sqltypes.BigInteger: _PGBigInteger,
  328. sqltypes.Enum: _PGEnum,
  329. sqltypes.ARRAY: _PGARRAY,
  330. OIDVECTOR: _PGOIDVECTOR,
  331. ranges.INT4RANGE: _Pg8000Range,
  332. ranges.INT8RANGE: _Pg8000Range,
  333. ranges.NUMRANGE: _Pg8000Range,
  334. ranges.DATERANGE: _Pg8000Range,
  335. ranges.TSRANGE: _Pg8000Range,
  336. ranges.TSTZRANGE: _Pg8000Range,
  337. ranges.INT4MULTIRANGE: _Pg8000MultiRange,
  338. ranges.INT8MULTIRANGE: _Pg8000MultiRange,
  339. ranges.NUMMULTIRANGE: _Pg8000MultiRange,
  340. ranges.DATEMULTIRANGE: _Pg8000MultiRange,
  341. ranges.TSMULTIRANGE: _Pg8000MultiRange,
  342. ranges.TSTZMULTIRANGE: _Pg8000MultiRange,
  343. },
  344. )
  345. def __init__(self, client_encoding=None, **kwargs):
  346. PGDialect.__init__(self, **kwargs)
  347. self.client_encoding = client_encoding
  348. if self._dbapi_version < (1, 16, 6):
  349. raise NotImplementedError("pg8000 1.16.6 or greater is required")
  350. if self._native_inet_types:
  351. raise NotImplementedError(
  352. "The pg8000 dialect does not fully implement "
  353. "ipaddress type handling; INET is supported by default, "
  354. "CIDR is not"
  355. )
  356. @util.memoized_property
  357. def _dbapi_version(self):
  358. if self.dbapi and hasattr(self.dbapi, "__version__"):
  359. return tuple(
  360. [
  361. int(x)
  362. for x in re.findall(
  363. r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__
  364. )
  365. ]
  366. )
  367. else:
  368. return (99, 99, 99)
  369. @classmethod
  370. def import_dbapi(cls):
  371. return __import__("pg8000")
  372. def create_connect_args(self, url):
  373. opts = url.translate_connect_args(username="user")
  374. if "port" in opts:
  375. opts["port"] = int(opts["port"])
  376. opts.update(url.query)
  377. return ([], opts)
  378. def is_disconnect(self, e, connection, cursor):
  379. if isinstance(e, self.dbapi.InterfaceError) and "network error" in str(
  380. e
  381. ):
  382. # new as of pg8000 1.19.0 for broken connections
  383. return True
  384. # connection was closed normally
  385. return "connection is closed" in str(e)
  386. def get_isolation_level_values(self, dbapi_connection):
  387. return (
  388. "AUTOCOMMIT",
  389. "READ COMMITTED",
  390. "READ UNCOMMITTED",
  391. "REPEATABLE READ",
  392. "SERIALIZABLE",
  393. )
  394. def set_isolation_level(self, dbapi_connection, level):
  395. level = level.replace("_", " ")
  396. if level == "AUTOCOMMIT":
  397. dbapi_connection.autocommit = True
  398. else:
  399. dbapi_connection.autocommit = False
  400. cursor = dbapi_connection.cursor()
  401. cursor.execute(
  402. "SET SESSION CHARACTERISTICS AS TRANSACTION "
  403. f"ISOLATION LEVEL {level}"
  404. )
  405. cursor.execute("COMMIT")
  406. cursor.close()
  407. def detect_autocommit_setting(self, dbapi_conn) -> bool:
  408. return bool(dbapi_conn.autocommit)
  409. def set_readonly(self, connection, value):
  410. cursor = connection.cursor()
  411. try:
  412. cursor.execute(
  413. "SET SESSION CHARACTERISTICS AS TRANSACTION %s"
  414. % ("READ ONLY" if value else "READ WRITE")
  415. )
  416. cursor.execute("COMMIT")
  417. finally:
  418. cursor.close()
  419. def get_readonly(self, connection):
  420. cursor = connection.cursor()
  421. try:
  422. cursor.execute("show transaction_read_only")
  423. val = cursor.fetchone()[0]
  424. finally:
  425. cursor.close()
  426. return val == "on"
  427. def set_deferrable(self, connection, value):
  428. cursor = connection.cursor()
  429. try:
  430. cursor.execute(
  431. "SET SESSION CHARACTERISTICS AS TRANSACTION %s"
  432. % ("DEFERRABLE" if value else "NOT DEFERRABLE")
  433. )
  434. cursor.execute("COMMIT")
  435. finally:
  436. cursor.close()
  437. def get_deferrable(self, connection):
  438. cursor = connection.cursor()
  439. try:
  440. cursor.execute("show transaction_deferrable")
  441. val = cursor.fetchone()[0]
  442. finally:
  443. cursor.close()
  444. return val == "on"
  445. def _set_client_encoding(self, dbapi_connection, client_encoding):
  446. cursor = dbapi_connection.cursor()
  447. cursor.execute(
  448. f"""SET CLIENT_ENCODING TO '{
  449. client_encoding.replace("'", "''")
  450. }'"""
  451. )
  452. cursor.execute("COMMIT")
  453. cursor.close()
  454. def do_begin_twophase(self, connection, xid):
  455. connection.connection.tpc_begin((0, xid, ""))
  456. def do_prepare_twophase(self, connection, xid):
  457. connection.connection.tpc_prepare()
  458. def do_rollback_twophase(
  459. self, connection, xid, is_prepared=True, recover=False
  460. ):
  461. connection.connection.tpc_rollback((0, xid, ""))
  462. def do_commit_twophase(
  463. self, connection, xid, is_prepared=True, recover=False
  464. ):
  465. connection.connection.tpc_commit((0, xid, ""))
  466. def do_recover_twophase(self, connection):
  467. return [row[1] for row in connection.connection.tpc_recover()]
  468. def on_connect(self):
  469. fns = []
  470. def on_connect(conn):
  471. conn.py_types[quoted_name] = conn.py_types[str]
  472. fns.append(on_connect)
  473. if self.client_encoding is not None:
  474. def on_connect(conn):
  475. self._set_client_encoding(conn, self.client_encoding)
  476. fns.append(on_connect)
  477. if self._native_inet_types is False:
  478. def on_connect(conn):
  479. # inet
  480. conn.register_in_adapter(869, lambda s: s)
  481. # cidr
  482. conn.register_in_adapter(650, lambda s: s)
  483. fns.append(on_connect)
  484. if self._json_deserializer:
  485. def on_connect(conn):
  486. # json
  487. conn.register_in_adapter(114, self._json_deserializer)
  488. # jsonb
  489. conn.register_in_adapter(3802, self._json_deserializer)
  490. fns.append(on_connect)
  491. if len(fns) > 0:
  492. def on_connect(conn):
  493. for fn in fns:
  494. fn(conn)
  495. return on_connect
  496. else:
  497. return None
  498. @util.memoized_property
  499. def _dialect_specific_select_one(self):
  500. return ";"
  501. dialect = PGDialect_pg8000