SDDS ToolKit Programs and Libraries for C and Python
Loading...
Searching...
No Matches
test_sdds3_advanced.cc
Go to the documentation of this file.
1/**
2 * @file test_sdds3_advanced.cc
3 * @brief Acceptance tests for selective I/O, custom streams, live files, and transformations.
4 *
5 * @details Covers projection pushdown, row slicing, page indexes, headerless
6 * streams, reconnect, file replacement, locking, live reads, update strategies,
7 * checked conversions, row masks, matrices, and transformed copies.
8 *
9 * @copyright
10 * - (c) 2026 The University of Chicago
11 *
12 * @license
13 * This file is distributed under the terms of the Software License Agreement
14 * found in the file LICENSE included with this distribution.
15 */
16
17#include "SDDS.hpp"
18
19#include <cassert>
20#include <cmath>
21#include <cstdio>
22#include <cstdint>
23#include <filesystem>
24#include <fstream>
25#include <limits>
26#include <sstream>
27#include <string>
28#include <vector>
29
30namespace {
31
32sdds::Layout makeLayout(sdds::DataMode mode = sdds::DataMode::Binary,
33 sdds::MajorOrder major = sdds::MajorOrder::Row,
34 sdds::RowCountMode rowCounts = sdds::RowCountMode::Variable) {
35 sdds::DataOptions data;
36 data.mode = mode;
37 data.majorOrder = major;
38 data.rowCountMode = rowCounts;
39 sdds::LayoutBuilder builder;
40 builder.setDataOptions(data)
41 .addParameter({{"p", std::nullopt, "count", std::nullopt, std::nullopt,
42 sdds::Type::Int64}, std::nullopt})
43 .addArray({{"a", std::nullopt, "m", std::nullopt, std::nullopt,
44 sdds::Type::Double}, 0, 1, std::nullopt})
45 .addColumn({{"x", std::nullopt, std::nullopt, std::nullopt, std::nullopt,
46 sdds::Type::Int32}, 0})
47 .addColumn({{"y", std::nullopt, "m", std::nullopt, std::nullopt,
48 sdds::Type::Double}, 0})
49 .addColumn({{"label", std::nullopt, std::nullopt, std::nullopt, std::nullopt,
50 sdds::Type::String}, 0});
51 return builder.build();
52}
53
54sdds::Page makePage(const std::shared_ptr<const sdds::Layout> &layout) {
55 sdds::Page page(layout);
56 page.setParameter("p", std::int64_t{7});
57 page.setArray("a", {{3}, std::vector<double>{1.0, 2.0, 3.0}});
58 page.setColumn("x", std::vector<std::int32_t>{1, 2, 3, 4, 5});
59 page.setColumn("y", std::vector<double>{0.5, 1.5, 2.5, 3.5, 4.5});
60 page.setColumn("label", std::vector<std::string>{"one", "two", "three", "four", "five"});
61 return page;
62}
63
64void writeOnePage(const std::filesystem::path &path, const sdds::Layout &layout,
65 sdds::WriterOptions options = {}) {
66 auto shared = std::make_shared<const sdds::Layout>(layout);
67 auto writer = sdds::Writer::create(path, layout, options);
68 writer.write(makePage(shared));
69 writer.close();
70}
71
72void selectiveIoAndIndex(const std::filesystem::path &path,
73 const std::filesystem::path &asciiPath) {
74 writeOnePage(path, makeLayout());
75 auto reader = sdds::Reader::open(path);
76 sdds::ReadRequest request;
77 request.parameters = sdds::FieldSelection::only({"p"});
78 request.arrays = sdds::FieldSelection::noFields();
79 request.columns = sdds::FieldSelection::only({"y"});
80 request.rows.first = 1;
81 request.rows.count = 2;
82 request.rows.stride = 2;
83 auto page = reader.next(request);
84 assert(page && page->rowCount() == 2);
85 assert(page->parameterLoaded("p"));
86 assert(!page->arrayLoaded("a"));
87 assert(!page->columnLoaded("x"));
88 assert(page->columnLoaded("y"));
89 assert((page->columnAs<double>("y") == std::vector<double>{1.5, 3.5}));
90 bool threw = false;
91 try {
92 (void)page->column("x");
93 } catch (const sdds::StateError &) {
94 threw = true;
95 }
96 assert(threw);
97 reader.close();
98
99 auto layout = makeLayout(sdds::DataMode::Ascii, sdds::MajorOrder::Row,
100 sdds::RowCountMode::None);
101 writeOnePage(asciiPath, layout);
102 reader = sdds::Reader::open(asciiPath);
103 request = {};
104 request.parameters = sdds::FieldSelection::noFields();
105 request.arrays = sdds::FieldSelection::noFields();
106 request.columns = sdds::FieldSelection::only({"x"});
107 request.rows.last = 2;
108 page = reader.next(request);
109 assert(page && (page->columnAs<std::int32_t>("x") ==
110 std::vector<std::int32_t>{4, 5}));
111 reader.close();
112
113 auto twoPageWriter = sdds::Writer::append(path);
114 twoPageWriter.write(makePage(std::make_shared<const sdds::Layout>(makeLayout())));
115 twoPageWriter.close();
116 reader = sdds::Reader::open(path);
117 reader.buildPageIndex();
118 assert(reader.indexedPageCount() == 2);
119 reader.gotoPage(2);
120 page = reader.next();
121 assert(page && page->number() == 2);
122 reader.close();
123}
124
125void memoryAndStreamIo() {
126 const auto layout = makeLayout();
127 auto shared = std::make_shared<const sdds::Layout>(layout);
128 for (const auto compression : {sdds::Compression::None, sdds::Compression::Gzip,
129 sdds::Compression::Xz, sdds::Compression::Lzma}) {
130 std::vector<std::uint8_t> bytes;
131 sdds::WriterOptions writerOptions;
132 writerOptions.compression = compression;
133 writerOptions.gzipLevel = 1;
134 writerOptions.lzmaPreset = 1;
135 auto writer = sdds::Writer::toSink(sdds::outputToMemory(bytes), layout, "memory",
136 writerOptions);
137 writer.write(makePage(shared));
138 writer.close();
139 assert(!bytes.empty());
140
141 sdds::ReaderOptions readerOptions;
142 readerOptions.compression = compression;
143 auto reader = sdds::Reader::fromSource(sdds::inputFromMemory(bytes.data(), bytes.size()),
144 "memory", readerOptions);
145 auto page = reader.next();
146 assert(page && page->columnAs<std::int32_t>("x")[4] == 5);
147 reader.close();
148 }
149
150 std::stringstream stream(std::ios::in | std::ios::out | std::ios::binary);
151 auto writer = sdds::Writer::toSink(sdds::outputToStream(stream), layout, "stringstream");
152 writer.write(makePage(shared));
153 writer.close();
154 stream.seekg(0);
155 auto reader = sdds::Reader::fromSource(sdds::inputFromStream(stream), "stringstream");
156 assert(reader.next());
157 reader.close();
158}
159
160void sourceCapabilities(const std::filesystem::path &inputPath,
161 const std::filesystem::path &outputPath) {
162 auto source = sdds::inputFromPath(inputPath);
163 const auto inputCapabilities = source->capabilities();
164 assert(inputCapabilities.read && inputCapabilities.seek && inputCapabilities.reopen);
165 std::uint8_t bytes[4]{};
166 assert(source->read(bytes, sizeof(bytes)) == sizeof(bytes));
167 source->close();
168 source->reopen();
169 assert(source->tell() == sizeof(bytes));
170 source->close();
171
172 FILE *borrowedInput = std::fopen(inputPath.string().c_str(), "rb");
173 assert(borrowedInput);
174 auto fileSource = sdds::inputFromFile(borrowedInput);
175 assert(fileSource->read(bytes, sizeof(bytes)) == sizeof(bytes));
176 fileSource->close();
177 assert(std::fseek(borrowedInput, 0, SEEK_SET) == 0);
178 std::fclose(borrowedInput);
179
180 auto sink = sdds::outputToPath(outputPath);
181 const auto outputCapabilities = sink->capabilities();
182 assert(outputCapabilities.write && outputCapabilities.seek && outputCapabilities.truncate &&
183 outputCapabilities.flush && outputCapabilities.sync && outputCapabilities.reopen);
184 const char first[] = "abc";
185 sink->write(first, sizeof(first) - 1);
186 sink->close();
187 sink->reopen();
188 const char second[] = "def";
189 sink->write(second, sizeof(second) - 1);
190 sink->close();
191 assert(std::filesystem::file_size(outputPath) == 6);
192
193 FILE *borrowedOutput = std::fopen(outputPath.string().c_str(), "wb+");
194 assert(borrowedOutput);
195 const auto layout = makeLayout();
196 auto writer = sdds::Writer::toSink(sdds::outputToFile(borrowedOutput), layout,
197 "borrowed-FILE");
198 writer.write(makePage(std::make_shared<const sdds::Layout>(layout)));
199 writer.close();
200 assert(std::fseek(borrowedOutput, 0, SEEK_SET) == 0);
201 char header[5]{};
202 assert(std::fread(header, 1, 5, borrowedOutput) == 5);
203 assert(std::string(header, 5) == "SDDS5");
204 std::fclose(borrowedOutput);
205}
206
207void headerlessIo(const std::filesystem::path &path) {
208 sdds::DataOptions data;
209 data.mode = sdds::DataMode::Ascii;
210 sdds::LayoutBuilder builder;
211 builder.setDataOptions(data)
212 .addColumn({{"x", std::nullopt, std::nullopt, std::nullopt, std::nullopt,
213 sdds::Type::Int32}, 0});
214 const auto layout = builder.build();
215 {
216 std::ofstream output(path, std::ios::binary);
217 output << "3\n1\n2\n3\n";
218 }
219 auto reader = sdds::Reader::openHeaderless(path, layout);
220 auto page = reader.next();
221 assert(page && (page->columnAs<std::int32_t>("x") ==
222 std::vector<std::int32_t>{1, 2, 3}));
223 reader.close();
224}
225
226void disconnectReconnectAndLocks(const std::filesystem::path &path,
227 const std::filesystem::path &replacement,
228 const std::filesystem::path &lockPath,
229 const std::filesystem::path &saved) {
230 writeOnePage(path, makeLayout());
231 auto reader = sdds::Reader::open(path);
232 reader.disconnect();
233 reader.reconnect();
234 assert(reader.next());
235 reader.close();
236
237 auto writer = sdds::Writer::create(replacement, makeLayout());
238 writer.disconnect();
239 writer.reconnect();
240 writer.write(makePage(std::make_shared<const sdds::Layout>(makeLayout())));
241 writer.close();
242
243 writer = sdds::Writer::create(lockPath, makeLayout());
244 bool threw = false;
245 try {
246 auto conflicting = sdds::Writer::append(lockPath);
247 conflicting.close();
248 } catch (const sdds::IoError &) {
249 threw = true;
250 }
251 assert(threw);
252 writer.close();
253
254 reader = sdds::Reader::open(path);
255 reader.disconnect();
256 std::filesystem::rename(path, saved);
257 std::filesystem::rename(replacement, path);
258 threw = false;
259 try {
260 reader.reconnect();
261 } catch (const sdds::StateError &) {
262 threw = true;
263 }
264 assert(threw);
265 reader.close();
266}
267
268sdds::Layout liveLayout(sdds::MajorOrder major = sdds::MajorOrder::Row) {
269 sdds::DataOptions data;
270 data.mode = sdds::DataMode::Binary;
271 data.majorOrder = major;
272 sdds::LayoutBuilder builder;
273 builder.setDataOptions(data)
274 .addColumn({{"x", std::nullopt, std::nullopt, std::nullopt, std::nullopt,
275 sdds::Type::Int32}, 0});
276 return builder.build();
277}
278
279void liveRowsAndUpdateStrategies(const std::filesystem::path &path,
280 const std::filesystem::path &columnMajorPath,
281 const std::filesystem::path &compressedPath) {
282 auto layout = liveLayout();
283 auto writer = sdds::Writer::create(path, layout);
284 writer.beginPage(2);
285 writer.setColumn("x", std::vector<std::int32_t>{1, 2});
286 writer.commitPage();
287 writer.close();
288
289 auto follower = sdds::Reader::open(path);
290 assert(follower.next());
291 assert(!follower.next());
292 writer = sdds::Writer::appendToLastPage(path, 1);
293 writer.setColumn("x", std::vector<std::int32_t>{3, 4}, 2);
294 writer.updatePage(true);
295 writer.close();
296 auto delta = follower.readNewRows();
297 assert(delta && delta->pageNumber == 1 && delta->firstRow == 2);
298 assert((delta->page.columnAs<std::int32_t>("x") == std::vector<std::int32_t>{3, 4}));
299 assert(!follower.readNewRows());
300 follower.close();
301
302 layout = liveLayout(sdds::MajorOrder::Column);
303 writer = sdds::Writer::create(columnMajorPath, layout);
304 writer.beginPage(2);
305 writer.setColumn("x", std::vector<std::int32_t>{1, 2});
306 writer.commitPage();
307 writer.close();
308 sdds::WriterOptions options;
309 options.updateStrategy = sdds::UpdateStrategy::InPlaceOnly;
310 bool threw = false;
311 try {
312 writer = sdds::Writer::appendToLastPage(columnMajorPath, 1, options);
313 } catch (const sdds::StateError &) {
314 threw = true;
315 }
316 assert(threw);
317 options.updateStrategy = sdds::UpdateStrategy::AtomicRewrite;
318 writer = sdds::Writer::appendToLastPage(columnMajorPath, 1, options);
319 writer.setColumn("x", std::vector<std::int32_t>{3}, 2);
320 writer.updatePage(true);
321 writer.close();
322 auto reader = sdds::Reader::open(columnMajorPath);
323 auto page = reader.next();
324 assert(page && (page->columnAs<std::int32_t>("x") ==
325 std::vector<std::int32_t>{1, 2, 3}));
326 reader.close();
327
328 layout = liveLayout();
329 writer = sdds::Writer::create(compressedPath, layout);
330 writer.beginPage(2);
331 writer.setColumn("x", std::vector<std::int32_t>{1, 2});
332 writer.commitPage();
333 writer.close();
334 writer = sdds::Writer::appendToLastPage(compressedPath, 1);
335 writer.setColumn("x", std::vector<std::int32_t>{3}, 2);
336 writer.updatePage(true);
337 writer.close();
338 reader = sdds::Reader::open(compressedPath);
339 page = reader.next();
340 assert(page && (page->columnAs<std::int32_t>("x") ==
341 std::vector<std::int32_t>{1, 2, 3}));
342 reader.close();
343}
344
345void transformations(const std::filesystem::path &input,
346 const std::filesystem::path &output) {
347 writeOnePage(input, makeLayout());
348 auto reader = sdds::Reader::open(input);
349 auto page = reader.next();
350 assert(page);
351 assert(page->row(2).get<std::int32_t>("x") == 3);
352 auto high = page->matchRows("x", [](const sdds::Scalar &value) {
353 return std::get<std::int32_t>(value) >= 3;
354 });
355 auto odd = page->matchRows<std::int32_t>("x", [](std::int32_t value) {
356 return value % 2 != 0;
357 });
358 auto filtered = page->filtered(high & odd);
359 assert((filtered.columnAs<std::int32_t>("x") == std::vector<std::int32_t>{3, 5}));
360 auto matrix = page->matrix<double>({"x", "y"}, sdds::ConversionMode::Checked);
361 assert(matrix.rows == 5 && matrix.columns == 2);
362 assert(matrix(3, 0) == 4.0 && matrix(3, 1) == 3.5);
363 auto converted = sdds::convertUnits(*page, sdds::FieldKind::Column, "y", "cm", 100.0L);
364 assert(converted.layout().columns[1].units == std::optional<std::string>("cm"));
365 assert(converted.columnAs<double>("y")[1] == 150.0);
366
367 bool threw = false;
368 try {
369 (void)sdds::convertScalar(std::numeric_limits<std::uint64_t>::max(), sdds::Type::Int32);
370 } catch (const sdds::TypeError &) {
371 threw = true;
372 }
373 assert(threw);
374 threw = false;
375 try {
376 (void)sdds::convertScalar(1.5, sdds::Type::Int32);
377 } catch (const sdds::TypeError &) {
378 threw = true;
379 }
380 assert(threw);
381 assert(std::get<std::int32_t>(sdds::convertScalar(
382 1.5, sdds::Type::Int32, sdds::RoundingMode::Nearest)) == 2);
383
384 sdds::LayoutEditor editor(makeLayout());
385 const auto edited = editor.renameColumn("y", "distance").dropColumn("label").build();
386 assert(edited.columns.size() == 2 && edited.columns[1].name == "distance");
387 reader.close();
388
389 reader = sdds::Reader::open(input);
390 sdds::CopyOptions copy;
391 copy.transformLayout = [](const sdds::Layout &layout) {
392 return sdds::LayoutEditor(layout).renameColumn("y", "distance").build();
393 };
394 sdds::copyDataset(reader, output, copy);
395 reader.close();
396 reader = sdds::Reader::open(output);
397 page = reader.next();
398 assert(page && page->columnAs<double>("distance")[4] == 4.5);
399 reader.close();
400}
401
402void limitsAndMalformedProjection(const std::filesystem::path &path,
403 const std::filesystem::path &truncated) {
404 writeOnePage(path, makeLayout());
405 sdds::ReaderOptions options;
406 options.limits.maxProjectedFields = 1;
407 bool threw = false;
408 try {
409 auto reader = sdds::Reader::open(path, options);
410 (void)reader.next();
411 } catch (const sdds::LimitError &) {
412 threw = true;
413 }
414 assert(threw);
415
416 options = {};
417 options.limits.maxTransformationElements = 4;
418 auto reader = sdds::Reader::open(path, options);
419 auto page = reader.next();
420 threw = false;
421 try {
422 (void)page->matrix<double>({"x"}, sdds::ConversionMode::Checked);
423 } catch (const sdds::LimitError &) {
424 threw = true;
425 }
426 assert(threw);
427 reader.close();
428
429 std::filesystem::copy_file(path, truncated,
430 std::filesystem::copy_options::overwrite_existing);
431 std::filesystem::resize_file(truncated, std::filesystem::file_size(truncated) - 1);
432 reader = sdds::Reader::open(truncated);
433 sdds::ReadRequest request;
434 request.parameters = sdds::FieldSelection::noFields();
435 request.arrays = sdds::FieldSelection::noFields();
436 request.columns = sdds::FieldSelection::noFields();
437 threw = false;
438 try {
439 (void)reader.next(request);
440 } catch (const sdds::FormatError &error) {
441 threw = true;
442 assert(error.path() == truncated);
443 assert(error.page() == 1);
444 assert(error.offset().has_value());
445 assert(error.row().has_value());
446 assert(error.field() == std::optional<std::string>("label"));
447 }
448 assert(threw);
449 reader.close();
450}
451
452} // namespace
453
454int main(int argc, char **argv) {
455 assert(sdds::cppApiVersion == 2);
456 if (argc != 2) return 2;
457 const std::filesystem::path directory(argv[1]);
458 std::filesystem::create_directories(directory);
459 selectiveIoAndIndex(directory / "selective.sdds", directory / "last-rows.sdds");
460 memoryAndStreamIo();
461 sourceCapabilities(directory / "selective.sdds", directory / "raw-output.bin");
462 headerlessIo(directory / "headerless.dat");
463 disconnectReconnectAndLocks(directory / "reconnect.sdds",
464 directory / "replacement.sdds",
465 directory / "locked.sdds",
466 directory / "saved.sdds");
467 liveRowsAndUpdateStrategies(directory / "live.sdds", directory / "column-update.sdds",
468 directory / "compressed-update.sdds.gz");
469 transformations(directory / "transform-input.sdds", directory / "transform-output.sdds");
470 limitsAndMalformedProjection(directory / "limits.sdds", directory / "truncated.sdds");
471 return 0;
472}