func (driver *Driver) Migrate(f file.File, pipe chan interface{}) { defer close(pipe) pipe <- f tx, err := driver.db.Begin() if err != nil { pipe <- err return } if f.Direction == direction.Up { if _, err := tx.Exec("INSERT INTO "+tableName+" (version) VALUES ($1)", f.Version); err != nil { pipe <- err if err := tx.Rollback(); err != nil { pipe <- err } return } } else if f.Direction == direction.Down { if _, err := tx.Exec("DELETE FROM "+tableName+" WHERE version=$1", f.Version); err != nil { pipe <- err if err := tx.Rollback(); err != nil { pipe <- err } return } } if err := f.ReadContent(); err != nil { pipe <- err return } if _, err := tx.Exec(string(f.Content)); err != nil { pqErr := err.(*pq.Error) offset, err := strconv.Atoi(pqErr.Position) if err == nil && offset >= 0 { lineNo, columnNo := file.LineColumnFromOffset(f.Content, offset-1) errorPart := file.LinesBeforeAndAfter(f.Content, lineNo, 5, 5, true) pipe <- errors.New(fmt.Sprintf("%s %v: %s in line %v, column %v:\n\n%s", pqErr.Severity, pqErr.Code, pqErr.Message, lineNo, columnNo, string(errorPart))) } else { pipe <- errors.New(fmt.Sprintf("%s %v: %s", pqErr.Severity, pqErr.Code, pqErr.Message)) } if err := tx.Rollback(); err != nil { pipe <- err } return } if err := tx.Commit(); err != nil { pipe <- err return } }
func (driver *Driver) multiStatementsFallback(content []byte, tx *sql.Tx) error { sqlStmts := bytes.Split(content, []byte(";")) for _, sqlStmt := range sqlStmts { sqlStmt = bytes.TrimSpace(sqlStmt) if len(sqlStmt) > 0 { if _, err := tx.Exec(string(sqlStmt)); err != nil { mysqlErr, isErr := err.(*mysql.MySQLError) if isErr { re, err := regexp.Compile(`at line ([0-9]+)$`) if err != nil { return err } var lineNo int lineNoRe := re.FindStringSubmatch(mysqlErr.Message) if len(lineNoRe) == 2 { lineNo, err = strconv.Atoi(lineNoRe[1]) } if err == nil { // get white-space offset // TODO this is broken, because we use sqlStmt instead of content wsLineOffset := 0 b := bufio.NewReader(bytes.NewBuffer(sqlStmt)) for { line, _, err := b.ReadLine() if err != nil { break } if bytes.TrimSpace(line) == nil { wsLineOffset += 1 } else { break } } message := mysqlErr.Error() message = re.ReplaceAllString(message, fmt.Sprintf("at line %v", lineNo+wsLineOffset)) errorPart := file.LinesBeforeAndAfter(sqlStmt, lineNo, 5, 5, true) return errors.New(fmt.Sprintf("%s\n\n%s", message, string(errorPart))) } else { return errors.New(mysqlErr.Error()) } } return err } } } return nil }