datasynth_output/
fast_csv.rs1use std::io::Write;
8
9#[inline]
14pub fn write_csv_field<W: Write>(w: &mut W, s: &str) -> std::io::Result<()> {
15 if s.contains(',') || s.contains('"') || s.contains('\n') {
16 w.write_all(b"\"")?;
17 for byte in s.as_bytes() {
18 if *byte == b'"' {
19 w.write_all(b"\"\"")?;
20 } else {
21 w.write_all(std::slice::from_ref(byte))?;
22 }
23 }
24 w.write_all(b"\"")?;
25 } else {
26 w.write_all(s.as_bytes())?;
27 }
28 Ok(())
29}
30
31#[inline]
33pub fn write_csv_opt_field<W: Write>(w: &mut W, opt: &Option<String>) -> std::io::Result<()> {
34 match opt {
35 Some(s) => write_csv_field(w, s),
36 None => Ok(()),
37 }
38}
39
40#[inline]
42pub fn write_csv_int<W: Write, I: itoa::Integer>(w: &mut W, val: I) -> std::io::Result<()> {
43 let mut buf = itoa::Buffer::new();
44 w.write_all(buf.format(val).as_bytes())
45}
46
47#[inline]
49pub fn write_csv_float<W: Write, F: ryu::Float>(w: &mut W, val: F) -> std::io::Result<()> {
50 let mut buf = ryu::Buffer::new();
51 w.write_all(buf.format(val).as_bytes())
52}
53
54#[inline]
58pub fn write_csv_decimal<W: Write>(w: &mut W, val: &rust_decimal::Decimal) -> std::io::Result<()> {
59 use std::fmt::Write as FmtWrite;
61 let mut buf = DecimalBuffer::new();
62 let _ = write!(buf, "{val}");
64 w.write_all(buf.as_bytes())
65}
66
67#[inline]
69pub fn write_sep<W: Write>(w: &mut W) -> std::io::Result<()> {
70 w.write_all(b",")
71}
72
73#[inline]
75pub fn write_newline<W: Write>(w: &mut W) -> std::io::Result<()> {
76 w.write_all(b"\n")
77}
78
79#[inline]
81pub fn write_csv_bool<W: Write>(w: &mut W, val: bool) -> std::io::Result<()> {
82 w.write_all(if val { b"true" } else { b"false" })
83}
84
85struct DecimalBuffer {
89 buf: [u8; 48],
90 len: usize,
91}
92
93impl DecimalBuffer {
94 #[inline]
95 fn new() -> Self {
96 Self {
97 buf: [0u8; 48],
98 len: 0,
99 }
100 }
101
102 #[inline]
103 fn as_bytes(&self) -> &[u8] {
104 &self.buf[..self.len]
105 }
106}
107
108impl std::fmt::Write for DecimalBuffer {
109 #[inline]
110 fn write_str(&mut self, s: &str) -> std::fmt::Result {
111 let bytes = s.as_bytes();
112 let remaining = self.buf.len() - self.len;
113 if bytes.len() > remaining {
114 return Err(std::fmt::Error);
115 }
116 self.buf[self.len..self.len + bytes.len()].copy_from_slice(bytes);
117 self.len += bytes.len();
118 Ok(())
119 }
120}
121
122#[cfg(test)]
123#[allow(clippy::unwrap_used)]
124mod tests {
125 use super::*;
126 use rust_decimal_macros::dec;
127
128 #[test]
129 fn test_write_csv_field_simple() {
130 let mut buf = Vec::new();
131 write_csv_field(&mut buf, "hello").unwrap();
132 assert_eq!(std::str::from_utf8(&buf).unwrap(), "hello");
133 }
134
135 #[test]
136 fn test_write_csv_field_with_comma() {
137 let mut buf = Vec::new();
138 write_csv_field(&mut buf, "hello,world").unwrap();
139 assert_eq!(std::str::from_utf8(&buf).unwrap(), "\"hello,world\"");
140 }
141
142 #[test]
143 fn test_write_csv_field_with_quote() {
144 let mut buf = Vec::new();
145 write_csv_field(&mut buf, "say \"hi\"").unwrap();
146 assert_eq!(std::str::from_utf8(&buf).unwrap(), "\"say \"\"hi\"\"\"");
147 }
148
149 #[test]
150 fn test_write_csv_int() {
151 let mut buf = Vec::new();
152 write_csv_int(&mut buf, 42i32).unwrap();
153 assert_eq!(std::str::from_utf8(&buf).unwrap(), "42");
154 }
155
156 #[test]
157 fn test_write_csv_int_negative() {
158 let mut buf = Vec::new();
159 write_csv_int(&mut buf, -123i64).unwrap();
160 assert_eq!(std::str::from_utf8(&buf).unwrap(), "-123");
161 }
162
163 #[test]
164 fn test_write_csv_decimal() {
165 let mut buf = Vec::new();
166 write_csv_decimal(&mut buf, &dec!(1234.56)).unwrap();
167 assert_eq!(std::str::from_utf8(&buf).unwrap(), "1234.56");
168 }
169
170 #[test]
171 fn test_write_csv_decimal_zero() {
172 let mut buf = Vec::new();
173 write_csv_decimal(&mut buf, &dec!(0.00)).unwrap();
174 assert_eq!(std::str::from_utf8(&buf).unwrap(), "0.00");
175 }
176
177 #[test]
178 fn test_write_csv_opt_field_some() {
179 let mut buf = Vec::new();
180 let val = Some("test".to_string());
181 write_csv_opt_field(&mut buf, &val).unwrap();
182 assert_eq!(std::str::from_utf8(&buf).unwrap(), "test");
183 }
184
185 #[test]
186 fn test_write_csv_opt_field_none() {
187 let mut buf = Vec::new();
188 write_csv_opt_field(&mut buf, &None).unwrap();
189 assert_eq!(std::str::from_utf8(&buf).unwrap(), "");
190 }
191
192 #[test]
193 fn test_write_csv_bool() {
194 let mut buf = Vec::new();
195 write_csv_bool(&mut buf, true).unwrap();
196 assert_eq!(std::str::from_utf8(&buf).unwrap(), "true");
197
198 let mut buf = Vec::new();
199 write_csv_bool(&mut buf, false).unwrap();
200 assert_eq!(std::str::from_utf8(&buf).unwrap(), "false");
201 }
202
203 #[test]
204 fn test_combined_row() {
205 let mut buf = Vec::new();
206 write_csv_field(&mut buf, "DOC001").unwrap();
207 write_sep(&mut buf).unwrap();
208 write_csv_int(&mut buf, 2024i32).unwrap();
209 write_sep(&mut buf).unwrap();
210 write_csv_decimal(&mut buf, &dec!(1500.00)).unwrap();
211 write_sep(&mut buf).unwrap();
212 write_csv_bool(&mut buf, false).unwrap();
213 write_newline(&mut buf).unwrap();
214
215 assert_eq!(
216 std::str::from_utf8(&buf).unwrap(),
217 "DOC001,2024,1500.00,false\n"
218 );
219 }
220}