]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
initial support for JSONB - this only allows you to define the JSONB
authorDamian Dimmich <damian@tauri-tec.com>
Tue, 24 Jun 2014 07:23:21 +0000 (11:23 +0400)
committerDamian Dimmich <damian@tauri-tec.com>
Tue, 24 Jun 2014 07:23:21 +0000 (11:23 +0400)
datatype - this does not add any of the additional support for
querying/indexing yet.

lib/sqlalchemy/dialects/postgresql/__init__.py
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/dialects/postgresql/json.py

index d1c768186d014d8727fccd0996de3d2a7e87eefd..1ed43b635194f8e3351478e3843df4a3d0418fd2 100644 (file)
@@ -15,7 +15,7 @@ from .base import \
     TSVECTOR
 from .constraints import ExcludeConstraint
 from .hstore import HSTORE, hstore
-from .json import JSON, JSONElement
+from .json import JSON, JSONElement, JSONB
 from .ranges import INT4RANGE, INT8RANGE, NUMRANGE, DATERANGE, TSRANGE, \
     TSTZRANGE
 
@@ -25,5 +25,5 @@ __all__ = (
     'DOUBLE_PRECISION', 'TIMESTAMP', 'TIME', 'DATE', 'BYTEA', 'BOOLEAN',
     'INTERVAL', 'ARRAY', 'ENUM', 'dialect', 'Any', 'All', 'array', 'HSTORE',
     'hstore', 'INT4RANGE', 'INT8RANGE', 'NUMRANGE', 'DATERANGE',
-    'TSRANGE', 'TSTZRANGE', 'json', 'JSON', 'JSONElement'
+    'TSRANGE', 'TSTZRANGE', 'json', 'JSON', 'JSONB', 'JSONElement'
 )
index 35f3352529000927be759b1db181b3f3c6deb0a3..6c4031b018d34900684f6d1a3d91dd7b1c688e80 100644 (file)
@@ -1384,6 +1384,9 @@ class PGTypeCompiler(compiler.GenericTypeCompiler):
     def visit_JSON(self, type_):
         return "JSON"
 
+    def visit_JSONB(self, type_):
+        return "JSONB"
+
     def visit_INT4RANGE(self, type_):
         return "INT4RANGE"
 
index 2e29185e8ac3fcb9fe896f1d1268a458ec6651c4..37196dfb18c65f5d417415135f435da161cd7763 100644 (file)
@@ -14,7 +14,7 @@ from ... import sql
 from ...sql import elements
 from ... import util
 
-__all__ = ('JSON', 'JSONElement')
+__all__ = ('JSON', 'JSONElement', 'JSONB')
 
 
 class JSONElement(elements.BinaryExpression):
@@ -197,3 +197,114 @@ class JSON(sqltypes.TypeEngine):
 
 
 ischema_names['json'] = JSON
+
+
+
+class JSONB(sqltypes.TypeEngine):
+    """Represent the Postgresql JSONB type.
+
+    The :class:`.JSONB` type stores arbitrary JSONB format data, e.g.::
+
+        data_table = Table('data_table', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('data', JSONB)
+        )
+
+        with engine.connect() as conn:
+            conn.execute(
+                data_table.insert(),
+                data = {"key1": "value1", "key2": "value2"}
+            )
+
+    :class:`.JSONB` provides several operations:
+
+    * Index operations::
+
+        data_table.c.data['some key']
+
+    * Index operations returning text (required for text comparison)::
+
+        data_table.c.data['some key'].astext == 'some value'
+
+    * Index operations with a built-in CAST call::
+
+        data_table.c.data['some key'].cast(Integer) == 5
+
+    * Path index operations::
+
+        data_table.c.data[('key_1', 'key_2', ..., 'key_n')]
+
+    * Path index operations returning text (required for text comparison)::
+
+        data_table.c.data[('key_1', 'key_2', ..., 'key_n')].astext == 'some value'
+
+    Index operations return an instance of :class:`.JSONElement`, which represents
+    an expression such as ``column -> index``.  This element then defines
+    methods such as :attr:`.JSONElement.astext` and :meth:`.JSONElement.cast`
+    for setting up type behavior.
+
+    The :class:`.JSON` type, when used with the SQLAlchemy ORM, does not detect
+    in-place mutations to the structure.  In order to detect these, the
+    :mod:`sqlalchemy.ext.mutable` extension must be used.  This extension will
+    allow "in-place" changes to the datastructure to produce events which
+    will be detected by the unit of work.  See the example at :class:`.HSTORE`
+    for a simple example involving a dictionary.
+
+    Custom serializers and deserializers are specified at the dialect level,
+    that is using :func:`.create_engine`.  The reason for this is that when
+    using psycopg2, the DBAPI only allows serializers at the per-cursor
+    or per-connection level.   E.g.::
+
+        engine = create_engine("postgresql://scott:tiger@localhost/test",
+                                json_serializer=my_serialize_fn,
+                                json_deserializer=my_deserialize_fn
+                        )
+
+    When using the psycopg2 dialect, the json_deserializer is registered
+    against the database using ``psycopg2.extras.register_default_json``.
+
+    .. versionadded:: 0.9.7
+
+    """
+
+    __visit_name__ = 'JSONB'
+
+    class comparator_factory(sqltypes.Concatenable.Comparator):
+        """Define comparison operations for :class:`.JSON`."""
+
+        def __getitem__(self, other):
+            """Get the value at a given key."""
+
+            return JSONElement(self.expr, other)
+
+        def _adapt_expression(self, op, other_comparator):
+            if isinstance(op, custom_op):
+                if op.opstring == '->':
+                    return op, sqltypes.Text
+            return sqltypes.Concatenable.Comparator.\
+                _adapt_expression(self, op, other_comparator)
+
+    def bind_processor(self, dialect):
+        json_serializer = dialect._json_serializer or json.dumps
+        if util.py2k:
+            encoding = dialect.encoding
+            def process(value):
+                return json_serializer(value).encode(encoding)
+        else:
+            def process(value):
+                return json_serializer(value)
+        return process
+
+    def result_processor(self, dialect, coltype):
+        json_deserializer = dialect._json_deserializer or json.loads
+        if util.py2k:
+            encoding = dialect.encoding
+            def process(value):
+                return json_deserializer(value.decode(encoding))
+        else:
+            def process(value):
+                return json_deserializer(value)
+        return process
+
+
+ischema_names['jsonb'] = JSONB
\ No newline at end of file