src/Pure/General/file_store.scala
author wenzelm
Wed, 12 Feb 2025 15:22:47 +0100
changeset 82148 b387a9099b72
child 82149 18709ffb8137
permissions -rw-r--r--
support persistent store for file-system content (notably SQLite);
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
82148
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     1
/*  Title:      Pure/General/file_store.scala
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     2
    Author:     Makarius
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     3
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     4
Persistent store for file-system content, backed by SQL database
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     5
(notably SQLite).
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     6
*/
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     7
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     8
package isabelle
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
     9
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    10
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    11
import java.io.{File => JFile}
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    12
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    13
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    14
object File_Store {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    15
  /* main operations */
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    16
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    17
  def make_database(database: Path, dir: Path,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    18
    pred: JFile => Boolean = _ => true,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    19
    name_prefix: String = "",
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    20
    compress_options: Compress.Options = Compress.Options(),
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    21
    compress_cache: Compress.Cache = Compress.Cache.none
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    22
  ): Unit = {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    23
    database.file.delete()
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    24
    using(SQLite.open_database(database)) { db =>
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    25
      private_data.transaction_lock(db, create = true) {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    26
        val root = dir.canonical_file
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    27
        val root_path = File.path(root)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    28
        for {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    29
          file <- File.find_files(root, pred = pred)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    30
          rel_path <- File.relative_path(root_path, File.path(file))
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    31
        } {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    32
          val entry =
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    33
            Entry.read_file(rel_path, name_prefix = name_prefix, dir = root_path,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    34
              compress_options = compress_options, compress_cache = compress_cache)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    35
          private_data.write_entry(db, entry)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    36
        }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    37
      }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    38
    }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    39
  }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    40
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    41
  def database_entry(database: Path, name: String): Option[Entry] =
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    42
    if (!database.is_file) None
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    43
    else {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    44
      using(SQLite.open_database(database)) { db =>
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    45
        private_data.transaction_lock(db) {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    46
          if (!private_data.tables.forall(db.exists_table)) None
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    47
          else private_data.read_entry(db, name)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    48
        }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    49
      }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    50
    }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    51
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    52
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    53
  /* entry */
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    54
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    55
  object Entry {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    56
    def read_file(path: Path,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    57
      name_prefix: String = "",
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    58
      dir: Path = Path.current,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    59
      compress_options: Compress.Options = Compress.Options(),
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    60
      compress_cache: Compress.Cache = Compress.Cache.none
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    61
    ): Entry = {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    62
      val name = Url.append_path(name_prefix, path.expand.implode)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    63
      val bs = Bytes.read(dir + path)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    64
      val size = bs.size
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    65
      val executable = File.is_executable(dir + path)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    66
      val (compressed, body) = bs.maybe_compress(options = compress_options, cache = compress_cache)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    67
      Entry(name, size, executable, compressed, body)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    68
    }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    69
  }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    70
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    71
  sealed case class Entry(
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    72
    name: String,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    73
    size: Long,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    74
    executable: Boolean,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    75
    compressed: Boolean,
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    76
    body: Bytes
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    77
  ) {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    78
    require(name.nonEmpty && size >= 0 && (size > 0 || compressed))
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    79
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    80
    def content(cache: Compress.Cache = Compress.Cache.none): Bytes =
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    81
      if (compressed) body.uncompress(cache = cache) else body
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    82
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    83
    def write_file(dir: Path, cache: Compress.Cache = Compress.Cache.none): Unit = {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    84
      val path = Path.explode(name)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    85
      File.content(path, content(cache = cache)).write(dir)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    86
      if (executable) File.set_executable(dir + path)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    87
    }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    88
  }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    89
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    90
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    91
  /* SQL data model */
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    92
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    93
  object private_data extends SQL.Data() {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    94
    override lazy val tables: SQL.Tables = SQL.Tables(Base.table)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    95
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    96
    object Base {
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    97
      val name = SQL.Column.string("name").make_primary_key
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    98
      val size = SQL.Column.long("size")
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
    99
      val executable = SQL.Column.bool("executable")
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   100
      val compressed = SQL.Column.bool("compressed")
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   101
      val body = SQL.Column.bytes("body")
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   102
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   103
      val table = SQL.Table("isabelle_file_store", List(name, size, executable, compressed, body))
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   104
    }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   105
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   106
    def read_names(db: SQL.Database): List[String] =
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   107
      db.execute_query_statement(
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   108
        Base.table.select(List(Base.name)),
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   109
        List.from[String], _.string(Base.name)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   110
      ).sorted
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   111
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   112
    def read_entry(db: SQL.Database, name: String): Option[Entry] =
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   113
      db.execute_query_statementO[Entry](
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   114
        Base.table.select(List(Base.size, Base.executable, Base.compressed, Base.body),
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   115
          sql = Base.name.where_equal(name)),
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   116
        { res =>
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   117
          val size = res.long(Base.size)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   118
          val executable = res.bool(Base.executable)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   119
          val compressed = res.bool(Base.compressed)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   120
          val body = res.bytes(Base.body)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   121
          Entry(name, size, executable, compressed, body)
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   122
        })
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   123
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   124
    def write_entry(db: SQL.Database, entry: Entry): Unit =
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   125
      db.execute_statement(Base.table.insert(), body = { stmt =>
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   126
        stmt.string(1) = entry.name
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   127
        stmt.long(2) = entry.size
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   128
        stmt.bool(3) = entry.executable
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   129
        stmt.bool(4) = entry.compressed
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   130
        stmt.bytes(5) = entry.body
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   131
      })
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   132
  }
b387a9099b72 support persistent store for file-system content (notably SQLite);
wenzelm
parents:
diff changeset
   133
}