28 if (condition.
op ==
"LIKE") {
32 std::strong_ordering order = std::strong_ordering::equal;
36 order = *left <=> *right;
41 const std::string& op = condition.
op;
42 if (op ==
"=")
return order == 0;
43 if (op ==
"!=" || op ==
"<>")
return order != 0;
44 if (op ==
"<")
return order < 0;
45 if (op ==
"<=")
return order <= 0;
46 if (op ==
">")
return order > 0;
54ResultSet fitted(std::vector<std::string> headers, std::vector<std::vector<std::string>> rows)
57 result.
widths.resize(headers.size());
58 for (std::size_t i = 0; i < headers.size(); ++i) {
59 result.
widths[i] = headers[i].size();
60 for (
const auto& row : rows) {
61 result.
widths[i] = std::max(result.
widths[i], row[i].size());
64 result.
literal.assign(headers.size(),
false);
66 result.
headers = std::move(headers);
67 result.
rows = std::move(rows);
75std::string
plural(std::size_t n, std::string_view word)
77 return std::format(
"{} {}{}", n, word, n == 1 ?
"" :
"s");
91void checkName(std::string_view name, std::string_view what)
93 auto isLetter = [](
char c) {
return (c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z'); };
94 auto isAllowed = [&](
char c) {
return isLetter(c) || (c >=
'0' && c <=
'9') || c ==
'_' || c ==
'-'; };
95 if (name.empty() || name.size() >
kMaxNameLength || !isLetter(name.front())
96 || !std::ranges::all_of(name, isAllowed)) {
97 throw SqlError(std::format(
"invalid {} name '{}': use up to {} letters, digits, _ and -, starting with a letter",
111 result = std::visit([
this](
const auto& stmt) {
return run(stmt); }, statement);
115 }
catch (
const std::exception& e) {
117 result.
message = std::format(
"internal error: {}", e.what());
119 result.
sql = std::string(
trim(sql));
125 std::vector<StatementResult> results;
127 results.push_back(
execute(sql));
140 std::optional<std::size_t> source;
143 std::vector<Output> outputs;
144 auto addColumn = [&](std::size_t index) {
148 const std::size_t width = custom !=
displayWidths_.end() ? custom->second
149 : std::max(column.
width(), column.
name.size());
150 outputs.push_back({column.
name, index, width});
155 for (std::size_t i = 0; i < schema.
columns.size(); ++i) {
160 auto index = schema.
indexOf(item.text);
162 throw SqlError(std::format(
"unknown column '{}' in table {}", item.text, schema.
name));
168 outputs.push_back({item.text, std::nullopt, item.text.size()});
175 std::optional<std::size_t> whereIndex;
179 throw SqlError(std::format(
"unknown column '{}' in table {}", stmt.
where->column, schema.
name));
183 for (
const Output& output : outputs) {
184 result.
headers.push_back(output.header);
185 result.
widths.push_back(output.width);
186 result.
literal.push_back(!output.source);
193 auto id = std::ranges::find_if(outputs, [](
const Output& o) {
return o.source == std::size_t{0}; });
194 if (
id != outputs.end()) {
195 result.
idColumn =
static_cast<std::size_t
>(
id - outputs.begin());
200 if (whereIndex && !matches(row[*whereIndex], *stmt.
where)) {
203 std::vector<std::string> values;
204 values.reserve(outputs.size());
205 for (
const Output& output : outputs) {
206 values.push_back(output.source ? row[*output.source] : output.header);
208 result.
rows.push_back(std::move(values));
213 statementResult.
message = plural(result.
rows.size(),
"row");
214 statementResult.
table = std::move(result);
215 return statementResult;
225 const std::size_t columns = schema.
columns.size();
226 if (schema.
hasAutoId() && values.size() + 1 == columns) {
227 values.insert(values.begin(), std::to_string(file.
nextId()));
228 }
else if (values.size() != columns) {
229 throw SqlError(std::format(
"table {} has {} columns{}: got {} values", schema.
name, columns,
230 schema.
hasAutoId() ? std::format(
" (give {} and ID is assigned)", columns - 1) :
"",
233 const auto rows = std::filesystem::exists(catalog.
dataFile(schema)) ? file.
readRows() : std::vector<Row>{};
234 if (std::ranges::any_of(rows, [&](
const Row& row) {
return row.front() == values.front(); })) {
235 throw SqlError(std::format(
"table {} already has a row with ID {}", schema.
name, values.front()));
242 result.
message = std::format(
"1 row inserted into {}", schema.
name);
244 result.
message += std::format(
" with ID {}", values.front());
254 throw SqlError(
"SYSTEM_TABLE cannot be changed with UPDATE: edit the file instead");
257 std::vector<std::pair<std::size_t, std::string>> newValues;
259 const auto index = schema.
indexOf(assignment.column);
261 throw SqlError(std::format(
"unknown column '{}' in table {}", assignment.column, schema.
name));
264 throw SqlError(
"the ID column cannot be changed: rows are found by their ID");
266 newValues.emplace_back(*index, assignment.value);
274 [&](
const Row& row) {
return matches(row[*whereIndex], stmt.
where); }, newValues);
278 result.
message = std::format(
"{} updated in {}", plural(updated,
"row"), schema.
name);
287 throw SqlError(
"SYSTEM_TABLE cannot be changed with DELETE: edit the file instead");
295 return matches(row[*whereIndex], stmt.
where);
300 result.
message = std::format(
"{} deleted from {}", plural(removed,
"row"), schema.
name);
309 checkName(stmt.
table,
"table");
312 if (std::ranges::any_of(catalog.
tables(), sameName)) {
313 throw SqlError(std::format(
"table {} already exists", schema.name));
316 checkName(definition.name,
"column");
317 const std::string name =
toUpper(definition.name);
318 if (schema.indexOf(name)) {
319 throw SqlError(std::format(
"column {} appears twice", name));
321 if (name ==
"ID" && !schema.columns.empty()) {
322 throw SqlError(
"the ID column must be the first one: rows are numbered by it");
326 constexpr std::size_t kLargest = std::numeric_limits<std::size_t>::max();
327 const std::size_t end =
328 definition.width > kLargest - schema.recordLength ? kLargest : schema.recordLength + definition.width;
329 schema.columns.push_back(
Column{
331 .type = definition.type,
332 .from = schema.recordLength,
335 schema.recordLength = end;
337 if (schema.recordLength > kMaxRecordLength) {
338 throw SqlError(std::format(
"a row of table {} would be {} characters long: the limit is {}",
339 schema.name, schema.recordLength, kMaxRecordLength));
345 long long id = catalogFile.
nextId();
346 std::vector<Row> lines;
347 for (std::size_t i = 0; i < schema.columns.size(); ++i) {
348 const Column& column = schema.columns[i];
349 lines.push_back({std::to_string(
id++), std::to_string(i + 1), schema.name, column.
name, column.
type,
350 std::to_string(column.
from), std::to_string(column.
to), std::to_string(column.
width()),
351 std::to_string(schema.recordLength)});
356 const auto dataFile = catalog.
dataFile(schema);
357 if (std::filesystem::exists(dataFile)) {
358 throw SqlError(std::format(
"data file {} already exists: remove it, or choose another name",
359 dataFile.filename().string()));
361 if (!std::ofstream(dataFile)) {
362 throw SqlError(std::format(
"cannot create {}", dataFile.string()));
367 std::filesystem::remove(dataFile);
373 result.
message = std::format(
"table {} created with {} (row length {})", schema.name,
374 plural(schema.columns.size(),
"column"), schema.recordLength);
389 std::vector<std::vector<std::string>> rows;
394 rows.push_back({table.name, std::to_string(table.columns.size()), std::to_string(table.recordLength)});
396 result.
message = plural(rows.size(),
"table");
397 result.
table = fitted({
"TABLE",
"COLUMNS",
"ROW_LENGTH"}, std::move(rows));
403 rows.push_back({column.name, column.type, std::to_string(column.from), std::to_string(column.to),
404 std::to_string(column.width())});
407 result.
message = plural(rows.size(),
"column");
408 result.
table = fitted({
"COLUMN",
"TYPE",
"FROM",
"TO",
"WIDTH"}, std::move(rows));
The table definitions read from SYSTEM_TABLE: tables, their columns and positions.
constexpr std::string_view kCatalogTable
Name of the catalogue table: it describes every table, itself included, and is its own data file.
Running statements, and the results they produce.
std::variant< SelectStmt, InsertStmt, UpdateStmt, DeleteStmt, CreateTableStmt, ColumnFormatStmt, DescribeStmt, CommentStmt > Statement
Any parsed statement: exactly one of the statement types above.
std::vector< std::string > splitStatements(std::string_view script)
Splits a script into statements.
Statement parseStatement(std::string_view sql)
Parses one statement.
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.
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.
std::string toUpper(std::string_view text)
Converts ASCII letters to upper case.
bool matchesLike(std::string_view text, std::string_view pattern)
Matches text against an SQL LIKE pattern, ignoring the case of ASCII letters.
bool equalsIgnoreCase(std::string_view a, std::string_view b)
Compares two strings, ignoring the case of ASCII letters.
std::string_view trim(std::string_view text)
Removes leading and trailing spaces, tabs and line ends.
All table definitions, read from the SYSTEM_TABLE file (one 80-character line per column).
std::filesystem::path dataFile(const TableSchema &table) const
The file holding a table's rows.
const std::vector< TableSchema > & tables() const
All tables, in the order they appear in SYSTEM_TABLE.
const TableSchema & table(std::string_view name) const
Finds a table by name.
static Catalog load(const std::filesystem::path &dataDir)
Reads SYSTEM_TABLE from a data directory.
std::filesystem::path dataDir_
Where the tables are.
StatementResult run(const SelectStmt &stmt)
Runs a SELECT.
std::map< std::string, std::size_t > displayWidths_
Upper-case column name to display width.
std::vector< StatementResult > executeScript(std::string_view script)
Runs every statement of a script, in order.
StatementResult execute(std::string_view sql)
Parses and runs one statement.
Session(std::filesystem::path dataDir)
Starts a session.
A problem with one statement: bad syntax, unknown table, value too long...
Reads and changes the rows of one table's data file.
std::size_t removeRows(const std::function< bool(const Row &)> &shouldRemove) const
Removes the rows that match a test.
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.
void append(const Row &values) const
Adds one row at the end of the file.
std::vector< Row > readRows() const
Reads every row of the file.
void appendRows(const std::vector< Row > &rows) const
Adds several rows at the end of the file, in one write.
constexpr std::size_t kMaxRecordLength
Longest row: SYSTEM_TABLE keeps column positions in 5 characters.
std::string plural(std::size_t n, std::string_view word)
Writes a count with a noun in singular or plural.
constexpr std::size_t kMaxNameLength
Longest table or column name: the width of the TABLE and FIELD columns of SYSTEM_TABLE.
void checkName(std::string_view name, std::string_view what)
Checks a new table or column name.
bool matches(std::string_view fieldValue, const Condition &condition)
Applies a WHERE test to one value.
ResultSet fitted(std::vector< std::string > headers, std::vector< std::vector< std::string > > rows)
Builds a result whose column widths fit the headers and the values (used by DESCRIBE).
One <column> = <value> of an UPDATE's SET list.
One column of a CREATE TABLE: <name> CHAR(<width>) or <name> NUMBER(<width>).
One column of a table: the characters it occupies in every fixed-width row.
std::size_t width() const
Number of characters the column occupies.
std::size_t to
Last character of the column, inclusive.
std::size_t from
First character of the column in a row, counted from 0.
std::string name
Column name, e.g. NOME.
std::string type
N1 or S1: recorded in SYSTEM_TABLE, not enforced.
A WHERE <column> <op> <value> test.
std::string value
The value to compare with (a number when both sides are integers), or the LIKE pattern.
std::string column
The column to test.
std::string op
One of = != <> < <= > >=, or LIKE.
CREATE TABLE <table> (<column> <type>(<width>)[, ...]): adds a table to SYSTEM_TABLE.
std::string table
The new table's name.
std::vector< ColumnDefinition > columns
Its columns, in file order.
DELETE FROM <table> WHERE <condition>. The WHERE is required, so a table is never emptied by accident...
Condition where
Which rows to remove.
std::string table
The table to remove rows from.
DESCRIBE lists the tables; DESCRIBE <table> lists that table's columns.
std::optional< std::string > table
The table to describe, or empty to list all tables.
INSERT INTO <table> VALUES (<values>).
std::vector< std::string > values
The values, without the ID when it is assigned automatically.
std::string table
The table to add a row to.
The rows produced by a SELECT or DESCRIBE. All per-column vectors have the same length.
std::vector< bool > literal
True for a 'literal' SELECT item.
std::vector< std::size_t > widths
Display width of each column in console output.
std::vector< std::string > headers
Column titles.
std::vector< std::size_t > storageWidths
Width of each column in the data file (0 for a literal).
std::vector< std::vector< std::string > > rows
The values, one inner vector per row.
std::optional< std::size_t > idColumn
Output column holding the row ID, if rows can be edited and deleted by ID.
One entry of a SELECT list: *, a column name, or a 'quoted literal' printed on every row.
@ Star
*: every column of the table.
@ Literal
A 'quoted text', printed as-is on every row.
SELECT <items> FROM <table> [WHERE <condition>].
std::vector< SelectItem > items
What to show, in order.
std::string table
The table to read.
std::optional< Condition > where
The optional WHERE test.
What running one statement produced: a table, a message, or an error.
std::string sql
The statement as it was run.
std::string tableName
The table the statement worked on, if any.
bool failed
True if the statement failed; message holds the error.
std::string message
E.g. "1 row inserted ...", or the error text.
std::optional< ResultSet > table
The rows, for SELECT and DESCRIBE.
bool silent
True for COLUMN and comments: nothing to show.
The definition of one table: its name, its columns, and the length of a row.
std::string name
Table name, e.g. EMPLOY.
std::optional< std::size_t > indexOf(std::string_view columnName) const
Finds a column by name.
bool hasAutoId() const
Tells whether rows are numbered automatically.
std::vector< Column > columns
Columns in file order.
UPDATE <table> SET <col> = <value>[, ...] WHERE <condition>. The WHERE is required,...
std::string table
The table to change.
std::vector< Assignment > assignments
The columns to set and their new values.
Condition where
Which rows to change.