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)]
123mod tests {
124 use super::*;
125 use rust_decimal_macros::dec;
126
127 #[test]
128 fn test_write_csv_field_simple() {
129 let mut buf = Vec::new();
130 write_csv_field(&mut buf, "hello").unwrap();
131 assert_eq!(std::str::from_utf8(&buf).unwrap(), "hello");
132 }
133
134 #[test]
135 fn test_write_csv_field_with_comma() {
136 let mut buf = Vec::new();
137 write_csv_field(&mut buf, "hello,world").unwrap();
138 assert_eq!(std::str::from_utf8(&buf).unwrap(), "\"hello,world\"");
139 }
140
141 #[test]
142 fn test_write_csv_field_with_quote() {
143 let mut buf = Vec::new();
144 write_csv_field(&mut buf, "say \"hi\"").unwrap();
145 assert_eq!(std::str::from_utf8(&buf).unwrap(), "\"say \"\"hi\"\"\"");
146 }
147
148 #[test]
149 fn test_write_csv_int() {
150 let mut buf = Vec::new();
151 write_csv_int(&mut buf, 42i32).unwrap();
152 assert_eq!(std::str::from_utf8(&buf).unwrap(), "42");
153 }
154
155 #[test]
156 fn test_write_csv_int_negative() {
157 let mut buf = Vec::new();
158 write_csv_int(&mut buf, -123i64).unwrap();
159 assert_eq!(std::str::from_utf8(&buf).unwrap(), "-123");
160 }
161
162 #[test]
163 fn test_write_csv_decimal() {
164 let mut buf = Vec::new();
165 write_csv_decimal(&mut buf, &dec!(1234.56)).unwrap();
166 assert_eq!(std::str::from_utf8(&buf).unwrap(), "1234.56");
167 }
168
169 #[test]
170 fn test_write_csv_decimal_zero() {
171 let mut buf = Vec::new();
172 write_csv_decimal(&mut buf, &dec!(0.00)).unwrap();
173 assert_eq!(std::str::from_utf8(&buf).unwrap(), "0.00");
174 }
175
176 #[test]
177 fn test_write_csv_opt_field_some() {
178 let mut buf = Vec::new();
179 let val = Some("test".to_string());
180 write_csv_opt_field(&mut buf, &val).unwrap();
181 assert_eq!(std::str::from_utf8(&buf).unwrap(), "test");
182 }
183
184 #[test]
185 fn test_write_csv_opt_field_none() {
186 let mut buf = Vec::new();
187 write_csv_opt_field(&mut buf, &None).unwrap();
188 assert_eq!(std::str::from_utf8(&buf).unwrap(), "");
189 }
190
191 #[test]
192 fn test_write_csv_bool() {
193 let mut buf = Vec::new();
194 write_csv_bool(&mut buf, true).unwrap();
195 assert_eq!(std::str::from_utf8(&buf).unwrap(), "true");
196
197 let mut buf = Vec::new();
198 write_csv_bool(&mut buf, false).unwrap();
199 assert_eq!(std::str::from_utf8(&buf).unwrap(), "false");
200 }
201
202 #[test]
203 fn test_combined_row() {
204 let mut buf = Vec::new();
205 write_csv_field(&mut buf, "DOC001").unwrap();
206 write_sep(&mut buf).unwrap();
207 write_csv_int(&mut buf, 2024i32).unwrap();
208 write_sep(&mut buf).unwrap();
209 write_csv_decimal(&mut buf, &dec!(1500.00)).unwrap();
210 write_sep(&mut buf).unwrap();
211 write_csv_bool(&mut buf, false).unwrap();
212 write_newline(&mut buf).unwrap();
213
214 assert_eq!(
215 std::str::from_utf8(&buf).unwrap(),
216 "DOC001,2024,1500.00,false\n"
217 );
218 }
219}