Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# postgresql/pg8000.py 

2# Copyright (C) 2005-2020 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: http://www.opensource.org/licenses/mit-license.php 

7r""" 

8.. dialect:: postgresql+pg8000 

9 :name: pg8000 

10 :dbapi: pg8000 

11 :connectstring: postgresql+pg8000://user:password@host:port/dbname[?key=value&key=value...] 

12 :url: https://pythonhosted.org/pg8000/ 

13 

14.. note:: 

15 

16 The pg8000 dialect is **not tested as part of SQLAlchemy's continuous 

17 integration** and may have unresolved issues. The recommended PostgreSQL 

18 dialect is psycopg2. 

19 

20.. _pg8000_unicode: 

21 

22Unicode 

23------- 

24 

25pg8000 will encode / decode string values between it and the server using the 

26PostgreSQL ``client_encoding`` parameter; by default this is the value in 

27the ``postgresql.conf`` file, which often defaults to ``SQL_ASCII``. 

28Typically, this can be changed to ``utf-8``, as a more useful default:: 

29 

30 #client_encoding = sql_ascii # actually, defaults to database 

31 # encoding 

32 client_encoding = utf8 

33 

34The ``client_encoding`` can be overridden for a session by executing the SQL: 

35 

36SET CLIENT_ENCODING TO 'utf8'; 

37 

38SQLAlchemy will execute this SQL on all new connections based on the value 

39passed to :func:`_sa.create_engine` using the ``client_encoding`` parameter:: 

40 

41 engine = create_engine( 

42 "postgresql+pg8000://user:pass@host/dbname", client_encoding='utf8') 

43 

44 

45.. _pg8000_isolation_level: 

46 

47pg8000 Transaction Isolation Level 

48------------------------------------- 

49 

50The pg8000 dialect offers the same isolation level settings as that 

51of the :ref:`psycopg2 <psycopg2_isolation_level>` dialect: 

52 

53* ``READ COMMITTED`` 

54* ``READ UNCOMMITTED`` 

55* ``REPEATABLE READ`` 

56* ``SERIALIZABLE`` 

57* ``AUTOCOMMIT`` 

58 

59.. versionadded:: 0.9.5 support for AUTOCOMMIT isolation level when using 

60 pg8000. 

61 

62.. seealso:: 

63 

64 :ref:`postgresql_isolation_level` 

65 

66 :ref:`psycopg2_isolation_level` 

67 

68 

69""" # noqa 

70import decimal 

71import re 

72 

73from .base import _DECIMAL_TYPES 

74from .base import _FLOAT_TYPES 

75from .base import _INT_TYPES 

76from .base import PGCompiler 

77from .base import PGDialect 

78from .base import PGExecutionContext 

79from .base import PGIdentifierPreparer 

80from .base import UUID 

81from .json import JSON 

82from ... import exc 

83from ... import processors 

84from ... import types as sqltypes 

85from ... import util 

86from ...sql.elements import quoted_name 

87 

88 

89try: 

90 from uuid import UUID as _python_UUID # noqa 

91except ImportError: 

92 _python_UUID = None 

93 

94 

95class _PGNumeric(sqltypes.Numeric): 

96 def result_processor(self, dialect, coltype): 

97 if self.asdecimal: 

98 if coltype in _FLOAT_TYPES: 

99 return processors.to_decimal_processor_factory( 

100 decimal.Decimal, self._effective_decimal_return_scale 

101 ) 

102 elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: 

103 # pg8000 returns Decimal natively for 1700 

104 return None 

105 else: 

106 raise exc.InvalidRequestError( 

107 "Unknown PG numeric type: %d" % coltype 

108 ) 

109 else: 

110 if coltype in _FLOAT_TYPES: 

111 # pg8000 returns float natively for 701 

112 return None 

113 elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: 

114 return processors.to_float 

115 else: 

116 raise exc.InvalidRequestError( 

117 "Unknown PG numeric type: %d" % coltype 

118 ) 

119 

120 

121class _PGNumericNoBind(_PGNumeric): 

122 def bind_processor(self, dialect): 

123 return None 

124 

125 

126class _PGJSON(JSON): 

127 def result_processor(self, dialect, coltype): 

128 if dialect._dbapi_version > (1, 10, 1): 

129 return None # Has native JSON 

130 else: 

131 return super(_PGJSON, self).result_processor(dialect, coltype) 

132 

133 

134class _PGUUID(UUID): 

135 def bind_processor(self, dialect): 

136 if not self.as_uuid: 

137 

138 def process(value): 

139 if value is not None: 

140 value = _python_UUID(value) 

141 return value 

142 

143 return process 

144 

145 def result_processor(self, dialect, coltype): 

146 if not self.as_uuid: 

147 

148 def process(value): 

149 if value is not None: 

150 value = str(value) 

151 return value 

152 

153 return process 

154 

155 

156class PGExecutionContext_pg8000(PGExecutionContext): 

157 pass 

158 

159 

160class PGCompiler_pg8000(PGCompiler): 

161 def visit_mod_binary(self, binary, operator, **kw): 

162 return ( 

163 self.process(binary.left, **kw) 

164 + " %% " 

165 + self.process(binary.right, **kw) 

166 ) 

167 

168 def post_process_text(self, text): 

169 if "%%" in text: 

170 util.warn( 

171 "The SQLAlchemy postgresql dialect " 

172 "now automatically escapes '%' in text() " 

173 "expressions to '%%'." 

174 ) 

175 return text.replace("%", "%%") 

176 

177 

178class PGIdentifierPreparer_pg8000(PGIdentifierPreparer): 

179 def _escape_identifier(self, value): 

180 value = value.replace(self.escape_quote, self.escape_to_quote) 

181 return value.replace("%", "%%") 

182 

183 

184class PGDialect_pg8000(PGDialect): 

185 driver = "pg8000" 

186 

187 supports_unicode_statements = True 

188 

189 supports_unicode_binds = True 

190 

191 default_paramstyle = "format" 

192 supports_sane_multi_rowcount = True 

193 execution_ctx_cls = PGExecutionContext_pg8000 

194 statement_compiler = PGCompiler_pg8000 

195 preparer = PGIdentifierPreparer_pg8000 

196 description_encoding = "use_encoding" 

197 

198 colspecs = util.update_copy( 

199 PGDialect.colspecs, 

200 { 

201 sqltypes.Numeric: _PGNumericNoBind, 

202 sqltypes.Float: _PGNumeric, 

203 JSON: _PGJSON, 

204 sqltypes.JSON: _PGJSON, 

205 UUID: _PGUUID, 

206 }, 

207 ) 

208 

209 def __init__(self, client_encoding=None, **kwargs): 

210 PGDialect.__init__(self, **kwargs) 

211 self.client_encoding = client_encoding 

212 

213 def initialize(self, connection): 

214 self.supports_sane_multi_rowcount = self._dbapi_version >= (1, 9, 14) 

215 super(PGDialect_pg8000, self).initialize(connection) 

216 

217 @util.memoized_property 

218 def _dbapi_version(self): 

219 if self.dbapi and hasattr(self.dbapi, "__version__"): 

220 return tuple( 

221 [ 

222 int(x) 

223 for x in re.findall( 

224 r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__ 

225 ) 

226 ] 

227 ) 

228 else: 

229 return (99, 99, 99) 

230 

231 @classmethod 

232 def dbapi(cls): 

233 return __import__("pg8000") 

234 

235 def create_connect_args(self, url): 

236 opts = url.translate_connect_args(username="user") 

237 if "port" in opts: 

238 opts["port"] = int(opts["port"]) 

239 opts.update(url.query) 

240 return ([], opts) 

241 

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

243 return "connection is closed" in str(e) 

244 

245 def set_isolation_level(self, connection, level): 

246 level = level.replace("_", " ") 

247 

248 # adjust for ConnectionFairy possibly being present 

249 if hasattr(connection, "connection"): 

250 connection = connection.connection 

251 

252 if level == "AUTOCOMMIT": 

253 connection.autocommit = True 

254 elif level in self._isolation_lookup: 

255 connection.autocommit = False 

256 cursor = connection.cursor() 

257 cursor.execute( 

258 "SET SESSION CHARACTERISTICS AS TRANSACTION " 

259 "ISOLATION LEVEL %s" % level 

260 ) 

261 cursor.execute("COMMIT") 

262 cursor.close() 

263 else: 

264 raise exc.ArgumentError( 

265 "Invalid value '%s' for isolation_level. " 

266 "Valid isolation levels for %s are %s or AUTOCOMMIT" 

267 % (level, self.name, ", ".join(self._isolation_lookup)) 

268 ) 

269 

270 def set_client_encoding(self, connection, client_encoding): 

271 # adjust for ConnectionFairy possibly being present 

272 if hasattr(connection, "connection"): 

273 connection = connection.connection 

274 

275 cursor = connection.cursor() 

276 cursor.execute("SET CLIENT_ENCODING TO '" + client_encoding + "'") 

277 cursor.execute("COMMIT") 

278 cursor.close() 

279 

280 def do_begin_twophase(self, connection, xid): 

281 connection.connection.tpc_begin((0, xid, "")) 

282 

283 def do_prepare_twophase(self, connection, xid): 

284 connection.connection.tpc_prepare() 

285 

286 def do_rollback_twophase( 

287 self, connection, xid, is_prepared=True, recover=False 

288 ): 

289 connection.connection.tpc_rollback((0, xid, "")) 

290 

291 def do_commit_twophase( 

292 self, connection, xid, is_prepared=True, recover=False 

293 ): 

294 connection.connection.tpc_commit((0, xid, "")) 

295 

296 def do_recover_twophase(self, connection): 

297 return [row[1] for row in connection.connection.tpc_recover()] 

298 

299 def on_connect(self): 

300 fns = [] 

301 

302 def on_connect(conn): 

303 conn.py_types[quoted_name] = conn.py_types[util.text_type] 

304 

305 fns.append(on_connect) 

306 

307 if self.client_encoding is not None: 

308 

309 def on_connect(conn): 

310 self.set_client_encoding(conn, self.client_encoding) 

311 

312 fns.append(on_connect) 

313 

314 if self.isolation_level is not None: 

315 

316 def on_connect(conn): 

317 self.set_isolation_level(conn, self.isolation_level) 

318 

319 fns.append(on_connect) 

320 

321 if len(fns) > 0: 

322 

323 def on_connect(conn): 

324 for fn in fns: 

325 fn(conn) 

326 

327 return on_connect 

328 else: 

329 return None 

330 

331 

332dialect = PGDialect_pg8000