Export / Import as XML

This commit is contained in:
Naoki Takezoe
2016-04-25 00:35:25 +09:00
parent 30b8b738b6
commit c22ee8acfd
3 changed files with 111 additions and 71 deletions

View File

@@ -80,20 +80,9 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
post("/import") { post("/import") {
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin => session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin =>
execute({ (file, fileId) => execute({ (file, fileId) =>
using(file.getInputStream){ in =>
import JDBCUtil._ import JDBCUtil._
val sql = IOUtils.toString(in, "UTF-8")
val conn = request2Session(request).conn val conn = request2Session(request).conn
conn.setAutoCommit(false) conn.importAsXML(file.getInputStream)
try {
conn.update(sql)
conn.commit()
} catch {
case e: Throwable =>
conn.rollback()
throw e
}
}
}, _ => true) }, _ => true)
} }
redirect("/admin/data") redirect("/admin/data")

View File

@@ -280,7 +280,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/export")(adminOnly { post("/admin/export")(adminOnly {
import gitbucket.core.util.JDBCUtil._ import gitbucket.core.util.JDBCUtil._
val session = request2Session(request) val session = request2Session(request)
val file = session.conn.export(request.getParameterValues("tableNames").toSeq) val file = session.conn.exportAsXML(request.getParameterValues("tableNames").toSeq)
contentType = "application/octet-stream" contentType = "application/octet-stream"
response.setHeader("Content-Disposition", "attachment; filename=" + file.getName) response.setHeader("Content-Disposition", "attachment; filename=" + file.getName)

View File

@@ -3,7 +3,10 @@ package gitbucket.core.util
import java.io._ import java.io._
import java.sql._ import java.sql._
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import javax.xml.stream.{XMLStreamConstants, XMLInputFactory, XMLOutputFactory}
import ControlUtil._ import ControlUtil._
import scala.StringBuilder
import scala.collection.mutable
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
/** /**
@@ -60,61 +63,131 @@ object JDBCUtil {
} }
} }
def export(targetTables: Seq[String]): File = { def importAsXML(in: InputStream): Unit = {
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss") conn.setAutoCommit(false)
val file = File.createTempFile("gitbucket-export-", ".sql") try {
val factory = XMLInputFactory.newInstance()
using(factory.createXMLStreamReader(in)){ reader =>
// stateful objects
var elementName = ""
var insertTable = ""
var insertColumns = Map.empty[String, (String, String)]
using(new FileOutputStream(file)) { out => while(reader.hasNext){
reader.next()
reader.getEventType match {
case XMLStreamConstants.START_ELEMENT =>
elementName = reader.getName.getLocalPart
if(elementName == "insert"){
insertTable = reader.getAttributeValue(null, "table")
} else if(elementName == "delete"){
val tableName = reader.getAttributeValue(null, "table")
conn.update(s"DELETE FROM ${tableName}")
} else if(elementName == "column"){
val columnName = reader.getAttributeValue(null, "name")
val columnType = reader.getAttributeValue(null, "type")
val columnValue = reader.getElementText
insertColumns = insertColumns + (columnName -> (columnType, columnValue))
}
case XMLStreamConstants.END_ELEMENT =>
// Execute insert statement
reader.getName.getLocalPart match {
case "insert" => {
val sb = new StringBuilder()
sb.append(s"INSERT INTO ${insertTable} (")
sb.append(insertColumns.map { case (columnName, _) => columnName }.mkString(", "))
sb.append(") VALUES (")
sb.append(insertColumns.map { case (_, (columnType, columnValue)) =>
if(columnType == null || columnValue == null){
"NULL"
} else if(columnType == "string"){
"'" + columnValue.replace("'", "''") + "'"
} else if(columnType == "timestamp"){
"'" + columnValue + "'"
} else {
columnValue.toString
}
}.mkString(", "))
sb.append(")")
conn.update(sb.toString)
insertColumns = Map.empty[String, (String, String)] // Clear column information
}
case _ => // Nothing to do
}
case _ => // Nothing to do
}
}
}
conn.commit()
} catch {
case e: Exception => {
conn.rollback()
throw e
}
}
}
def exportAsXML(targetTables: Seq[String]): File = {
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
val file = File.createTempFile("gitbucket-export-", ".xml")
val factory = XMLOutputFactory.newInstance()
using(factory.createXMLStreamWriter(new FileOutputStream(file))){ writer =>
val dbMeta = conn.getMetaData val dbMeta = conn.getMetaData
val allTablesInDatabase = allTablesOrderByDependencies(dbMeta) val allTablesInDatabase = allTablesOrderByDependencies(dbMeta)
writer.writeStartDocument("UTF-8", "1.0")
writer.writeStartElement("tables")
allTablesInDatabase.reverse.foreach { tableName => allTablesInDatabase.reverse.foreach { tableName =>
if (targetTables.contains(tableName)) { if (targetTables.contains(tableName)) {
out.write(s"DELETE FROM ${tableName};\n".getBytes("UTF-8")) writer.writeStartElement("delete")
writer.writeAttribute("table", tableName)
writer.writeEndElement()
} }
} }
allTablesInDatabase.foreach { tableName => allTablesInDatabase.foreach { tableName =>
if (targetTables.contains(tableName)) { if (targetTables.contains(tableName)) {
val sb = new StringBuilder()
select(s"SELECT * FROM ${tableName}") { rs => select(s"SELECT * FROM ${tableName}") { rs =>
sb.append(s"INSERT INTO ${tableName} (") writer.writeStartElement("insert")
writer.writeAttribute("table", tableName)
val rsMeta = rs.getMetaData val rsMeta = rs.getMetaData
val columns = (1 to rsMeta.getColumnCount).map { i => (1 to rsMeta.getColumnCount).foreach { i =>
(rsMeta.getColumnName(i), rsMeta.getColumnType(i)) val columnName = rsMeta.getColumnName(i)
} val (columnType, columnValue) = if(rs.getObject(columnName) == null){
sb.append(columns.map(_._1).mkString(", ")) (null, null)
sb.append(") VALUES (")
val values = columns.map { case (columnName, columnType) =>
if(rs.getObject(columnName) == null){
null
} else { } else {
columnType match { rsMeta.getColumnType(i) match {
case Types.BOOLEAN | Types.BIT => rs.getBoolean(columnName) case Types.BOOLEAN | Types.BIT => ("boolean" , rs.getBoolean(columnName))
case Types.VARCHAR | Types.CLOB | Types.CHAR => rs.getString(columnName) case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR
case Types.INTEGER => rs.getInt(columnName) => ("string" , rs.getString(columnName))
case Types.TIMESTAMP => rs.getTimestamp(columnName) case Types.INTEGER => ("int" , rs.getInt(columnName))
case Types.TIMESTAMP => ("timestamp", dateFormat.format(rs.getTimestamp(columnName)))
}
}
writer.writeStartElement("column")
writer.writeAttribute("name", columnName)
if(columnType != null){
writer.writeAttribute("type", columnType)
}
if(columnValue != null){
writer.writeCharacters(columnValue.toString)
}
writer.writeEndElement()
}
writer.writeEndElement()
} }
} }
} }
val columnValues = values.map { value => writer.writeEndElement()
value match { writer.writeEndDocument()
case x: String => "'" + x.replace("'", "''") + "'"
case x: Timestamp => "'" + dateFormat.format(x) + "'"
case null => "NULL"
case x => x
}
}
sb.append(columnValues.mkString(", "))
sb.append(");\n")
}
out.write(sb.toString.getBytes("UTF-8"))
}
}
} }
file file
@@ -133,27 +206,6 @@ object JDBCUtil {
} }
} }
// private def parentTables(meta: DatabaseMetaData, tableName: String): Seq[String] = {
// val normalizedTableName =
// if(meta.getDatabaseProductName == "PostgreSQL"){
// tableName.toLowerCase
// } else {
// tableName
// }
//
// using(meta.getImportedKeys(null, null, normalizedTableName)) { rs =>
// val parents = new ListBuffer[String]
// while (rs.next) {
// val parentTableName = rs.getString("PKTABLE_NAME").toUpperCase
// if(!parents.contains(parentTableName)){
// parents += parentTableName
// parents ++= parentTables(meta, parentTableName)
// }
// }
// parents.distinct.toSeq
// }
// }
private def childTables(meta: DatabaseMetaData, tableName: String): Seq[String] = { private def childTables(meta: DatabaseMetaData, tableName: String): Seq[String] = {
val normalizedTableName = val normalizedTableName =
if(meta.getDatabaseProductName == "PostgreSQL"){ if(meta.getDatabaseProductName == "PostgreSQL"){
@@ -179,7 +231,6 @@ object JDBCUtil {
private def allTablesOrderByDependencies(meta: DatabaseMetaData): Seq[String] = { private def allTablesOrderByDependencies(meta: DatabaseMetaData): Seq[String] = {
val tables = allTableNames.map { tableName => val tables = allTableNames.map { tableName =>
val result = TableDependency(tableName, childTables(meta, tableName)) val result = TableDependency(tableName, childTables(meta, tableName))
println(result)
result result
} }
tables.sortWith { (a, b) => tables.sortWith { (a, b) =>