|
|
| version 1.14, 2007/07/31 11:28:48 | version 1.15, 2007/08/09 15:09:27 |
|---|---|
| Line 19 except: | Line 19 except: |
| fm_ns = 'http://www.filemaker.com/fmpxmlresult' | fm_ns = 'http://www.filemaker.com/fmpxmlresult' |
| version_string = "V0.4 ROC 29.3.2007" | version_string = "V0.4.1 ROC 9.8.2007" |
| def getTextFromNode(nodename): | def getTextFromNode(nodename): |
| """get the cdata content of a node""" | """get the cdata content of a node""" |
| Line 42 def sql_quote(v): | Line 42 def sql_quote(v): |
| def SimpleSearch(curs,query, args=None, ascii=False): | def SimpleSearch(curs,query, args=None, ascii=False): |
| """execute sql query and return data""" | """execute sql query and return data""" |
| #logging.debug("executing: "+query) | #logger.debug("executing: "+query) |
| if ascii: | if ascii: |
| # encode all in UTF-8 | # encode all in UTF-8 |
| query = query.encode("UTF-8") | query = query.encode("UTF-8") |
| Line 56 def SimpleSearch(curs,query, args=None, | Line 56 def SimpleSearch(curs,query, args=None, |
| args = encargs | args = encargs |
| curs.execute(query, args) | curs.execute(query, args) |
| #logging.debug("sql done") | #logger.debug("sql done") |
| try: | try: |
| return curs.fetchall() | return curs.fetchall() |
| except: | except: |
| Line 99 class xml_handler: | Line 99 class xml_handler: |
| @param options.ascii_db: (optional) assume ascii encoding in db | @param options.ascii_db: (optional) assume ascii encoding in db |
| @param options.replace_table: (optional) delete and re-insert data | @param options.replace_table: (optional) delete and re-insert data |
| @param options.backup_table: (optional) create backup of old table (breaks indices) | @param options.backup_table: (optional) create backup of old table (breaks indices) |
| @param options.use_logger_instance: (optional) use this instance of a logger | |
| """ | """ |
| # set up logger | |
| if hasattr(options, 'use_logger_instance'): | |
| self.logger = options.use_logger_instance | |
| else: | |
| self.logger = logging.getLogger('db.import.fmpxml') | |
| # set up parser | # set up parser |
| self.event = None | self.event = None |
| self.top_dispatcher = { | self.top_dispatcher = { |
| Line 125 class xml_handler: | Line 133 class xml_handler: |
| self.replace_table = getattr(options,"replace_table",None) | self.replace_table = getattr(options,"replace_table",None) |
| self.backup_table = getattr(options,"backup_table",None) | self.backup_table = getattr(options,"backup_table",None) |
| logging.debug("dsn: "+repr(getattr(options,"dsn",None))) | self.logger.debug("dsn: "+repr(getattr(options,"dsn",None))) |
| logging.debug("table: "+repr(self.table)) | self.logger.debug("table: "+repr(self.table)) |
| logging.debug("update_fields: "+repr(self.update_fields)) | self.logger.debug("update_fields: "+repr(self.update_fields)) |
| logging.debug("id_field: "+repr(self.id_field)) | self.logger.debug("id_field: "+repr(self.id_field)) |
| logging.debug("sync_mode: "+repr(self.sync_mode)) | self.logger.debug("sync_mode: "+repr(self.sync_mode)) |
| logging.debug("lc_names: "+repr(self.lc_names)) | self.logger.debug("lc_names: "+repr(self.lc_names)) |
| logging.debug("keep_fields: "+repr(self.keep_fields)) | self.logger.debug("keep_fields: "+repr(self.keep_fields)) |
| logging.debug("ascii_db: "+repr(self.ascii_db)) | self.logger.debug("ascii_db: "+repr(self.ascii_db)) |
| logging.debug("replace_table: "+repr(self.replace_table)) | self.logger.debug("replace_table: "+repr(self.replace_table)) |
| logging.debug("backup_table: "+repr(self.backup_table)) | self.logger.debug("backup_table: "+repr(self.backup_table)) |
| self.dbIDs = {} | self.dbIDs = {} |
| self.rowcnt = 0 | self.rowcnt = 0 |
| Line 147 class xml_handler: | Line 155 class xml_handler: |
| self.dbIDs[id[0]] = 0; | self.dbIDs[id[0]] = 0; |
| self.rowcnt += 1 | self.rowcnt += 1 |
| logging.info("%d entries in DB to sync"%self.rowcnt) | self.logger.info("%d entries in DB to sync"%self.rowcnt) |
| # names of fields in XML file | # names of fields in XML file |
| self.xml_field_names = [] | self.xml_field_names = [] |
| Line 165 class xml_handler: | Line 173 class xml_handler: |
| } | } |
| #First round through the generator corresponds to the | #First round through the generator corresponds to the |
| #start element event | #start element event |
| logging.debug("START METADATA") | self.logger.info("reading metadata...") |
| self.logger.debug("START METADATA") | |
| yield None | yield None |
| #delegate is a generator that handles all the events "within" | #delegate is a generator that handles all the events "within" |
| Line 177 class xml_handler: | Line 186 class xml_handler: |
| yield None | yield None |
| #Element closed. Wrap up | #Element closed. Wrap up |
| logging.debug("END METADATA") | self.logger.debug("END METADATA") |
| # rename table for backup | # rename table for backup |
| if self.backup_table: | if self.backup_table: |
| Line 194 class xml_handler: | Line 203 class xml_handler: |
| if self.id_field: | if self.id_field: |
| # sync mode -- copy table | # sync mode -- copy table |
| logging.info("copy table %s to %s"%(self.orig_table,self.table)) | self.logger.info("copy table %s to %s"%(self.orig_table,self.table)) |
| qstr = "CREATE TABLE %s AS (SELECT * FROM %s)"%(self.table,self.orig_table) | qstr = "CREATE TABLE %s AS (SELECT * FROM %s)"%(self.table,self.orig_table) |
| else: | else: |
| # rename table and create empty new one | # rename table and create empty new one |
| logging.info("create empty table %s"%(self.table)) | self.logger.info("create empty table %s"%(self.table)) |
| qstr = "CREATE TABLE %s AS (SELECT * FROM %s WHERE 1=0)"%(self.table,self.orig_table) | qstr = "CREATE TABLE %s AS (SELECT * FROM %s WHERE 1=0)"%(self.table,self.orig_table) |
| self.db.execute(qstr) | self.db.execute(qstr) |
| Line 207 class xml_handler: | Line 216 class xml_handler: |
| # delete data from table for replace | # delete data from table for replace |
| if self.replace_table: | if self.replace_table: |
| logging.info("delete data from table %s"%(self.table)) | self.logger.info("delete data from table %s"%(self.table)) |
| qstr = "TRUNCATE TABLE %s"%(self.table) | qstr = "TRUNCATE TABLE %s"%(self.table) |
| self.db.execute(qstr) | self.db.execute(qstr) |
| self.dbCon.commit() | self.dbCon.commit() |
| Line 218 class xml_handler: | Line 227 class xml_handler: |
| # translate id_field (SQL-name) to XML-name | # translate id_field (SQL-name) to XML-name |
| self.xml_id = self.sql_field_map.get(self.id_field, None) | self.xml_id = self.sql_field_map.get(self.id_field, None) |
| #logging.debug("xml-fieldnames:"+repr(self.xml_field_names)) | #self.logger.debug("xml-fieldnames:"+repr(self.xml_field_names)) |
| # get list of fields and types of db table | # get list of fields and types of db table |
| qstr="select attname, format_type(pg_attribute.atttypid, pg_attribute.atttypmod) from pg_attribute, pg_class where attrelid = pg_class.oid and pg_attribute.attnum > 0 and relname = '%s'" | qstr="select attname, format_type(pg_attribute.atttypid, pg_attribute.atttypmod) from pg_attribute, pg_class where attrelid = pg_class.oid and pg_attribute.attnum > 0 and relname = '%s'" |
| self.sql_fields={} | self.sql_fields={} |
| Line 258 class xml_handler: | Line 267 class xml_handler: |
| if not self.keep_fields: | if not self.keep_fields: |
| # adjust db table to fields in XML and update_fields | # adjust db table to fields in XML and update_fields |
| for f in self.xml_field_map.values(): | for f in self.xml_field_map.values(): |
| logging.debug("sync-fieldname: %s"%f.getName()) | self.logger.debug("sync-fieldname: %s"%f.getName()) |
| sf = self.sql_fields.get(f.getName(), None) | sf = self.sql_fields.get(f.getName(), None) |
| uf = self.update_fields.get(f.getName(), None) | uf = self.update_fields.get(f.getName(), None) |
| if sf is not None: | if sf is not None: |
| # name in db -- check type | # name in db -- check type |
| if f.getType() != sf.getType(): | if f.getType() != sf.getType(): |
| logging.debug("field %s has different type (%s vs %s)"%(f,f.getType(),sf.getType())) | self.logger.debug("field %s has different type (%s vs %s)"%(f,f.getType(),sf.getType())) |
| elif uf is not None: | elif uf is not None: |
| # add field to table | # add field to table |
| qstr="alter table %s add %s %s"%(self.table,uf.getName(),uf.getType()) | qstr="alter table %s add %s %s"%(self.table,uf.getName(),uf.getType()) |
| logging.info("db add field:"+qstr) | self.logger.info("db add field:"+qstr) |
| if self.ascii_db and type(qstr)==types.UnicodeType: | if self.ascii_db and type(qstr)==types.UnicodeType: |
| qstr=qstr.encode('utf-8') | qstr=qstr.encode('utf-8') |
| Line 283 class xml_handler: | Line 292 class xml_handler: |
| fields=string.join([self.xml_field_map[x].getName() for x in self.xml_update_list], ',') | fields=string.join([self.xml_field_map[x].getName() for x in self.xml_update_list], ',') |
| values=string.join(['%s' for f in self.xml_update_list], ',') | values=string.join(['%s' for f in self.xml_update_list], ',') |
| self.addQuery="INSERT INTO %s (%s) VALUES (%s)"%(self.table,fields,values) | self.addQuery="INSERT INTO %s (%s) VALUES (%s)"%(self.table,fields,values) |
| logging.debug("update-query: "+self.updQuery) | self.logger.debug("update-query: "+self.updQuery) |
| logging.debug("add-query: "+self.addQuery) | self.logger.debug("add-query: "+self.addQuery) |
| return | return |
| def handle_meta_field(self, end_condition): | def handle_meta_field(self, end_condition): |
| Line 300 class xml_handler: | Line 309 class xml_handler: |
| # map to sql name and default text type | # map to sql name and default text type |
| self.xml_field_map[name] = TableColumn(sqlname, 'text') | self.xml_field_map[name] = TableColumn(sqlname, 'text') |
| self.sql_field_map[sqlname] = name | self.sql_field_map[sqlname] = name |
| logging.debug("FIELD name: "+name) | self.logger.debug("FIELD name: "+name) |
| return | return |
| def handle_data_fields(self, end_condition): | def handle_data_fields(self, end_condition): |
| Line 310 class xml_handler: | Line 319 class xml_handler: |
| } | } |
| #First round through the generator corresponds to the | #First round through the generator corresponds to the |
| #start element event | #start element event |
| logging.debug("START RESULTSET") | self.logger.info("reading data...") |
| self.logger.debug("START RESULTSET") | |
| self.rowcnt = 0 | self.rowcnt = 0 |
| yield None | yield None |
| Line 323 class xml_handler: | Line 333 class xml_handler: |
| yield None | yield None |
| #Element closed. Wrap up | #Element closed. Wrap up |
| logging.debug("END RESULTSET") | self.logger.debug("END RESULTSET") |
| self.dbCon.commit() | self.dbCon.commit() |
| if self.sync_mode: | if self.sync_mode: |
| # delete unmatched entries in db | # delete unmatched entries in db |
| logging.info("deleting unmatched rows from db") | self.logger.info("deleting unmatched rows from db") |
| delQuery = "DELETE FROM %s WHERE %s = %%s"%(self.table,self.id_field) | delQuery = "DELETE FROM %s WHERE %s = %%s"%(self.table,self.id_field) |
| for id in self.dbIDs.keys(): | for id in self.dbIDs.keys(): |
| # find all not-updated fields | # find all not-updated fields |
| if self.dbIDs[id] == 0: | if self.dbIDs[id] == 0: |
| logging.info(" delete:"+id) | self.logger.info(" delete:"+id) |
| SimpleSearch(self.db, delQuery, [id], ascii=self.ascii_db) | SimpleSearch(self.db, delQuery, [id], ascii=self.ascii_db) |
| sys.exit(1) | sys.exit(1) |
| elif self.dbIDs[id] > 1: | elif self.dbIDs[id] > 1: |
| logging.info(" sync: ID %s used more than once?"%id) | self.logger.info(" sync: ID %s used more than once?"%id) |
| self.dbCon.commit() | self.dbCon.commit() |
| # reinstate backup tables | # reinstate backup tables |
| if self.backup_table: | if self.backup_table: |
| backup_name = "%s_%s"%(self.orig_table,time.strftime('%Y_%m_%d_%H_%M_%S')) | backup_name = "%s_%s"%(self.orig_table,time.strftime('%Y_%m_%d_%H_%M_%S')) |
| logging.info("rename backup table %s to %s"%(self.orig_table,backup_name)) | self.logger.info("rename backup table %s to %s"%(self.orig_table,backup_name)) |
| qstr = "ALTER TABLE %s RENAME TO %s"%(self.orig_table,backup_name) | qstr = "ALTER TABLE %s RENAME TO %s"%(self.orig_table,backup_name) |
| self.db.execute(qstr) | self.db.execute(qstr) |
| logging.info("rename working table %s to %s"%(self.table,self.orig_table)) | self.logger.info("rename working table %s to %s"%(self.table,self.orig_table)) |
| qstr = "ALTER TABLE %s RENAME TO %s"%(self.table,self.orig_table) | qstr = "ALTER TABLE %s RENAME TO %s"%(self.table,self.orig_table) |
| self.db.execute(qstr) | self.db.execute(qstr) |
| self.dbCon.commit() | self.dbCon.commit() |
| Line 360 class xml_handler: | Line 370 class xml_handler: |
| (saxtools.START_ELEMENT, fm_ns, u'COL'): | (saxtools.START_ELEMENT, fm_ns, u'COL'): |
| self.handle_col, | self.handle_col, |
| } | } |
| logging.debug("START ROW") | self.logger.debug("START ROW") |
| self.xml_data = {} | self.xml_data = {} |
| self.colIdx = 0 | self.colIdx = 0 |
| yield None | yield None |
| Line 374 class xml_handler: | Line 384 class xml_handler: |
| yield None | yield None |
| #Element closed. Wrap up | #Element closed. Wrap up |
| logging.debug("END ROW") | self.logger.debug("END ROW") |
| self.rowcnt += 1 | self.rowcnt += 1 |
| # process collected row data | # process collected row data |
| update=False | update=False |
| Line 406 class xml_handler: | Line 416 class xml_handler: |
| # update existing row (by id_field) | # update existing row (by id_field) |
| # last argument is ID match | # last argument is ID match |
| args.append(id_val) | args.append(id_val) |
| logging.debug("update: %s = %s"%(id_val, args)) | self.logger.debug("update: %s = %s"%(id_val, args)) |
| SimpleSearch(self.db, self.updQuery, args, ascii=self.ascii_db) | SimpleSearch(self.db, self.updQuery, args, ascii=self.ascii_db) |
| else: | else: |
| # create new row | # create new row |
| logging.debug("insert: %s"%args) | self.logger.debug("insert: %s"%args) |
| SimpleSearch(self.db, self.addQuery, args, ascii=self.ascii_db) | SimpleSearch(self.db, self.addQuery, args, ascii=self.ascii_db) |
| #logging.info(" row:"+"%d (%s)"%(self.rowcnt,id_val)) | #self.logger.info(" row:"+"%d (%s)"%(self.rowcnt,id_val)) |
| if (self.rowcnt % 10) == 0: | if (self.rowcnt % 100) == 0: |
| logging.info(" row:"+"%d (%s)"%(self.rowcnt,id_val)) | self.logger.info(" row:"+"%d (id:%s)"%(self.rowcnt,id_val)) |
| self.dbCon.commit() | self.dbCon.commit() |
| return | return |
| Line 471 def importFMPXML(options): | Line 481 def importFMPXML(options): |
| @param options.backup_table: (optional) create backup of old table (breaks indices) | @param options.backup_table: (optional) create backup of old table (breaks indices) |
| """ | """ |
| if getattr(options,'update_fields',None): | if getattr(options,'update_fields',None): |
| uf = {} | uf = {} |
| for f in options.update_fields.split(','): | for f in options.update_fields.split(','): |
| Line 486 def importFMPXML(options): | Line 495 def importFMPXML(options): |
| if getattr(options,'id_field',None) and getattr(options,'replace_table',None): | if getattr(options,'id_field',None) and getattr(options,'replace_table',None): |
| logging.error("ABORT: sorry, you can't do both sync (id_field) and replace") | logging.error("ABORT: sorry, you can't do both sync (id_field) and replace") |
| sys.exit(1) | return |
| parser = sax.make_parser() | parser = sax.make_parser() |
| #The "consumer" is our own handler | #The "consumer" is our own handler |