pgtoolkit/pgdiff.py
changeset 47 bb8c729ae6ce
parent 9 2fcc8ef0b97d
child 53 4a049a5af657
equal deleted inserted replaced
46:037410ef2b6b 47:bb8c729ae6ce
    30 
    30 
    31 class DiffBase:
    31 class DiffBase:
    32     COLORS = {
    32     COLORS = {
    33         '+' : BOLD | GREEN,
    33         '+' : BOLD | GREEN,
    34         '-' : BOLD | RED,
    34         '-' : BOLD | RED,
    35         '*' : BOLD | YELLOW}
    35         '*' : BOLD | YELLOW,
       
    36     }
       
    37     
       
    38     COMMANDS = {
       
    39         '+' : 'CREATE',
       
    40         '-' : 'DROP',
       
    41         '*' : 'ALTER',
       
    42     }
    36     
    43     
    37     def __init__(self):
    44     def __init__(self):
    38         self.changes = None
    45         self.changes = None
    39 
    46 
    40     def format(self):
    47     def format(self):
    44         out.append(self.change)
    51         out.append(self.change)
    45         
    52         
    46         out += [' ', self.type, ' ', self.name, highlight(0)]
    53         out += [' ', self.type, ' ', self.name, highlight(0)]
    47         
    54         
    48         if self.changes:
    55         if self.changes:
    49             out += [highlight(1, WHITE), ' (', self.formatchanges(), ')', highlight(0)]
    56             out += [highlight(1, WHITE), ' (', self._formatchanges(), ')', highlight(0)]
    50 
    57 
    51         return ''.join(out)
    58         return ''.join(out)
    52 
    59 
    53     def formatnotnull(self, notnull):
    60     def _formatnotnull(self, notnull):
    54         if notnull:
    61         if notnull:
    55             return 'NOT NULL'
    62             return 'NOT NULL'
    56         else:
    63         else:
    57             return None
    64             return None
    58     
    65     
    59     def formatchanges(self):
    66     def _formatchanges(self):
    60         res = []
    67         res = []
    61         for x in self.changes:
    68         for x in self.changes:
    62             type, a, b = x
    69             type, a, b = x
    63             if type == 'notnull':
    70             if type == 'notnull':
    64                 type = ''
    71                 type = ''
    65                 a = self.formatnotnull(a)
    72                 a = self._formatnotnull(a)
    66                 b = self.formatnotnull(b)
    73                 b = self._formatnotnull(b)
    67                 
    74                 
    68             if a and b:
    75             if a and b:
    69                 s = ''.join(['Changed ', type, ' from ',
    76                 s = ''.join(['Changed ', type, ' from ',
    70                     highlight(1,15), a, highlight(0), ' to ',
    77                     highlight(1,15), a, highlight(0), ' to ',
    71                     highlight(1,15), b, highlight(0), '.'])
    78                     highlight(1,15), b, highlight(0), '.'])
    81                     l += [type, ' ']
    88                     l += [type, ' ']
    82                 l += [highlight(1,15), b, highlight(0), '.']
    89                 l += [highlight(1,15), b, highlight(0), '.']
    83                 s = ''.join(l)
    90                 s = ''.join(l)
    84             res.append(s)
    91             res.append(s)
    85         return ' '.join(res)
    92         return ' '.join(res)
       
    93     
       
    94     def format_patch(self):
       
    95         if self.change == '*' and self.type in ('schema', 'table'):
       
    96             return None
       
    97         return '%s %s %s;' % (self.COMMANDS[self.change], self.type.upper(), self.name)
    86 
    98 
    87 
    99 
    88 class DiffSchema(DiffBase):
   100 class DiffSchema(DiffBase):
    89     def __init__(self, change, schema):
   101     def __init__(self, change, schema):
    90         DiffBase.__init__(self)
   102         DiffBase.__init__(self)
    91         self.level = 0
   103         self.level = 0
    92         self.type = 'schema'
   104         self.type = 'schema'
    93         self.change = change
   105         self.change = change
    94         self.schema = schema
   106         self.schema = schema
    95         self.name = schema
   107         self.name = schema
    96         
   108 
    97 
   109 
    98 class DiffTable(DiffBase):
   110 class DiffTable(DiffBase):
    99     def __init__(self, change, schema, table):
   111     def __init__(self, change, schema, table):
   100         DiffBase.__init__(self)
   112         DiffBase.__init__(self)
   101         self.level = 1
   113         self.level = 1
   105         self.table = table
   117         self.table = table
   106         self.name = table
   118         self.name = table
   107 
   119 
   108 
   120 
   109 class DiffColumn(DiffBase):
   121 class DiffColumn(DiffBase):
   110     def __init__(self, change, schema, table, column, changes=None):
   122     ALTER_COMMANDS = {
       
   123         '+' : 'ADD',
       
   124         '-' : 'DROP',
       
   125         '*' : 'ALTER',
       
   126     }
       
   127     
       
   128     def __init__(self, change, schema, table, column, columntype, columndefault, changes=None):
   111         DiffBase.__init__(self)
   129         DiffBase.__init__(self)
   112         self.level = 2
   130         self.level = 2
   113         self.type = 'column'
   131         self.type = 'column'
   114         self.change = change
   132         self.change = change
   115         self.schema = schema
   133         self.schema = schema
   116         self.table = table
   134         self.table = table
   117         self.column = column
   135         self.column = column
       
   136         self.columntype = columntype
       
   137         self.columndefault = columndefault
   118         self.name = column
   138         self.name = column
   119         self.changes = changes
   139         self.changes = changes
       
   140     
       
   141     def format_patch(self):
       
   142         out = 'ALTER TABLE %s.%s %s COLUMN %s %s' % (
       
   143             self.schema,
       
   144             self.table,
       
   145             self.ALTER_COMMANDS[self.change],
       
   146             self.name,
       
   147             self.columntype
       
   148         )
       
   149         if self.columndefault:
       
   150             out += ' DEFAULT ' + self.columndefault
       
   151         out += ';'
       
   152         return out
   120 
   153 
   121 
   154 
   122 class DiffConstraint(DiffBase):
   155 class DiffConstraint(DiffBase):
   123     def __init__(self, change, schema, table, constraint, changes=None):
   156     def __init__(self, change, schema, table, constraint, changes=None):
   124         DiffBase.__init__(self)
   157         DiffBase.__init__(self)
   140         self.include_schemas = set()  # if not empty, consider only these schemas for diff
   173         self.include_schemas = set()  # if not empty, consider only these schemas for diff
   141         self.exclude_schemas = set()  # exclude these schemas from diff
   174         self.exclude_schemas = set()  # exclude these schemas from diff
   142         self.include_tables = set()
   175         self.include_tables = set()
   143         self.exclude_tables = set()
   176         self.exclude_tables = set()
   144     
   177     
   145     def _test_filter(self, schema):
   178     def _test_schema(self, schema):
   146         if self.include_schemas and schema not in self.include_schemas:
   179         if self.include_schemas and schema not in self.include_schemas:
   147             return False
   180             return False
   148         if schema in self.exclude_schemas:
   181         if schema in self.exclude_schemas:
   149             return False
   182             return False
   150         return True
   183         return True
   151     
   184     
   152     def _test_table(self, schema, table):
   185     def _test_table(self, table):
   153         name = schema + '.' + table
   186         if self.include_tables and table not in self.include_tables:
   154         if self.include_tables and name not in self.include_tables:
       
   155             return False
   187             return False
   156         if name in self.exclude_tables:
   188         if table in self.exclude_tables:
   157             return False
   189             return False
   158         return True 
   190         return True 
   159     
   191     
   160     def _diffnames(self, src, dst):
   192     def _diff_names(self, src, dst):
   161         for x in src:
   193         for x in src:
   162             if x in dst:
   194             if x in dst:
   163                 yield ('*', x)
   195                 yield ('*', x)
   164             else:
   196             else:
   165                 yield ('-', x)
   197                 yield ('-', x)
   184         if a.definition != b.definition:
   216         if a.definition != b.definition:
   185             diff.append(('definition', a.definition, b.definition))
   217             diff.append(('definition', a.definition, b.definition))
   186         return diff
   218         return diff
   187                 
   219                 
   188     def _diff_columns(self, schema, table, src_columns, dst_columns):
   220     def _diff_columns(self, schema, table, src_columns, dst_columns):
   189         for nd in self._diffnames(src_columns, dst_columns):
   221         for nd in self._diff_names(src_columns, dst_columns):
   190             cdo = DiffColumn(change=nd[0], schema=schema, table=table, column=nd[1])
   222             cdo = DiffColumn(change=nd[0], schema=schema, table=table, column=nd[1],
       
   223                 columntype=dst_columns[nd[1]].type, columndefault=dst_columns[nd[1]].default)
   191             if nd[0] == '*':
   224             if nd[0] == '*':
   192                 a = src_columns[nd[1]]
   225                 a = src_columns[nd[1]]
   193                 b = dst_columns[nd[1]]
   226                 b = dst_columns[nd[1]]
   194                 cdo.changes = self._compare_columns(a, b)
   227                 cdo.changes = self._compare_columns(a, b)
   195                 if cdo.changes:
   228                 if cdo.changes:
   196                     yield cdo
   229                     yield cdo
   197             else:
   230             else:
   198                 yield cdo
   231                 yield cdo
   199 
   232 
   200     def _diff_constraints(self, schema, table, src_constraints, dst_constraints):
   233     def _diff_constraints(self, schema, table, src_constraints, dst_constraints):
   201         for nd in self._diffnames(src_constraints, dst_constraints):
   234         for nd in self._diff_names(src_constraints, dst_constraints):
   202             cdo = DiffConstraint(change=nd[0], schema=schema, table=table, constraint=nd[1])
   235             cdo = DiffConstraint(change=nd[0], schema=schema, table=table, constraint=nd[1])
   203             if nd[0] == '*':
   236             if nd[0] == '*':
   204                 a = src_constraints[nd[1]]
   237                 a = src_constraints[nd[1]]
   205                 b = dst_constraints[nd[1]]
   238                 b = dst_constraints[nd[1]]
   206                 cdo.changes = self._compare_constraints(a, b)
   239                 cdo.changes = self._compare_constraints(a, b)
   208                     yield cdo
   241                     yield cdo
   209             else:
   242             else:
   210                 yield cdo
   243                 yield cdo
   211 
   244 
   212     def _difftables(self, schema, src_tables, dst_tables):
   245     def _difftables(self, schema, src_tables, dst_tables):
   213         for nd in self._diffnames(src_tables, dst_tables):
   246         for nd in self._diff_names(src_tables, dst_tables):
   214             if not self._test_table(schema, nd[1]):
   247             if not self._test_table(nd[1]):
   215                 continue
   248                 continue
   216             tdo = DiffTable(change=nd[0], schema=schema, table=nd[1])
   249             tdo = DiffTable(change=nd[0], schema=schema, table=nd[1])
   217             if nd[0] == '*':
   250             if nd[0] == '*':
   218                 # columns
   251                 # columns
   219                 src_columns = src_tables[nd[1]].columns
   252                 src_columns = src_tables[nd[1]].columns
   234             else:
   267             else:
   235                 yield tdo
   268                 yield tdo
   236 
   269 
   237     def iter_diff(self):
   270     def iter_diff(self):
   238         '''Return diff between src and dst database schema.
   271         '''Return diff between src and dst database schema.
   239 
   272         
   240         Yields one line at the time. Each line is in form of object
   273         Yields one line at the time. Each line is in form of object
   241         iherited from DiffBase. This object contains all information
   274         iherited from DiffBase. This object contains all information
   242         about changes. See format() method.
   275         about changes. See format() method.
   243         
   276         
   244         '''
   277         '''
   245         src_schemas = self.src.schemas
   278         src_schemas = self.src.schemas
   246         dst_schemas = self.dst.schemas
   279         dst_schemas = self.dst.schemas
   247         src = [x.name for x in src_schemas.values() if not x.system and self._test_filter(x.name)]
   280         src = [x.name for x in src_schemas.values() if not x.system and self._test_schema(x.name)]
   248         dst = [x.name for x in dst_schemas.values() if not x.system and self._test_filter(x.name)]
   281         dst = [x.name for x in dst_schemas.values() if not x.system and self._test_schema(x.name)]
   249         for nd in self._diffnames(src, dst):
   282         for nd in self._diff_names(src, dst):
   250             sdo = DiffSchema(change=nd[0], schema=nd[1])
   283             sdo = DiffSchema(change=nd[0], schema=nd[1])
   251             if nd[0] == '*':
   284             if nd[0] == '*':
   252                 src_tables = src_schemas[nd[1]].tables
   285                 src_tables = src_schemas[nd[1]].tables
   253                 dst_tables = dst_schemas[nd[1]].tables
   286                 dst_tables = dst_schemas[nd[1]].tables
   254                 for tdo in self._difftables(nd[1], src_tables, dst_tables):
   287                 for tdo in self._difftables(nd[1], src_tables, dst_tables):
   284         Even if it works as intended, it can cause table lock ups
   317         Even if it works as intended, it can cause table lock ups
   285         and/or loss of data. You have been warned.
   318         and/or loss of data. You have been warned.
   286         
   319         
   287         '''
   320         '''
   288         for ln in self.iter_diff():
   321         for ln in self.iter_diff():
   289             print(ln.format_patch())
   322             patch = ln.format_patch()
       
   323             if patch:
       
   324                 print(patch)
   290 
   325 
   291     def filter_schemas(self, include=[], exclude=[]):
   326     def filter_schemas(self, include=[], exclude=[]):
   292         '''Modify list of schemas which are used for computing diff.
   327         '''Modify list of schemas which are used for computing diff.
   293         
   328         
   294         include (list) -- if not empty, consider only these schemas for diff
   329         include (list) -- if not empty, consider only these schemas for diff