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/json.py 

2# Copyright (C) 2005-2020 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 

7from __future__ import absolute_import 

8 

9from ... import types as sqltypes 

10from ... import util 

11from ...sql import operators 

12 

13 

14__all__ = ("JSON", "JSONB") 

15 

16idx_precedence = operators._PRECEDENCE[operators.json_getitem_op] 

17 

18ASTEXT = operators.custom_op( 

19 "->>", 

20 precedence=idx_precedence, 

21 natural_self_precedent=True, 

22 eager_grouping=True, 

23) 

24 

25JSONPATH_ASTEXT = operators.custom_op( 

26 "#>>", 

27 precedence=idx_precedence, 

28 natural_self_precedent=True, 

29 eager_grouping=True, 

30) 

31 

32 

33HAS_KEY = operators.custom_op( 

34 "?", 

35 precedence=idx_precedence, 

36 natural_self_precedent=True, 

37 eager_grouping=True, 

38) 

39 

40HAS_ALL = operators.custom_op( 

41 "?&", 

42 precedence=idx_precedence, 

43 natural_self_precedent=True, 

44 eager_grouping=True, 

45) 

46 

47HAS_ANY = operators.custom_op( 

48 "?|", 

49 precedence=idx_precedence, 

50 natural_self_precedent=True, 

51 eager_grouping=True, 

52) 

53 

54CONTAINS = operators.custom_op( 

55 "@>", 

56 precedence=idx_precedence, 

57 natural_self_precedent=True, 

58 eager_grouping=True, 

59) 

60 

61CONTAINED_BY = operators.custom_op( 

62 "<@", 

63 precedence=idx_precedence, 

64 natural_self_precedent=True, 

65 eager_grouping=True, 

66) 

67 

68 

69class JSONPathType(sqltypes.JSON.JSONPathType): 

70 def bind_processor(self, dialect): 

71 super_proc = self.string_bind_processor(dialect) 

72 

73 def process(value): 

74 assert isinstance(value, util.collections_abc.Sequence) 

75 tokens = [util.text_type(elem) for elem in value] 

76 value = "{%s}" % (", ".join(tokens)) 

77 if super_proc: 

78 value = super_proc(value) 

79 return value 

80 

81 return process 

82 

83 def literal_processor(self, dialect): 

84 super_proc = self.string_literal_processor(dialect) 

85 

86 def process(value): 

87 assert isinstance(value, util.collections_abc.Sequence) 

88 tokens = [util.text_type(elem) for elem in value] 

89 value = "{%s}" % (", ".join(tokens)) 

90 if super_proc: 

91 value = super_proc(value) 

92 return value 

93 

94 return process 

95 

96 

97class JSON(sqltypes.JSON): 

98 """Represent the PostgreSQL JSON type. 

99 

100 This type is a specialization of the Core-level :class:`_types.JSON` 

101 type. Be sure to read the documentation for :class:`_types.JSON` for 

102 important tips regarding treatment of NULL values and ORM use. 

103 

104 .. versionchanged:: 1.1 :class:`_postgresql.JSON` is now a PostgreSQL- 

105 specific specialization of the new :class:`_types.JSON` type. 

106 

107 The operators provided by the PostgreSQL version of :class:`_types.JSON` 

108 include: 

109 

110 * Index operations (the ``->`` operator):: 

111 

112 data_table.c.data['some key'] 

113 

114 data_table.c.data[5] 

115 

116 

117 * Index operations returning text (the ``->>`` operator):: 

118 

119 data_table.c.data['some key'].astext == 'some value' 

120 

121 Note that equivalent functionality is available via the 

122 :attr:`.JSON.Comparator.as_string` accessor. 

123 

124 * Index operations with CAST 

125 (equivalent to ``CAST(col ->> ['some key'] AS <type>)``):: 

126 

127 data_table.c.data['some key'].astext.cast(Integer) == 5 

128 

129 Note that equivalent functionality is available via the 

130 :attr:`.JSON.Comparator.as_integer` and similar accessors. 

131 

132 * Path index operations (the ``#>`` operator):: 

133 

134 data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')] 

135 

136 * Path index operations returning text (the ``#>>`` operator):: 

137 

138 data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')].astext == 'some value' 

139 

140 .. versionchanged:: 1.1 The :meth:`_expression.ColumnElement.cast` 

141 operator on 

142 JSON objects now requires that the :attr:`.JSON.Comparator.astext` 

143 modifier be called explicitly, if the cast works only from a textual 

144 string. 

145 

146 Index operations return an expression object whose type defaults to 

147 :class:`_types.JSON` by default, 

148 so that further JSON-oriented instructions 

149 may be called upon the result type. 

150 

151 Custom serializers and deserializers are specified at the dialect level, 

152 that is using :func:`_sa.create_engine`. The reason for this is that when 

153 using psycopg2, the DBAPI only allows serializers at the per-cursor 

154 or per-connection level. E.g.:: 

155 

156 engine = create_engine("postgresql://scott:tiger@localhost/test", 

157 json_serializer=my_serialize_fn, 

158 json_deserializer=my_deserialize_fn 

159 ) 

160 

161 When using the psycopg2 dialect, the json_deserializer is registered 

162 against the database using ``psycopg2.extras.register_default_json``. 

163 

164 .. seealso:: 

165 

166 :class:`_types.JSON` - Core level JSON type 

167 

168 :class:`_postgresql.JSONB` 

169 

170 """ # noqa 

171 

172 astext_type = sqltypes.Text() 

173 

174 def __init__(self, none_as_null=False, astext_type=None): 

175 """Construct a :class:`_types.JSON` type. 

176 

177 :param none_as_null: if True, persist the value ``None`` as a 

178 SQL NULL value, not the JSON encoding of ``null``. Note that 

179 when this flag is False, the :func:`.null` construct can still 

180 be used to persist a NULL value:: 

181 

182 from sqlalchemy import null 

183 conn.execute(table.insert(), data=null()) 

184 

185 .. versionchanged:: 0.9.8 - Added ``none_as_null``, and :func:`.null` 

186 is now supported in order to persist a NULL value. 

187 

188 .. seealso:: 

189 

190 :attr:`_types.JSON.NULL` 

191 

192 :param astext_type: the type to use for the 

193 :attr:`.JSON.Comparator.astext` 

194 accessor on indexed attributes. Defaults to :class:`_types.Text`. 

195 

196 .. versionadded:: 1.1 

197 

198 """ 

199 super(JSON, self).__init__(none_as_null=none_as_null) 

200 if astext_type is not None: 

201 self.astext_type = astext_type 

202 

203 class Comparator(sqltypes.JSON.Comparator): 

204 """Define comparison operations for :class:`_types.JSON`.""" 

205 

206 @property 

207 def astext(self): 

208 """On an indexed expression, use the "astext" (e.g. "->>") 

209 conversion when rendered in SQL. 

210 

211 E.g.:: 

212 

213 select([data_table.c.data['some key'].astext]) 

214 

215 .. seealso:: 

216 

217 :meth:`_expression.ColumnElement.cast` 

218 

219 """ 

220 if isinstance(self.expr.right.type, sqltypes.JSON.JSONPathType): 

221 return self.expr.left.operate( 

222 JSONPATH_ASTEXT, 

223 self.expr.right, 

224 result_type=self.type.astext_type, 

225 ) 

226 else: 

227 return self.expr.left.operate( 

228 ASTEXT, self.expr.right, result_type=self.type.astext_type 

229 ) 

230 

231 comparator_factory = Comparator 

232 

233 

234class JSONB(JSON): 

235 """Represent the PostgreSQL JSONB type. 

236 

237 The :class:`_postgresql.JSONB` type stores arbitrary JSONB format data, e. 

238 g.:: 

239 

240 data_table = Table('data_table', metadata, 

241 Column('id', Integer, primary_key=True), 

242 Column('data', JSONB) 

243 ) 

244 

245 with engine.connect() as conn: 

246 conn.execute( 

247 data_table.insert(), 

248 data = {"key1": "value1", "key2": "value2"} 

249 ) 

250 

251 The :class:`_postgresql.JSONB` type includes all operations provided by 

252 :class:`_types.JSON`, including the same behaviors for indexing operations 

253 . 

254 It also adds additional operators specific to JSONB, including 

255 :meth:`.JSONB.Comparator.has_key`, :meth:`.JSONB.Comparator.has_all`, 

256 :meth:`.JSONB.Comparator.has_any`, :meth:`.JSONB.Comparator.contains`, 

257 and :meth:`.JSONB.Comparator.contained_by`. 

258 

259 Like the :class:`_types.JSON` type, the :class:`_postgresql.JSONB` 

260 type does not detect 

261 in-place changes when used with the ORM, unless the 

262 :mod:`sqlalchemy.ext.mutable` extension is used. 

263 

264 Custom serializers and deserializers 

265 are shared with the :class:`_types.JSON` class, 

266 using the ``json_serializer`` 

267 and ``json_deserializer`` keyword arguments. These must be specified 

268 at the dialect level using :func:`_sa.create_engine`. When using 

269 psycopg2, the serializers are associated with the jsonb type using 

270 ``psycopg2.extras.register_default_jsonb`` on a per-connection basis, 

271 in the same way that ``psycopg2.extras.register_default_json`` is used 

272 to register these handlers with the json type. 

273 

274 .. versionadded:: 0.9.7 

275 

276 .. seealso:: 

277 

278 :class:`_types.JSON` 

279 

280 """ 

281 

282 __visit_name__ = "JSONB" 

283 

284 class Comparator(JSON.Comparator): 

285 """Define comparison operations for :class:`_types.JSON`.""" 

286 

287 def has_key(self, other): 

288 """Boolean expression. Test for presence of a key. Note that the 

289 key may be a SQLA expression. 

290 """ 

291 return self.operate(HAS_KEY, other, result_type=sqltypes.Boolean) 

292 

293 def has_all(self, other): 

294 """Boolean expression. Test for presence of all keys in jsonb 

295 """ 

296 return self.operate(HAS_ALL, other, result_type=sqltypes.Boolean) 

297 

298 def has_any(self, other): 

299 """Boolean expression. Test for presence of any key in jsonb 

300 """ 

301 return self.operate(HAS_ANY, other, result_type=sqltypes.Boolean) 

302 

303 def contains(self, other, **kwargs): 

304 """Boolean expression. Test if keys (or array) are a superset 

305 of/contained the keys of the argument jsonb expression. 

306 """ 

307 return self.operate(CONTAINS, other, result_type=sqltypes.Boolean) 

308 

309 def contained_by(self, other): 

310 """Boolean expression. Test if keys are a proper subset of the 

311 keys of the argument jsonb expression. 

312 """ 

313 return self.operate( 

314 CONTAINED_BY, other, result_type=sqltypes.Boolean 

315 ) 

316 

317 comparator_factory = Comparator