use std::fs::File;
use std::io;
use std::io::Write;
use std::path::Path;
use crate::SQLOG_EXT;
#[cfg(feature = "gzip")]
use crate::SQLOG_GZ_EXT;
#[cfg(feature = "zstd")]
use crate::SQLOG_ZST_EXT;
pub type QlogFileWriter = Box<dyn Write + Send + Sync>;
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
PartialEq,
serde::Deserialize,
serde::Serialize,
)]
#[serde(rename_all = "snake_case")]
pub enum QlogCompression {
#[default]
None,
#[cfg(feature = "gzip")]
Gzip,
#[cfg(feature = "zstd")]
Zstd,
}
#[cfg(feature = "foundations")]
impl foundations::settings::Settings for QlogCompression {}
pub fn qlog_file_name(id: &str, compression: QlogCompression) -> String {
match compression {
QlogCompression::None => format!("{id}{SQLOG_EXT}"),
#[cfg(feature = "gzip")]
QlogCompression::Gzip => format!("{id}{SQLOG_GZ_EXT}"),
#[cfg(feature = "zstd")]
QlogCompression::Zstd => format!("{id}{SQLOG_ZST_EXT}"),
}
}
pub fn make_qlog_writer<W>(
inner: W, compression: QlogCompression,
) -> io::Result<QlogFileWriter>
where
W: Write + Send + Sync + 'static,
{
match compression {
QlogCompression::None => Ok(Box::new(inner)),
#[cfg(feature = "gzip")]
QlogCompression::Gzip => {
let encoder = flate2::write::GzEncoder::new(
inner,
flate2::Compression::default(),
);
Ok(Box::new(encoder))
},
#[cfg(feature = "zstd")]
QlogCompression::Zstd => {
let encoder = zstd::Encoder::new(inner, 3)?;
Ok(Box::new(ZstdFinishOnDrop {
encoder: Some(encoder),
}))
},
}
}
pub fn make_qlog_writer_from_path<P: AsRef<Path>>(
path: P, compression: QlogCompression,
) -> io::Result<QlogFileWriter> {
let file = File::create(path.as_ref())?;
make_qlog_writer(file, compression)
}
#[cfg(feature = "zstd")]
struct ZstdFinishOnDrop<W: Write> {
encoder: Option<zstd::Encoder<'static, W>>,
}
#[cfg(feature = "zstd")]
impl<W: Write> Write for ZstdFinishOnDrop<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.encoder
.as_mut()
.expect("encoder present until drop")
.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.encoder
.as_mut()
.expect("encoder present until drop")
.flush()
}
}
#[cfg(feature = "zstd")]
impl<W: Write> Drop for ZstdFinishOnDrop<W> {
fn drop(&mut self) {
if let Some(encoder) = self.encoder.take() {
if let Err(error) = encoder.finish() {
eprintln!("qlog: failed to finish zstd encoder: {error}");
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_name_uses_constants_for_none() {
let name = qlog_file_name("abc", QlogCompression::None);
assert_eq!(name, "abc.sqlog");
assert!(name.ends_with(SQLOG_EXT));
}
#[cfg(feature = "gzip")]
#[test]
fn file_name_uses_constants_for_gzip() {
let name = qlog_file_name("abc", QlogCompression::Gzip);
assert_eq!(name, "abc.sqlog.gz");
assert!(name.ends_with(SQLOG_GZ_EXT));
}
#[cfg(feature = "zstd")]
#[test]
fn file_name_uses_constants_for_zstd() {
let name = qlog_file_name("abc", QlogCompression::Zstd);
assert_eq!(name, "abc.sqlog.zst");
assert!(name.ends_with(SQLOG_ZST_EXT));
}
}