自动映射的通用数据模型架构上的SQLAlchemy级联删除

时间:2019-02-06 11:53:16

标签: python postgresql sqlalchemy ofbiz

我有一个来自OfBiz安装的预定义PostgreSQL数据库。该数据库具有许多外键组件。我正在尝试编写一个python程序,以将生产数据库中的数据复制到暂存或开发数据库中。

最后一步是从我的数据集中清除一些开发人员不应该看到的私人数据。

我的反射设置如下:

def _name_for_scalar_relationship(base, local_cls, referred_cls, constraint):
    global baseNum
    if constraint.name:
        baseNum += 1
        disc = '_'.join(col.name for col in constraint.columns)
        return referred_cls.__name__.lower() + '.' + disc + "_scalar_" + str(baseNum)
    # if this didn't work, revert to the default behavior
    return name_for_scalar_relationship(base, local_cls, referred_cls, constraint)

def _name_for_collection_relationship(base, local_cls, referred_cls, constraint):
    global baseNum
    if constraint.name:
        baseNum += 1

        disc = '_'.join(col.name for col in constraint.columns)
        return referred_cls.__name__.lower() + '.' + disc + "_collection_" + str(baseNum)

    # if this didn't work, revert to the default behavior
    return name_for_collection_relationship(base, local_cls, referred_cls, constraint)

def _generate_relationship(base, direction, return_fn, attrname, local_cls, referred_cls, **kw):

    if direction is interfaces.ONETOMANY:
        kw['cascade'] = 'all, delete-orphan'
        kw['passive_deletes'] = True

    return generate_relationship(base, direction, return_fn, attrname, local_cls, referred_cls, **kw)

我的反思设置如下

def getTableList(smeta):
    tableList = []
    if args.tables:
        ##Validate tables are in database
        for table in args.tables:
            if smeta.tables[table] in smeta.sorted_tables:
                tableList.append(str(smeta.tables[table]))
            else:
                log('Table {0} does not exist on source'.format(table))
    else:
        tableList = smeta.sorted_tables

    if args.tables:
        for table in tableList:

            for relationship in getattr(Base.classes,str(table)).__mapper__.relationships:
                #print(relationship)
                tableName = re.search(r'\.(.*)\.', str(relationship)).group(1)
                if tableName and tableName not in tableList:
                    tableList.append(tableName)

    return tableList

我可以使用以下代码查看表之间的关系:

def cleanData():
    log("Clean Data")
    destSession = sessionmaker()
    destSess = destSession(bind=db2)

    for partyId in partyIds:
        log("Cleaning data for {0}".format(partyId))
        voucher = Base.classes.voucher
        invoice = Base.classes.invoice
        voucherDelete = destSess.query(voucher).filter_by(party_id=partyId)
        voucherDelete.delete(synchronize_session=False)
        invoiceDelete = destSess.query(invoice).filter_by(party_id_from=partyId)
        invoiceDelete.delete(synchronize_session=False)
        destSess.commit()

但是我希望是删除代码:

def _generate_relationship(base, direction, return_fn, attrname, local_cls, referred_cls, **kw):
## Write this to include cascade delete see: https://docs.sqlalchemy.org/en/latest/orm/extensions/automap.html#custom-relationship-arguments
if direction is interfaces.ONETOMANY or direction is interfaces.MANYTOMANY:
    kw['cascade'] = 'all, delete, delete-orphan'
    kw['passive_deletes'] = False

if direction is interfaces.MANYTOONE:
    kw['viewonly'] = True

return generate_relationship(base, direction, return_fn, attrname, local_cls, referred_cls, **kw)

删除操作确实会删除凭证和发票,但不会删除子invoice_item记录。

我的数据库设置不包含外键的级联删除功能,但我希望可以由ORM提供该功能。

理想情况下,此代码将删除凭证或发票的子代。

编辑

新的关系生成如下:

voucher = Base.classes.voucher
    invoice = Base.classes.invoice
    invoiceDelete = destSess.query(invoice).filter_by(party_id_from=partyId)
    rs = invoiceDelete.all()
    for result in rs:
        destSess.delete(result)
    voucherDelete = destSess.query(voucher).filter_by(party_id=partyId)
    rs = voucherDelete.all()
    for result in rs:
        destSess.delete(result)
    destSess.commit()

删除代码更改为:

sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) update or delete on table "invoice_item" violates foreign key constraint "invoice_imat_itm" on table "invoice_item_attribute"
DETAIL:  Key (invoice_id, invoice_item_seq_id)=(19439, 00001) is still referenced from table "invoice_item_attribute".
 [SQL: 'DELETE FROM invoice_item WHERE invoice_item.invoice_id = %(invoice_id)s AND invoice_item.invoice_item_seq_id = %(invoice_item_seq_id)s'] [parameters: ({'invoice_id': '19439', 'invoice_item_seq_id': '00001'}, {'invoice_id': '33674', 'invoice_item_seq_id': '00001'}, {'invoice_id': '49384', 'invoice_item_seq_id': '00001'}, {'invoice_id': '58135', 'invoice_item_seq_id': '00001'}, {'invoice_id': '83457', 'invoice_item_seq_id': '00001'})] (Background on this error at: http://sqlalche.me/e/gkpj)

这会导致以下错误:

{{1}}

1 个答案:

答案 0 :(得分:1)

以下配置使我可以删除数据库中“凭单”和“发票”的子记录。

关系自动映射器设置如下:

def _generate_relationship(base, direction, return_fn, attrname, local_cls, referred_cls, **kw):
    ## Write this to include cascade delete see: https://docs.sqlalchemy.org/en/latest/orm/extensions/automap.html#custom-relationship-arguments
    if direction is interfaces.ONETOMANY:
        kw['cascade'] = 'all, delete, delete-orphan'
        kw['passive_deletes'] = False
        kw['lazy'] = 'immediate'
    if direction is interfaces.MANYTOONE or direction is interfaces.MANYTOMANY:
        kw['viewonly'] = True

    return generate_relationship(base, direction, return_fn, attrname, local_cls, referred_cls, **kw)

删除代码为:

def cleanData():
    for partyId in partyIds:
        log("Cleaning data for {0}".format(partyId))

        invoice = Base.classes.invoice
        invoiceDelete = destSess.query(invoice).filter_by(party_id_from=partyId)
        rs = invoiceDelete.all()
        for result in rs:
            deleteChildren(result, destSess)
            destSess.delete(result)
        destSess.commit()

        voucher = Base.classes.voucher
        voucherDelete = destSess.query(voucher).filter_by(party_id=partyId)
        rs = voucherDelete.all()
        for result in rs:
            deleteChildren(result, destSess)        
            destSess.delete(result)

        destSess.commit()

def deleteChildren(result, destSess):
    for relationship in result.__mapper__.relationships:
        if relationship.direction is interfaces.ONETOMANY:
            childs = getattr(result, str(re.search(r'\.(.*)', str(relationship)).group(1)))
            for child in childs:
                if child.__mapper__.relationships:
                    deleteChildren(child, destSess)
                    destSess.commit()
                destSess.delete(child)
                destSess.commit()

为说明起见,我获得了要从数据库中删除的与我的聚会ID相关的记录,并使用递归方法使用渴望获取的方法从定义的关系中获得孩子。如果该子记录中有子记录,则调用相同的方法。当我要遵循的一对多关系用尽时,我将删除记录并返回其父项,同时删除该记录。