database storage of Meta_Info and Build_Info;
authorwenzelm
Fri, 28 Apr 2017 00:17:34 +0200
changeset 65599 08dfa79866ec
parent 65598 5deef985e38e
child 65600 138ffa41dc54
database storage of Meta_Info and Build_Info;
src/Pure/Admin/build_log.scala
--- a/src/Pure/Admin/build_log.scala	Thu Apr 27 23:36:59 2017 +0200
+++ b/src/Pure/Admin/build_log.scala	Fri Apr 28 00:17:34 2017 +0200
@@ -11,6 +11,7 @@
 import java.time.ZoneId
 import java.time.format.{DateTimeFormatter, DateTimeParseException}
 import java.util.Locale
+import java.sql.PreparedStatement
 
 import scala.collection.mutable
 import scala.util.matching.Regex
@@ -277,16 +278,14 @@
   object Meta_Info
   {
     val empty: Meta_Info = Meta_Info(Nil, Nil)
-
-    val log_filename = SQL.Column.string("log_filename", primary_key = true)
-
-    val columns: List[SQL.Column] =
-      log_filename :: Prop.columns ::: Settings.all_settings.map(SQL.Column.string(_))
   }
 
   sealed case class Meta_Info(props: Properties.T, settings: List[(String, String)])
   {
     def is_empty: Boolean = props.isEmpty && settings.isEmpty
+
+    def get(c: SQL.Column): Option[String] = Properties.get(props, c.name)
+    def get_date(c: SQL.Column): Option[Date] = get(c).map(Log_File.Date_Format.parse(_))
   }
 
   object Isatest
@@ -337,11 +336,11 @@
       val build_engine = if (engine == "") Nil else List(Prop.build_engine.name -> engine)
       val build_host = if (host == "") Nil else List(Prop.build_host.name -> host)
 
-      val start_date = List(Prop.build_start.name -> start.toString)
+      val start_date = List(Prop.build_start.name -> print_date(start))
       val end_date =
         log_file.lines.last match {
           case End(log_file.Strict_Date(end_date)) =>
-            List(Prop.build_end.name -> end_date.toString)
+            List(Prop.build_end.name -> print_date(end_date))
           case _ => Nil
         }
 
@@ -406,6 +405,27 @@
     val CANCELLED = Value("cancelled")
   }
 
+  object Session_Entry
+  {
+    val encode: XML.Encode.T[Session_Entry] = (entry: Session_Entry) =>
+    {
+      import XML.Encode._
+      pair(string, pair(list(string), pair(option(int), pair(Timing.encode, pair(Timing.encode,
+        pair(list(properties), pair(option(long), string)))))))(
+        entry.chapter, (entry.groups, (entry.threads, (entry.timing, (entry.ml_timing,
+        (entry.ml_statistics, (entry.heap_size, entry.status.toString)))))))
+    }
+    val decode: XML.Decode.T[Session_Entry] = (body: XML.Body) =>
+    {
+      import XML.Decode._
+      val (chapter, (groups, (threads, (timing, (ml_timing, (ml_statistics, (heap_size, status))))))) =
+        pair(string, pair(list(string), pair(option(int), pair(Timing.decode, pair(Timing.decode,
+          pair(list(properties), pair(option(long), string)))))))(body)
+      Session_Entry(chapter, groups, threads, timing, ml_timing, ml_statistics, heap_size,
+        Session_Status.withName(status))
+    }
+  }
+
   sealed case class Session_Entry(
     chapter: String,
     groups: List[String],
@@ -419,6 +439,20 @@
     def finished: Boolean = status == Session_Status.FINISHED
   }
 
+  object Build_Info
+  {
+    def encode: XML.Encode.T[Build_Info] = (info: Build_Info) =>
+    {
+      import XML.Encode._
+      list(pair(string, Session_Entry.encode))(info.sessions.toList)
+    }
+    def decode: XML.Decode.T[Build_Info] = (body: XML.Body) =>
+    {
+      import XML.Decode._
+      Build_Info(list(pair(string, Session_Entry.decode))(body).toMap)
+    }
+  }
+
   sealed case class Build_Info(sessions: Map[String, Session_Entry])
   {
     def session(name: String): Session_Entry = sessions(name)
@@ -578,6 +612,20 @@
 
   /** persistent store **/
 
+  object Info
+  {
+    val log_filename = SQL.Column.string("log_filename", primary_key = true)
+    val settings = SQL.Column.bytes("settings")
+    val build_info = SQL.Column.bytes("build_info")
+
+    val table =
+      SQL.Table("isabelle_build_log", log_filename :: Prop.columns ::: List(settings, build_info))
+
+    def select_statement(db: SQL.Database, name: String, columns: List[SQL.Column])
+        : PreparedStatement =
+      db.select_statement(table, columns, log_filename.sql_where_eq_string(name))
+  }
+
   def store(options: Options): Store = new Store(options)
 
   class Store private[Build_Log](options: Options) extends Properties.Store
@@ -598,5 +646,48 @@
           if (ssh_host == "") None
           else Some(SSH.init_context(options).open_session(ssh_host, ssh_user, port)))
     }
+
+    def compress_build_info(build_info: Build_Info, options: XZ.Options = XZ.options()): Bytes =
+      Bytes(YXML.string_of_body(Build_Info.encode(build_info))).compress(options)
+
+    def uncompress_build_info(bytes: Bytes): Build_Info =
+      Build_Info.decode(xml_cache.body(YXML.parse_body(bytes.uncompress().text)))
+
+    def write_infos(db: SQL.Database, files: List[JFile])
+    {
+      db.transaction {
+        db.create_table(Info.table)
+
+        val known_files =
+          using(db.select_statement(Info.table, List(Info.log_filename)))(stmt =>
+            SQL.iterator(stmt.executeQuery)(rs => db.string(rs, Info.log_filename)).toSet)
+
+        using(db.insert_statement(Info.table))(stmt =>
+        {
+          for (file <- files.toSet if !known_files(file.getName)) {
+            val log_file = Log_File(file)
+            val meta_info = log_file.parse_meta_info()
+            val build_info = log_file.parse_build_info()
+
+            stmt.clearParameters
+
+            db.set_string(stmt, 1, file.getName)
+
+            for ((c, i) <- Prop.columns.zipWithIndex) {
+              if (c.T == SQL.Type.Date)
+                db.set_date(stmt, i + 2, meta_info.get_date(c).orNull)
+              else
+                db.set_string(stmt, i + 2, meta_info.get(c).orNull)
+            }
+
+            val n = Info.table.columns.length
+            db.set_bytes(stmt, n - 1, encode_properties(meta_info.settings))
+            db.set_bytes(stmt, n, compress_build_info(build_info))
+
+            stmt.execute()
+          }
+        })
+      }
+    }
   }
 }