How to explicitly cast type of array literal in sqlalchemy using postgresql?

Question:

After attempting to cast a literal arrays type via cast, type_coerce, and type_ and not having any success thought I would ask.

from pprint import pprint

from sqlalchemy import String, null, Integer, Column, ForeignKey, 
    create_engine
from sqlalchemy.dialects.postgresql import array
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import aliased, sessionmaker, relationship

Base = declarative_base()

temp_db_name = 'cf_LlAcKpxFHzOW'
engine = create_engine('postgresql://localhost/{}'.format(temp_db_name))


class JobGroup(Base):
    __tablename__ = 'job_group'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    parent_id = Column(Integer, ForeignKey("job_group.id"))
    parent = relationship("JobGroup", remote_side=[id])

    def __init__(self, name, parent=None):
        self.name = name
        self.parent = parent


    def __repr__(self):
        return "JobGroup {} {}".format(self.id, self.name)

Base.metadata.create_all(bind=engine)

Session = sessionmaker()
Session.configure(bind=engine)

session = Session()

gp_group = JobGroup(name="grandpa")
p_group = JobGroup(name="parent", parent=gp_group)
c_group = JobGroup(name="child", parent=p_group)

session.add(gp_group)
session.add(p_group)
session.add(c_group)
session.commit()
session.refresh(gp_group)
session.refresh(p_group)
session.refresh(c_group)

# for jg in session.query(JobGroup).all():
#     pprint(jg.__dict__)

try:
    tree_parts = session.query(
        JobGroup.id,
        JobGroup.name,
        JobGroup.parent_id,
        array([]).label("ancestors")
    ).filter(
        JobGroup.parent_id == null()
    ).cte(name="tree_parts", recursive=True)

    jg_alias = aliased(JobGroup, name="jg")
    tree_parts_alias = aliased(tree_parts, name="tp")

    tree_parts = tree_parts.union_all(
        session.query(
            jg_alias.id,
            jg_alias.name,
            jg_alias.parent_id,
            (tree_parts_alias.c.ancestors + 
             array([jg_alias.parent_id])).label("ancestors")
        ).filter(jg_alias.parent_id == tree_parts_alias.c.id)
    )
    pprint(session.query(tree_parts).all())
finally:
    session.rollback()
    session.close_all()
    Base.metadata.drop_all(bind=engine)

This results in the postgres error:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) cannot determine type of empty array
LINE 2: ...p.name AS name, job_group.parent_id AS parent_id, ARRAY[] AS...
                                                              ^
HINT:  Explicitly cast to the desired type, for example ARRAY[]::integer[].

There are ways to work around this for this use case, such as pre-populating the ancestors array literal with an integer that is an invalid parent_id, like -1.

Asked By: Coke Fiend

||

Answers:

Casting is very straightforward:

from sqlalchemy import cast, Integer
from sqlalchemy.dialects.postgresql import array, ARRAY

cast(array([]), ARRAY(Integer))

Without seeing what you’ve tried, I can only speculate that you tried to cast to array(Integer) instead of ARRAY(Integer).

Answered By: univerio