diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index 4f4ee99..ed578d0 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -280,7 +280,11 @@ post("/admin/export")(adminOnly { import gitbucket.core.util.JDBCUtil._ val session = request2Session(request) - val file = session.conn.exportAsXML(request.getParameterValues("tableNames").toSeq) + val file = if(params("type") == "sql"){ + session.conn.exportAsSQL(request.getParameterValues("tableNames").toSeq) + } else { + session.conn.exportAsXML(request.getParameterValues("tableNames").toSeq) + } contentType = "application/octet-stream" response.setHeader("Content-Disposition", "attachment; filename=" + file.getName) diff --git a/src/main/scala/gitbucket/core/util/JDBCUtil.scala b/src/main/scala/gitbucket/core/util/JDBCUtil.scala index 80e1469..08a62a1 100644 --- a/src/main/scala/gitbucket/core/util/JDBCUtil.scala +++ b/src/main/scala/gitbucket/core/util/JDBCUtil.scala @@ -164,11 +164,10 @@ (null, null) } else { rsMeta.getColumnType(i) match { - case Types.BOOLEAN | Types.BIT => ("boolean" , rs.getBoolean(columnName)) - case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR - => ("string" , rs.getString(columnName)) - case Types.INTEGER => ("int" , rs.getInt(columnName)) - case Types.TIMESTAMP => ("timestamp", dateFormat.format(rs.getTimestamp(columnName))) + case Types.BOOLEAN | Types.BIT => ("boolean", rs.getBoolean(columnName)) + case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR => ("string", rs.getString(columnName)) + case Types.INTEGER => ("int", rs.getInt(columnName)) + case Types.TIMESTAMP => ("timestamp", dateFormat.format(rs.getTimestamp(columnName))) } } writer.writeStartElement("column") @@ -193,6 +192,66 @@ file } + def exportAsSQL(targetTables: Seq[String]): File = { + val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss") + val file = File.createTempFile("gitbucket-export-", ".sql") + + using(new FileOutputStream(file)) { out => + val dbMeta = conn.getMetaData + val allTablesInDatabase = allTablesOrderByDependencies(dbMeta) + + allTablesInDatabase.reverse.foreach { tableName => + if (targetTables.contains(tableName)) { + out.write(s"DELETE FROM ${tableName};\n".getBytes("UTF-8")) + } + } + + allTablesInDatabase.foreach { tableName => + if (targetTables.contains(tableName)) { + val sb = new StringBuilder() + select(s"SELECT * FROM ${tableName}") { rs => + sb.append(s"INSERT INTO ${tableName} (") + + val rsMeta = rs.getMetaData + val columns = (1 to rsMeta.getColumnCount).map { i => + (rsMeta.getColumnName(i), rsMeta.getColumnType(i)) + } + sb.append(columns.map(_._1).mkString(", ")) + sb.append(") VALUES (") + + val values = columns.map { case (columnName, columnType) => + if(rs.getObject(columnName) == null){ + null + } else { + columnType match { + case Types.BOOLEAN | Types.BIT => rs.getBoolean(columnName) + case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR => rs.getString(columnName) + case Types.INTEGER => rs.getInt(columnName) + case Types.TIMESTAMP => rs.getTimestamp(columnName) + } + } + } + + val columnValues = values.map { value => + value match { + 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 + } + def allTableNames(): Seq[String] = { using(conn.getMetaData.getTables(null, null, "%", Seq("TABLE").toArray)) { rs => val tableNames = new ListBuffer[String] diff --git a/src/main/twirl/gitbucket/core/admin/data.scala.html b/src/main/twirl/gitbucket/core/admin/data.scala.html index b318d37..44f590f 100644 --- a/src/main/twirl/gitbucket/core/admin/data.scala.html +++ b/src/main/twirl/gitbucket/core/admin/data.scala.html @@ -15,6 +15,16 @@ } +
+ +
+
+ +