pg8000.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. # dialects/postgresql/pg8000.py
  2. # Copyright (C) 2005-2024 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
  25. # encoding
  26. client_encoding = utf8
  27. The ``client_encoding`` can be overridden for a session by executing the 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. .. _pg8000_ssl:
  34. SSL Connections
  35. ---------------
  36. pg8000 accepts a Python ``SSLContext`` object which may be specified using the
  37. :paramref:`_sa.create_engine.connect_args` dictionary::
  38. import ssl
  39. ssl_context = ssl.create_default_context()
  40. engine = sa.create_engine(
  41. "postgresql+pg8000://scott:tiger@192.168.0.199/test",
  42. connect_args={"ssl_context": ssl_context},
  43. )
  44. If the server uses an automatically-generated certificate that is self-signed
  45. or does not match the host name (as seen from the client), it may also be
  46. necessary to disable hostname checking::
  47. import ssl
  48. ssl_context = ssl.create_default_context()
  49. ssl_context.check_hostname = False
  50. ssl_context.verify_mode = ssl.CERT_NONE
  51. engine = sa.create_engine(
  52. "postgresql+pg8000://scott:tiger@192.168.0.199/test",
  53. connect_args={"ssl_context": ssl_context},
  54. )
  55. .. _pg8000_isolation_level:
  56. pg8000 Transaction Isolation Level
  57. -------------------------------------
  58. The pg8000 dialect offers the same isolation level settings as that
  59. of the :ref:`psycopg2 <psycopg2_isolation_level>` dialect:
  60. * ``READ COMMITTED``
  61. * ``READ UNCOMMITTED``
  62. * ``REPEATABLE READ``
  63. * ``SERIALIZABLE``
  64. * ``AUTOCOMMIT``
  65. .. seealso::
  66. :ref:`postgresql_isolation_level`
  67. :ref:`psycopg2_isolation_level`
  68. """ # noqa
  69. import decimal
  70. import re
  71. from . import ranges
  72. from .array import ARRAY as PGARRAY
  73. from .base import _DECIMAL_TYPES
  74. from .base import _FLOAT_TYPES
  75. from .base import _INT_TYPES
  76. from .base import ENUM
  77. from .base import INTERVAL
  78. from .base import PGCompiler
  79. from .base import PGDialect
  80. from .base import PGExecutionContext
  81. from .base import PGIdentifierPreparer
  82. from .json import JSON
  83. from .json import JSONB
  84. from .json import JSONPathType
  85. from .pg_catalog import _SpaceVector
  86. from .pg_catalog import OIDVECTOR
  87. from .types import CITEXT
  88. from ... import exc
  89. from ... import util
  90. from ...engine import processors
  91. from ...sql import sqltypes
  92. from ...sql.elements import quoted_name
  93. class _PGString(sqltypes.String):
  94. render_bind_cast = True
  95. class _PGNumeric(sqltypes.Numeric):
  96. render_bind_cast = True
  97. def result_processor(self, dialect, coltype):
  98. if self.asdecimal:
  99. if coltype in _FLOAT_TYPES:
  100. return processors.to_decimal_processor_factory(
  101. decimal.Decimal, self._effective_decimal_return_scale
  102. )
  103. elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
  104. # pg8000 returns Decimal natively for 1700
  105. return None
  106. else:
  107. raise exc.InvalidRequestError(
  108. "Unknown PG numeric type: %d" % coltype
  109. )
  110. else:
  111. if coltype in _FLOAT_TYPES:
  112. # pg8000 returns float natively for 701
  113. return None
  114. elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
  115. return processors.to_float
  116. else:
  117. raise exc.InvalidRequestError(
  118. "Unknown PG numeric type: %d" % coltype
  119. )
  120. class _PGFloat(_PGNumeric, sqltypes.Float):
  121. __visit_name__ = "float"
  122. render_bind_cast = True
  123. class _PGNumericNoBind(_PGNumeric):
  124. def bind_processor(self, dialect):
  125. return None
  126. class _PGJSON(JSON):
  127. render_bind_cast = True
  128. def result_processor(self, dialect, coltype):
  129. return None
  130. class _PGJSONB(JSONB):
  131. render_bind_cast = True
  132. def result_processor(self, dialect, coltype):
  133. return None
  134. class _PGJSONIndexType(sqltypes.JSON.JSONIndexType):
  135. def get_dbapi_type(self, dbapi):
  136. raise NotImplementedError("should not be here")
  137. class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType):
  138. __visit_name__ = "json_int_index"
  139. render_bind_cast = True
  140. class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType):
  141. __visit_name__ = "json_str_index"
  142. render_bind_cast = True
  143. class _PGJSONPathType(JSONPathType):
  144. pass
  145. # DBAPI type 1009
  146. class _PGEnum(ENUM):
  147. def get_dbapi_type(self, dbapi):
  148. return dbapi.UNKNOWN
  149. class _PGInterval(INTERVAL):
  150. render_bind_cast = True
  151. def get_dbapi_type(self, dbapi):
  152. return dbapi.INTERVAL
  153. @classmethod
  154. def adapt_emulated_to_native(cls, interval, **kw):
  155. return _PGInterval(precision=interval.second_precision)
  156. class _PGTimeStamp(sqltypes.DateTime):
  157. render_bind_cast = True
  158. class _PGDate(sqltypes.Date):
  159. render_bind_cast = True
  160. class _PGTime(sqltypes.Time):
  161. render_bind_cast = True
  162. class _PGInteger(sqltypes.Integer):
  163. render_bind_cast = True
  164. class _PGSmallInteger(sqltypes.SmallInteger):
  165. render_bind_cast = True
  166. class _PGNullType(sqltypes.NullType):
  167. pass
  168. class _PGBigInteger(sqltypes.BigInteger):
  169. render_bind_cast = True
  170. class _PGBoolean(sqltypes.Boolean):
  171. render_bind_cast = True
  172. class _PGARRAY(PGARRAY):
  173. render_bind_cast = True
  174. class _PGOIDVECTOR(_SpaceVector, OIDVECTOR):
  175. pass
  176. class _Pg8000Range(ranges.AbstractRangeImpl):
  177. def bind_processor(self, dialect):
  178. pg8000_Range = dialect.dbapi.Range
  179. def to_range(value):
  180. if isinstance(value, ranges.Range):
  181. value = pg8000_Range(
  182. value.lower, value.upper, value.bounds, value.empty
  183. )
  184. return value
  185. return to_range
  186. def result_processor(self, dialect, coltype):
  187. def to_range(value):
  188. if value is not None:
  189. value = ranges.Range(
  190. value.lower,
  191. value.upper,
  192. bounds=value.bounds,
  193. empty=value.is_empty,
  194. )
  195. return value
  196. return to_range
  197. class _Pg8000MultiRange(ranges.AbstractMultiRangeImpl):
  198. def bind_processor(self, dialect):
  199. pg8000_Range = dialect.dbapi.Range
  200. def to_multirange(value):
  201. if isinstance(value, list):
  202. mr = []
  203. for v in value:
  204. if isinstance(v, ranges.Range):
  205. mr.append(
  206. pg8000_Range(v.lower, v.upper, v.bounds, v.empty)
  207. )
  208. else:
  209. mr.append(v)
  210. return mr
  211. else:
  212. return value
  213. return to_multirange
  214. def result_processor(self, dialect, coltype):
  215. def to_multirange(value):
  216. if value is None:
  217. return None
  218. mr = []
  219. for v in value:
  220. mr.append(
  221. ranges.Range(
  222. v.lower, v.upper, bounds=v.bounds, empty=v.is_empty
  223. )
  224. )
  225. return mr
  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 set_readonly(self, connection, value):
  408. cursor = connection.cursor()
  409. try:
  410. cursor.execute(
  411. "SET SESSION CHARACTERISTICS AS TRANSACTION %s"
  412. % ("READ ONLY" if value else "READ WRITE")
  413. )
  414. cursor.execute("COMMIT")
  415. finally:
  416. cursor.close()
  417. def get_readonly(self, connection):
  418. cursor = connection.cursor()
  419. try:
  420. cursor.execute("show transaction_read_only")
  421. val = cursor.fetchone()[0]
  422. finally:
  423. cursor.close()
  424. return val == "on"
  425. def set_deferrable(self, connection, value):
  426. cursor = connection.cursor()
  427. try:
  428. cursor.execute(
  429. "SET SESSION CHARACTERISTICS AS TRANSACTION %s"
  430. % ("DEFERRABLE" if value else "NOT DEFERRABLE")
  431. )
  432. cursor.execute("COMMIT")
  433. finally:
  434. cursor.close()
  435. def get_deferrable(self, connection):
  436. cursor = connection.cursor()
  437. try:
  438. cursor.execute("show transaction_deferrable")
  439. val = cursor.fetchone()[0]
  440. finally:
  441. cursor.close()
  442. return val == "on"
  443. def _set_client_encoding(self, dbapi_connection, client_encoding):
  444. cursor = dbapi_connection.cursor()
  445. cursor.execute(
  446. f"""SET CLIENT_ENCODING TO '{
  447. client_encoding.replace("'", "''")
  448. }'"""
  449. )
  450. cursor.execute("COMMIT")
  451. cursor.close()
  452. def do_begin_twophase(self, connection, xid):
  453. connection.connection.tpc_begin((0, xid, ""))
  454. def do_prepare_twophase(self, connection, xid):
  455. connection.connection.tpc_prepare()
  456. def do_rollback_twophase(
  457. self, connection, xid, is_prepared=True, recover=False
  458. ):
  459. connection.connection.tpc_rollback((0, xid, ""))
  460. def do_commit_twophase(
  461. self, connection, xid, is_prepared=True, recover=False
  462. ):
  463. connection.connection.tpc_commit((0, xid, ""))
  464. def do_recover_twophase(self, connection):
  465. return [row[1] for row in connection.connection.tpc_recover()]
  466. def on_connect(self):
  467. fns = []
  468. def on_connect(conn):
  469. conn.py_types[quoted_name] = conn.py_types[str]
  470. fns.append(on_connect)
  471. if self.client_encoding is not None:
  472. def on_connect(conn):
  473. self._set_client_encoding(conn, self.client_encoding)
  474. fns.append(on_connect)
  475. if self._native_inet_types is False:
  476. def on_connect(conn):
  477. # inet
  478. conn.register_in_adapter(869, lambda s: s)
  479. # cidr
  480. conn.register_in_adapter(650, lambda s: s)
  481. fns.append(on_connect)
  482. if self._json_deserializer:
  483. def on_connect(conn):
  484. # json
  485. conn.register_in_adapter(114, self._json_deserializer)
  486. # jsonb
  487. conn.register_in_adapter(3802, self._json_deserializer)
  488. fns.append(on_connect)
  489. if len(fns) > 0:
  490. def on_connect(conn):
  491. for fn in fns:
  492. fn(conn)
  493. return on_connect
  494. else:
  495. return None
  496. @util.memoized_property
  497. def _dialect_specific_select_one(self):
  498. return ";"
  499. dialect = PGDialect_pg8000