# HG changeset patch # User Paul Boddie # Date 1495810345 -7200 # Node ID 3c756db2eb7e32a6503bb959d8e8cb9671b6f98f # Parent 779e632b6e6982d2349e29e97f349283de18f63e Support direct copying of record data when inserting periods into PostgreSQL. diff -r 779e632b6e69 -r 3c756db2eb7e imiptools/freebusy.py --- a/imiptools/freebusy.py Wed May 24 15:41:14 2017 +0200 +++ b/imiptools/freebusy.py Fri May 26 16:52:25 2017 +0200 @@ -24,18 +24,62 @@ from imiptools.period import get_overlapping, Period, PeriodBase from imiptools.sql import DatabaseOperations +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + def from_string(s, encoding): + + "Interpret 's' using 'encoding', preserving None." + if s: return unicode(s, encoding) else: return s def to_string(s, encoding): + + "Encode 's' using 'encoding', preserving None." + if s: return s.encode(encoding) else: return s +def to_copy_string(s, encoding): + + """ + Encode 's' using 'encoding' as a string suitable for use in tabular data + acceptable to the PostgreSQL COPY command with \N as null. + """ + + s = to_string(s, encoding) + return s is None and "\\N" or s + +def to_copy_file(records): + + """ + Encode the given 'records' and store them in a file-like object for use with + a tabular import mechanism. Return the file-like object. + """ + + io = StringIO() + for values in records: + l = [] + for v in values: + l.append(to_copy_string(v, "utf-8")) + io.write("\t".join(l)) + io.write("\n") + io.seek(0) + return io + +def quote_column(column): + + "Quote 'column' using the SQL keyword quoting notation." + + return '"%s"' % column + class FreeBusyPeriod(PeriodBase): "A free/busy record abstraction." @@ -265,8 +309,7 @@ # List emulation methods. def __iadd__(self, periods): - for period in periods: - self.insert_period(period) + self.insert_periods(periods) return self def append(self, period): @@ -274,6 +317,13 @@ # Operations. + def insert_periods(self, periods): + + "Insert the given 'periods' into the collection." + + for p in periods: + self.insert_period(p) + def can_schedule(self, periods, uid, recurrenceid): """ @@ -409,8 +459,7 @@ self.remove_specific_event_periods(uid, recurrenceid) - for p in periods: - self.insert_period(p) + self.insert_periods(periods) def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser): @@ -446,8 +495,7 @@ self.remove_specific_event_periods(uid, recurrenceid, attendee) - for p in periods: - self.insert_period(p) + self.insert_periods(periods) def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser, attendee=None): @@ -790,6 +838,27 @@ self.cursor.execute(query, values) + def insert_periods(self, periods): + + "Insert the given 'periods' into the collection." + + if not hasattr(self.cursor, "copy_from"): + return FreeBusyCollectionBase.insert_periods(self, periods) + + self._check_mutable() + + columns = self.merge_default_columns(self.period_columns) + + all_values = [] + for period in periods: + all_values.append(self.merge_default_values(period.as_tuple(string_datetimes=True))) + + f = to_copy_file(all_values) + + # Copy from the file-like object to the table. + + self.cursor.copy_from(f, self.table_name, columns=map(quote_column, columns)) + def remove_periods(self, periods): "Remove the given 'periods' from the collection."