///|
fn little_integer(bytes : Bytes, width : Int) -> Int64? {
  if bytes.length() != width {
    return None
  }
  let mut n = 0L
  for i in 0.. Scalar? {
  match field.field_type {
    String("int" | "date") => little_integer(bytes, 4).map(n => Integer(n))
    String("long" | "time" | "timestamp" | "timestamptz") =>
      little_integer(bytes, 8).map(n => Integer(n))
    String("double") =>
      little_integer(bytes, 8).map(n => Real(n.reinterpret_as_double()))
    String("string") => {
      let s = @utf8.decode(bytes) catch { _ => return None }
      Some(Text(s))
    }
    String("boolean") =>
      if bytes.length() == 1 {
        Some(Boolean(bytes[0] != 0))
      } else {
        None
      }
    String("binary") => Some(Binary(bytes))
    // Unknown or unsupported encodings retain the file, including float bounds.
    _ => None
  }
}

///|
/// Return an exclusion proof, never a guess that a file is irrelevant.
fn metrics_exclusion(
  file : DataFile,
  schema : Schema,
  p : Predicate,
) -> String? {
  match p {
    All => None
    And(a, b) =>
      match metrics_exclusion(file, schema, a) {
        Some(r) => Some(r)
        None => metrics_exclusion(file, schema, b)
      }
    Or(a, b) =>
      match
        (metrics_exclusion(file, schema, a), metrics_exclusion(file, schema, b)) {
        (Some(a), Some(b)) => Some("Both OR branches excluded: \{a}; \{b}")
        _ => None
      }
    IsNull(id) =>
      if file.null_counts.get(id) == Some(0L) {
        Some("field \{id}: null_count = 0")
      } else {
        None
      }
    NotNull(id) =>
      if file.null_counts.get(id) == Some(file.record_count) {
        Some("field \{id}: every row is null")
      } else {
        None
      }
    Compare(id, op, value) => {
      if file.null_counts.get(id) == Some(file.record_count) {
        return Some("field \{id}: every row is null")
      }
      let field = match schema.field(id) {
        Some(f) => f
        None => return None
      }
      // Floating-point ordering with NaNs needs more information than min/max.
      if field.field_type is String("float" | "double") &&
        file.nan_counts.get(id) != Some(0L) {
        return None
      }
      let lower = file.lower_bounds.get(id).bind(b => decode_bound(b, field))
      let upper = file.upper_bounds.get(id).bind(b => decode_bound(b, field))
      if (lower, upper) is (Some(lo), Some(hi)) {
        if scalar_compare(lo, hi) is Some(c) && c > 0 {
          return None
        }
      }
      let lo = lower.bind(v => scalar_compare(v, value))
      let hi = upper.bind(v => scalar_compare(v, value))
      let below = match (op, lo) {
        (Eq | Lt, Some(c)) => c > 0 || (op == Lt && c == 0)
        (Le, Some(c)) => c > 0
        _ => false
      }
      let above = match (op, hi) {
        (Eq | Gt, Some(c)) => c < 0 || (op == Gt && c == 0)
        (Ge, Some(c)) => c < 0
        _ => false
      }
      if below {
        Some("field \{id}: lower bound excludes the predicate")
      } else if above {
        Some("field \{id}: upper bound excludes the predicate")
      } else {
        None
      }
    }
  }
}