diff options
Diffstat (limited to 'gleam/src/ocda/load_db.gleam')
| -rw-r--r-- | gleam/src/ocda/load_db.gleam | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/gleam/src/ocda/load_db.gleam b/gleam/src/ocda/load_db.gleam index 641923e..18a5335 100644 --- a/gleam/src/ocda/load_db.gleam +++ b/gleam/src/ocda/load_db.gleam @@ -112,6 +112,7 @@ pub fn load_file( ) let document = metadata(conn, chosen.doc_id, document) let document = objects(conn, chosen.doc_id, document) + let document = count_unread(conn, chosen.doc_id, document) Ok(document) } @@ -561,3 +562,108 @@ fn json_joined(value: String) -> String { parts -> string.join(parts, "|") } } + +/// What the database holds and the model does not. +/// +/// The text loader meets every property line and counts those it does not +/// keep, so a reader can say what was read and not understood. This loader +/// asks for columns by name, and would never see the ones it did not ask +/// for: it would report that it had understood everything, which is the +/// silence the count exists to prevent. So it counts them, on the +/// conditions under which the `.ssp` writes the same property +/// (specification section 6.3), and the report is the same from either +/// form, as the D loader's is. +fn count_unread( + conn: sqlight.Connection, + doc_id: Int, + document: Document, +) -> Document { + let id = int.to_string(doc_id) + let counts = [ + #("ancestors_collapsed", "coalesce(ancestors_collapsed,'') != ''"), + #("dom_status", "coalesce(dom_status,'') != ''"), + #("dom_status_collapsed", "coalesce(dom_status_collapsed,'') != ''"), + #("heading_lev_collapsed", "heading_lev_collapsed is not null"), + #("parent_lev", "coalesce(parent_lev,0) != 0"), + #("segment_lv4_is", "coalesce(segment_lv4_is,'') != ''"), + #("attrib", "coalesce(attrib,'') != ''"), + ] + let property = + list.fold(counts, document.unknown.property, fn(acc, c) { + let n = + count( + conn, + "select count(*) from objects where doc_id = " <> id <> " and " <> c.1, + ) + case n > 0 { + True -> dict.insert(acc, c.0, n) + False -> acc + } + }) + let property = case has(conn, "object_subtoc") { + False -> property + True -> { + let n = + count( + conn, + "select count(*) from object_subtoc s join objects o + on o.id = s.object_id where o.doc_id = " <> id, + ) + case n > 0 { + True -> dict.insert(property, "lev4_subtoc", n) + False -> property + } + } + } + model.Document( + ..document, + unknown: model.Unknown(..document.unknown, property: property), + ) +} + +fn count(conn: sqlight.Connection, sql: String) -> Int { + let d = decode.field(0, decode.int, decode.success) + case sqlight.query(sql, on: conn, with: [], expecting: d) { + Ok([n, ..]) -> n + _ -> 0 + } +} + +/// Every language the database at `path` holds, in manifest order. +pub fn languages_of_file(path: String) -> Result(List(Language), String) { + use conn <- with_connection("file:" <> path <> "?mode=ro", path) + Ok(languages(conn)) +} + +/// What the database at `path` carries besides the abstraction, by role +/// and storage: role, compression, count, original bytes, stored bytes. +pub fn carried_of_file(path: String) -> Result(List(List(String)), String) { + use conn <- with_connection("file:" <> path <> "?mode=ro", path) + case has(conn, "files") { + False -> Ok([]) + True -> { + let d = { + use role <- decode.field(0, decode.string) + use compression <- decode.field(1, decode.string) + use n <- decode.field(2, decode.int) + use bytes <- decode.field(3, decode.int) + use stored <- decode.field(4, decode.int) + decode.success([ + role, + compression, + int.to_string(n), + int.to_string(bytes), + int.to_string(stored), + ]) + } + sqlight.query( + "select role, compression, count(*), sum(bytes), sum(length(data)) + from files group by role, compression order by role", + on: conn, + with: [], + expecting: d, + ) + |> result.map_error(fn(e) { path <> ": " <> e.message }) + } + } +} |
