################################################################################ Copyright (c) 2009 Zope Foundation and Contributors.# All Rights Reserved.## This software is subject to the provisions of the Zope Public License,# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS# FOR A PARTICULAR PURPOSE.###############################################################################"""Batch table row insert/delete support."""from__future__importabsolute_importimportrefromrelstorage._compatimportlist_valuesfrom..batchimportRowBatcheroracle_rowvar_re=re.compile(r":([a-zA-Z0-9_]+)")
[docs]classOracleRowBatcher(RowBatcher):""" Oracle-specific row batcher. Expects :name parameters and a dictionary for each row. """insert_placeholder=delete_placeholder=':1'# ORA-01795: maximum number of expressions in a list is 1000row_limit=999def__init__(self,cursor,inputsizes,row_limit=None):super().__init__(cursor,row_limit)self.inputsizes=inputsizesself.array_ops={}# {(operation, row_schema): {rowkey: [row]}}def_do_inserts(self):# pylint:disable=too-many-localsdefreplace_var(match):name=match.group(1)new_name='%s_%d'%(name,rownum)# pylint:disable=used-before-assignmentifnameinself.inputsizes:stmt_inputsizes[new_name]=self.inputsizes[name]params[new_name]=row[name]# pylint:disable=possibly-used-before-assignmentreturn':%s'%new_nameitems=sorted(self.inserts.items())for(_command,header,row_schema,_),rowsinitems:stmt_inputsizes={}iflen(rows)==1:# use the single insert syntaxrow=list_values(rows)[0]stmt="INSERT INTO %s VALUES (%s)"%(header,row_schema)fornameinself.inputsizes:ifnameinrow:stmt_inputsizes[name]=self.inputsizes[name]ifstmt_inputsizes:self.cursor.setinputsizes(**stmt_inputsizes)self.cursor.execute(stmt,row)else:# use the multi-insert syntaxparts=[]params={}forrownum,rowinenumerate(rows.values()):mod_row=oracle_rowvar_re.sub(replace_var,row_schema)parts.append("INTO %s VALUES (%s)"%(header,mod_row))stmt="INSERT ALL\n%s\nSELECT * FROM DUAL"%'\n'.join(parts)ifstmt_inputsizes:self.cursor.setinputsizes(**stmt_inputsizes)self.cursor.execute(stmt,params)defadd_array_op(self,operation,row_schema,row,rowkey,size):key=(operation,row_schema)rows=self.array_ops.get(key)ifrowsisNone:self.array_ops[key]=rows={}rows[rowkey]=row# note that this may replace a rowself.rows_added+=1self.size_added+=sizeif(self.rows_added>=self.row_limitorself.size_added>=self.size_limit):self.flush()
def_do_array_ops(self):items=sorted(self.array_ops.items())for(operation,row_schema),rowsinitems:r=list_values(rows)params=[]datatypes=[self.inputsizes[name]fornameinrow_schema.split()]fori,columninenumerate(zip(*r)):params.append(self.cursor.arrayvar(datatypes[i],list(column)))self.cursor.execute(operation,tuple(params))# TODO: Can we override _make_single_column_query() to pass# an array to the database for use in a sql statement?# We'd need the TABLE operator, probably. How far back is that supported?def_make_placeholder_list_of_length(self,count):return','.join((':%d'%iforiinrange(count)))