def create(self, message, branch, sql_up="", sql_down=""): if branch: target_branch = self.root.find_branch(branch) if target_branch is None: bpath = branch.split(".") if len(bpath) < 2 or len(bpath) % 2: raise OperationError("Invalid branch name %s" % branch) else: parent_ver = ".".join(bpath[0:-1]) parent = self.root.find_version(parent_ver) if parent is None: raise OperationError( "Version %s does not exists" "" % parent_ver ) target_branch = Branch(bpath, self, parent) parent.branches[bpath[-1]] = target_branch else: target_branch = self.root head = target_branch.head if head is not None: vpath = head.vpath[:] vpath[-1] = Version.format_version(int(vpath[-1]) + 1, head.branch) else: vpath = (target_branch.bpath or []) + [ Version.format_version(1, target_branch) ] name = Version._get_name(message) new_version = Version(vpath, None, name, self, target_branch) new_version.save(sql_up=sql_up, sql_down=sql_down) # target_branch.append(new_version) self.reload() return new_version.filename
def validate_version(version): vpath = version.split(".") for i in range(0, len(vpath), 2): try: int(vpath[i]) except ValueError: raise OperationError("Invalid version: %s" % version)
def rebase(self, branch): br = self.root.find_branch(branch) if br is None: raise OperationError( "Target branch %s not found in repository" % branch ) return br.rebase()
def _current_version(self, dsn): current_ver = self.current(dsn) if current_ver and current_ver != "0": current = self.root.find_version(current_ver) if current is None: raise OperationError( "Current version %s not found in repository" % current_ver ) return current return None
def head(self, branch): if branch: head_branch = self.root.find_branch(branch) if head_branch is None: raise OperationError("Branch %s not found" % branch) else: head_branch = self.root if head_branch.head: return head_branch.head.full_version else: return "0"
def init(path, dsn): pg_dump_path = None if dsn: pg_dump_path = autodetect_pg_dump_path() if not pg_dump_path: raise OperationError( "Error: pg_dump executable not found.\n" "Please add the directory containing " "pg_dump to the PATH" ) if os.path.exists(path): raise OperationError("Directory %s already exists" % path) os.mkdir(path) try: if pg_dump_path: Repository._create_baseline(path, dsn) except Exception: os.rmdir(path) raise return "Initialized new repository in %s" % path
def rebase(self): if self.parent is None: raise OperationError("The selected branch can not be rebased") target_branch = self.parent.branch for version in self: if not version.can_rebase(target_branch): raise OperationError( "Version %s can not be rebased, because " "have own branches" % self.full_version ) history = [] result = "" for version in self[:]: history.append(version.full_version) result += version.rebase(target_branch) self.parent.branches.pop(self.name) meta = self.rep.meta if "history" not in meta: meta["history"] = [] meta["history"].append(history) self.rep.meta = meta return result
def execute(self, db, dry_run, up_only=False, down_only=False): if up_only or down_only: for action in self: if up_only and not action.is_up: raise OperationError( "Can not be downgraded from version %s" % repr(action) ) if down_only and action.is_up: raise OperationError( "Can not be upgraded to version %s" % repr(action) ) old_dry_run = db.dry_run db.dry_run = dry_run tr = None for action in self: if tr is None and action.is_transactional: tr = Transaction(db, action.isolation_level) tr.begin() elif tr is not None and not action.is_transactional: tr.commit() tr = None elif ( tr is not None and tr.isolation_level != action.isolation_level ): tr.commit() tr = Transaction(db, action.isolation_level) tr.begin() try: action.execute(db) except Exception: if tr is not None: tr.rollback() raise if tr is not None: tr.commit() db.dry_run = old_dry_run
def _create_baseline(path, dsn): fileno, dump_filename = tempfile.mkstemp() returncode, stdout, stderr = pg_dump(dsn, dump_filename) if returncode != 0: raise OperationError(stderr) with open(dump_filename, "a") as f: f.write("SET search_path = public, pg_catalog;\n") rep = Repository.load(path) db = rep.get_db(dsn) with io.open(dump_filename, "r", encoding="UTF8") as f: sql_up = f.read() sql_down = "\n".join(db.schemas) filename = rep.create("baseline", None, sql_up, sql_down) db.ops_add("up", None, os.path.basename(filename).split("#")[0])
def down(self, version, dsn, show_plan, dry_run): """ Downgrade database to target version :param version: target version :param dsn: database connection string :param show_plan: if True then returns execution plan to do :param dry_run: if True then returns sql queries to do :type version: str :type dsn: str :type show_plan: bool :type dry_run: bool :return: current version as string(if show_plan=False & dry_run=False) or execution plan (if show_plan=True & dry_run=False) or sql queries as string (if show_plan=False & dry_run=True) :rtype: Union[str, Plan] """ self._check_consistency(dsn) current = self._current_version(dsn) if version: if version == "0": target = None else: target = self.root.find_version(version) if target is None: raise OperationError( "Target version %s not found in repository" % version ) elif current is not None: target = current.prev else: target = None plan = Plan.get_switch_plan(current, target, self) db = self.get_db(dsn) if show_plan: return plan else: plan.execute(db, dry_run, down_only=True) if dry_run: return "\n\n".join(db.buffer) else: return self.current(dsn) or "0"
def switch(self, version, dsn, show_plan, dry_run): self._check_consistency(dsn) current = self._current_version(dsn) if version == "0": target = None else: target = self.root.find_version(version) if target is None: raise OperationError( "Target version %s not found in repository" % version ) plan = Plan.get_switch_plan(current, target, self) db = self.get_db(dsn) if show_plan: return plan else: plan.execute(db, dry_run) if dry_run: return "\n\n".join(db.buffer) else: return self.current(dsn) or "0"
def rebase(self, branch): """ :type branch: Branch """ if not self.can_rebase(branch): raise OperationError( "Version %s can not be rebased, because have " "own branches" % self.full_version ) result = "" old_full_version = self.full_version head = branch.head if head: version = head.version + 1 else: version = 1 self.branch.remove(self) self.version = version version_str = Version.format_version(version, branch) self.vpath = self.vpath[0:-3] + [version_str] self.branch = branch old_filename = self.filename self.filename = os.path.join( self.rep.path, Version.TEMPLATE_FILENAME.format( version=self.full_version, name=self.name ), ) branch.append(self) result += "Moving %s to %s\n" % (old_full_version, self.full_version) meta = self.rep.meta if meta is None: meta = {} if "rebase" not in meta: meta["rebase"] = {} meta["rebase"][old_full_version] = self.full_version self.rep.meta = meta os.rename(old_filename, self.filename) return result
def find_branch(self, path): """ Search branch recursively :param path: path to branch. Ex: 001.TEST.2.TEST2 :type path: str :return: branch or None :rtype: Branch """ if path is None: return self bpath = path.split(".") search_version_val = bpath.pop(0) try: search_version = int(search_version_val) except ValueError: raise OperationError("Invalid version %s" % search_version_val) search_branch = bpath.pop(0) for ver in self: if ver.version == search_version: for branch_name, branch in ver.branches.items(): if branch_name == search_branch: return branch.find_branch(".".join(bpath) or None) return None
def save(self, sql_up, sql_down): if self.filename: raise OperationError("File already exists %s" % self.filename) self.filename = os.path.join( self.rep.path, Version.TEMPLATE_FILENAME.format( version=self.full_version, name=self.name ), ) logging.debug("Generating %s" % self.filename) tmpl = jinja2.Template( MIGRATION_TEMPLATE.format( sql_up=repr_str_multiline(sql_up), sql_down=repr_str_multiline(sql_down), ) ) with io.open(self.filename, "w", encoding="UTF8") as f: f.write( tmpl.render( now=datetime.datetime.utcnow(), version=self.full_version, name=self.name, ) )
def _check_consistency(self, dsn): errors = self._check_for_actualize(dsn) if len(errors): err_str = ", ".join(["%s -> %s" % e for e in errors]) raise OperationError("Inconsistent state: %s" % err_str)