myTinySQL v0.2.0-2-g5000098
A tiny SQL interpreter over fixed-width text files
Loading...
Searching...
No Matches
TableFile.cpp
Go to the documentation of this file.
1
3
4#include "TableFile.hpp"
5
6#include <algorithm>
7#include <format>
8#include <fstream>
9
10#include "Util.hpp"
11
12namespace tinysql {
13
14namespace {
15
20void checkFits(const Column& column, const std::string& value)
21{
22 if (value.size() > column.width()) {
23 throw SqlError(std::format("value '{}' is too long for column {} (max {} characters)",
24 value, column.name, column.width()));
25 }
26}
27
33void place(std::string& record, const Column& column, const std::string& value)
34{
35 checkFits(column, value);
36 std::string padded = value;
37 padded.resize(column.width(), ' ');
38 record.replace(column.from, column.width(), padded);
39}
40
41} // namespace
42
43TableFile::TableFile(const TableSchema& schema, std::filesystem::path path)
44 : schema_(schema), path_(std::move(path))
45{
46}
47
48std::vector<Row> TableFile::readRows() const
49{
50 std::ifstream in(path_);
51 if (!in) {
52 throw SqlError(std::format("data file {} not found", path_.filename().string()));
53 }
54
55 std::vector<Row> rows;
56 std::string line;
57 while (std::getline(in, line)) {
58 if (trim(line).empty()) {
59 continue;
60 }
61 rows.push_back(splitLine(line));
62 }
63 return rows;
64}
65
66Row TableFile::splitLine(std::string_view line) const
67{
68 Row row;
69 row.reserve(schema_.columns.size());
70 for (const Column& column : schema_.columns) {
71 std::string_view text = column.from < line.size() ? line.substr(column.from, column.width()) : std::string_view{};
72 row.emplace_back(trimRight(text));
73 }
74 return row;
75}
76
77std::size_t TableFile::removeRows(const std::function<bool(const Row&)>& shouldRemove) const
78{
79 return rewrite([&](std::string&, const Row& row) { return shouldRemove(row) ? Change::Remove : Change::Keep; });
80}
81
82std::size_t TableFile::updateRows(const std::function<bool(const Row&)>& shouldUpdate,
83 const std::vector<std::pair<std::size_t, std::string>>& newValues) const
84{
85 // Check every value before touching the file, so a bad value changes nothing.
86 for (const auto& [index, value] : newValues) {
87 checkFits(schema_.columns.at(index), value);
88 }
89 return rewrite([&](std::string& line, const Row& row) {
90 if (!shouldUpdate(row)) {
91 return Change::Keep;
92 }
93 if (line.size() < schema_.recordLength) {
94 line.resize(schema_.recordLength, ' ');
95 }
96 for (const auto& [index, value] : newValues) {
97 place(line, schema_.columns[index], value);
98 }
99 return Change::Update;
100 });
101}
102
103std::size_t TableFile::rewrite(const std::function<Change(std::string& line, const Row& row)>& decide) const
104{
105 std::ifstream in(path_);
106 if (!in) {
107 throw SqlError(std::format("data file {} not found", path_.filename().string()));
108 }
109
110 std::vector<std::string> kept;
111 std::size_t changed = 0;
112 std::string line;
113 while (std::getline(in, line)) {
114 if (trim(line).empty()) {
115 continue;
116 }
117 const Change change = decide(line, splitLine(line));
118 if (change != Change::Keep) {
119 ++changed;
120 }
121 if (change != Change::Remove) {
122 kept.push_back(std::move(line));
123 }
124 }
125 in.close();
126
127 if (changed == 0) {
128 return 0; // nothing to rewrite
129 }
130
131 std::filesystem::path temporary = path_;
132 temporary += ".tmp";
133 {
134 std::ofstream out(temporary, std::ios::trunc);
135 if (!out) {
136 throw SqlError(std::format("cannot write {}", temporary.string()));
137 }
138 for (const std::string& keptLine : kept) {
139 out << keptLine << '\n';
140 }
141 if (!out.flush()) {
142 throw SqlError(std::format("cannot write {}", temporary.string()));
143 }
144 } // the file is closed here, before it replaces the original
145 std::filesystem::rename(temporary, path_);
146 return changed;
147}
148
149long long TableFile::nextId() const
150{
151 long long maxId = 0;
152 if (std::filesystem::exists(path_)) {
153 for (const Row& row : readRows()) {
154 if (auto id = parseInteger(row.front())) {
155 maxId = std::max(maxId, *id);
156 }
157 }
158 }
159 return maxId + 1;
160}
161
162void TableFile::append(const Row& values) const
163{
164 appendRows({values});
165}
166
167void TableFile::appendRows(const std::vector<Row>& rows) const
168{
169 // Build every record first, so a bad value stops before the file is touched.
170 std::vector<std::string> records;
171 for (const Row& values : rows) {
172 if (values.size() != schema_.columns.size()) {
173 throw SqlError(std::format("table {} has {} columns, got {} values",
174 schema_.name, schema_.columns.size(), values.size()));
175 }
176 std::string record(schema_.recordLength, ' ');
177 for (std::size_t i = 0; i < values.size(); ++i) {
178 place(record, schema_.columns[i], values[i]);
179 }
180 records.push_back(std::move(record));
181 }
182
183 // Make sure the new row starts on its own line, even if the file lacks a final newline.
184 bool needsNewline = false;
185 if (std::ifstream existing(path_, std::ios::binary | std::ios::ate); existing && existing.tellg() > 0) {
186 existing.seekg(-1, std::ios::end);
187 needsNewline = existing.get() != '\n';
188 }
189
190 std::ofstream out(path_, std::ios::app);
191 if (!out) {
192 throw SqlError(std::format("cannot write {}", path_.string()));
193 }
194 if (needsNewline) {
195 out << '\n';
196 }
197 for (const std::string& record : records) {
198 out << record << '\n';
199 }
200}
201
202} // namespace tinysql
Reading, appending, editing and removing the rows of one table's data file.
std::vector< std::string > Row
One row of a table: one value per column, trailing spaces removed.
Definition TableFile.hpp:19
The SqlError exception and small string helpers used everywhere.
std::optional< long long > parseInteger(std::string_view text)
Parses a whole string as a base-10 integer.
Definition Util.cpp:82
std::string_view trimRight(std::string_view text)
Removes trailing spaces, tabs and line ends.
Definition Util.cpp:26
std::string_view trim(std::string_view text)
Removes leading and trailing spaces, tabs and line ends.
Definition Util.cpp:34
A problem with one statement: bad syntax, unknown table, value too long...
Definition Util.hpp:16
std::size_t removeRows(const std::function< bool(const Row &)> &shouldRemove) const
Removes the rows that match a test.
Definition TableFile.cpp:77
long long nextId() const
The ID to give the next new row.
std::size_t updateRows(const std::function< bool(const Row &)> &shouldUpdate, const std::vector< std::pair< std::size_t, std::string > > &newValues) const
Changes some columns in the rows that match a test.
Definition TableFile.cpp:82
std::size_t rewrite(const std::function< Change(std::string &line, const Row &row)> &decide) const
Rewrites the file through a temporary copy, line by line.
Change
What rewrite() does with one line.
Definition TableFile.hpp:71
@ Keep
Write the line back unchanged.
@ Update
Write the line back as decide edited it.
@ Remove
Leave the line out.
std::filesystem::path path_
The data file.
Definition TableFile.hpp:90
void append(const Row &values) const
Adds one row at the end of the file.
const TableSchema & schema_
The table's definition.
Definition TableFile.hpp:89
std::vector< Row > readRows() const
Reads every row of the file.
Definition TableFile.cpp:48
Row splitLine(std::string_view line) const
Cuts one line of the file into its column values.
Definition TableFile.cpp:66
void appendRows(const std::vector< Row > &rows) const
Adds several rows at the end of the file, in one write.
TableFile(const TableSchema &schema, std::filesystem::path path)
Prepares access to one data file.
Definition TableFile.cpp:43
void place(std::string &record, const Column &column, const std::string &value)
Writes a value into its column's characters of a row, padded with spaces to the column width.
Definition TableFile.cpp:33
void checkFits(const Column &column, const std::string &value)
Checks that a value fits in its column.
Definition TableFile.cpp:20
One column of a table: the characters it occupies in every fixed-width row.
Definition Catalog.hpp:19
std::size_t width() const
Number of characters the column occupies.
Definition Catalog.hpp:27
std::size_t from
First character of the column in a row, counted from 0.
Definition Catalog.hpp:22
std::string name
Column name, e.g. NOME.
Definition Catalog.hpp:20
The definition of one table: its name, its columns, and the length of a row.
Definition Catalog.hpp:31
std::string name
Table name, e.g. EMPLOY.
Definition Catalog.hpp:32
std::size_t recordLength
Length of a full row, in characters.
Definition Catalog.hpp:34
std::vector< Column > columns
Columns in file order.
Definition Catalog.hpp:33