From 9f986ce10c6755af3f347a56f9ea03e0e2c5943e Mon Sep 17 00:00:00 2001 From: Sebastian Bank Date: Mon, 2 Apr 2018 11:25:08 -0400 Subject: [PATCH] Add postgresl.REGCLASS type for casting table names to OIDs and vice versa Fixes: #4160 Change-Id: Id0bdbad1be3a0950dc8f35895ee13d9264244722 Pull-request: https://github.com/zzzeek/sqlalchemy/pull/435 --- doc/build/changelog/unreleased_12/4160.rst | 7 ++++ .../dialects/postgresql/__init__.py | 6 +-- lib/sqlalchemy/dialects/postgresql/base.py | 14 +++++++ test/dialect/postgresql/test_types.py | 40 +++++++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 doc/build/changelog/unreleased_12/4160.rst diff --git a/doc/build/changelog/unreleased_12/4160.rst b/doc/build/changelog/unreleased_12/4160.rst new file mode 100644 index 0000000000..859720c6f4 --- /dev/null +++ b/doc/build/changelog/unreleased_12/4160.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: feature, postgresql + :tickets: 4160 + :versions: 1.3.0b1 + + Added new PG type :class:`.postgresql.REGCLASS` which assists in casting + table names to OID values. Pull request courtesy Sebastian Bank. diff --git a/lib/sqlalchemy/dialects/postgresql/__init__.py b/lib/sqlalchemy/dialects/postgresql/__init__.py index a985f06a91..d2f8057b67 100644 --- a/lib/sqlalchemy/dialects/postgresql/__init__.py +++ b/lib/sqlalchemy/dialects/postgresql/__init__.py @@ -10,7 +10,7 @@ from . import base, psycopg2, pg8000, pypostgresql, pygresql, \ from .base import \ INTEGER, BIGINT, SMALLINT, VARCHAR, CHAR, TEXT, NUMERIC, FLOAT, REAL, \ - INET, CIDR, UUID, BIT, MACADDR, MONEY, OID, DOUBLE_PRECISION, \ + INET, CIDR, UUID, BIT, MACADDR, MONEY, OID, REGCLASS, DOUBLE_PRECISION, \ TIMESTAMP, TIME, DATE, BYTEA, BOOLEAN, INTERVAL, ENUM, TSVECTOR, \ DropEnumType, CreateEnumType from .hstore import HSTORE, hstore @@ -28,8 +28,8 @@ base.dialect = dialect = psycopg2.dialect __all__ = ( 'INTEGER', 'BIGINT', 'SMALLINT', 'VARCHAR', 'CHAR', 'TEXT', 'NUMERIC', 'FLOAT', 'REAL', 'INET', 'CIDR', 'UUID', 'BIT', 'MACADDR', 'MONEY', 'OID', - 'DOUBLE_PRECISION', 'TIMESTAMP', 'TIME', 'DATE', 'BYTEA', 'BOOLEAN', - 'INTERVAL', 'ARRAY', 'ENUM', 'dialect', 'array', 'HSTORE', + 'REGCLASS', 'DOUBLE_PRECISION', 'TIMESTAMP', 'TIME', 'DATE', 'BYTEA', + 'BOOLEAN', 'INTERVAL', 'ARRAY', 'ENUM', 'dialect', 'array', 'HSTORE', 'hstore', 'INT4RANGE', 'INT8RANGE', 'NUMRANGE', 'DATERANGE', 'TSRANGE', 'TSTZRANGE', 'json', 'JSON', 'JSONB', 'Any', 'All', 'DropEnumType', 'CreateEnumType', 'ExcludeConstraint', diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 1dffe8db93..c5b0db6ce5 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -1000,6 +1000,16 @@ class OID(sqltypes.TypeEngine): __visit_name__ = "OID" +class REGCLASS(sqltypes.TypeEngine): + + """Provide the PostgreSQL REGCLASS type. + + .. versionadded:: 1.2.7 + + """ + __visit_name__ = "REGCLASS" + + class TIMESTAMP(sqltypes.TIMESTAMP): def __init__(self, timezone=False, precision=None): @@ -1382,6 +1392,7 @@ ischema_names = { 'macaddr': MACADDR, 'money': MONEY, 'oid': OID, + 'regclass': REGCLASS, 'double precision': DOUBLE_PRECISION, 'timestamp': TIMESTAMP, 'timestamp with time zone': TIMESTAMP, @@ -1876,6 +1887,9 @@ class PGTypeCompiler(compiler.GenericTypeCompiler): def visit_OID(self, type_, **kw): return "OID" + def visit_REGCLASS(self, type_, **kw): + return "REGCLASS" + def visit_FLOAT(self, type_, **kw): if not type_.precision: return "FLOAT" diff --git a/test/dialect/postgresql/test_types.py b/test/dialect/postgresql/test_types.py index dea17bc2a9..8aa9d1b1f5 100644 --- a/test/dialect/postgresql/test_types.py +++ b/test/dialect/postgresql/test_types.py @@ -630,6 +630,46 @@ class OIDTest(fixtures.TestBase): assert isinstance(t2.c.y.type, postgresql.OID) +class RegClassTest(fixtures.TestBase): + __only_on__ = 'postgresql' + __backend__ = True + + @staticmethod + def _scalar(expression): + with testing.db.connect() as conn: + return conn.scalar(select([expression])) + + def test_cast_name(self): + eq_( + self._scalar(cast('pg_class', postgresql.REGCLASS)), + 'pg_class' + ) + + def test_cast_path(self): + eq_( + self._scalar(cast('pg_catalog.pg_class', postgresql.REGCLASS)), + 'pg_class' + ) + + def test_cast_oid(self): + regclass = cast('pg_class', postgresql.REGCLASS) + oid = self._scalar(cast(regclass, postgresql.OID)) + assert isinstance(oid, int) + eq_(self._scalar(cast(oid, postgresql.REGCLASS)), 'pg_class') + + def test_cast_whereclause(self): + pga = Table('pg_attribute', MetaData(testing.db), + Column('attrelid', postgresql.OID), + Column('attname', String(64))) + with testing.db.connect() as conn: + oid = conn.scalar( + select([pga.c.attrelid]).where( + pga.c.attrelid == cast('pg_class', postgresql.REGCLASS) + ) + ) + assert isinstance(oid, int) + + class NumericInterpretationTest(fixtures.TestBase): __only_on__ = 'postgresql' __backend__ = True -- 2.47.2