54 const auto byte =
static_cast<unsigned char>(c);
55 return (
byte >=
'A' &&
byte <=
'Z') || (
byte >=
'a' &&
byte <=
'z') || (
byte >=
'0' &&
byte <=
'9')
69 return i > 0 && i + 1 < text.size() && text[i] ==
'\''
77std::vector<Token>
tokenize(std::string_view sql)
79 std::vector<Token> tokens;
81 while (i < sql.size()) {
82 const char c = sql[i];
83 if (c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\n') {
85 }
else if (c ==
'\'') {
88 const bool afterWord = i > 0 &&
isWordChar(sql[i - 1]);
91 if (i >= sql.size()) {
92 throw SqlError(afterWord ?
"unterminated quoted string (an apostrophe at the end of a word, "
93 "as in Ca' Foscari, is written twice inside quotes: 'Ca'' Foscari')"
94 :
"unterminated quoted string");
97 if (i + 1 < sql.size() && sql[i + 1] ==
'\'') {
111 tokens.push_back({TokenKind::String, std::move(text)});
112 }
else if (std::string_view(
"(),*;@").find(c) != std::string_view::npos) {
113 tokens.push_back({TokenKind::Symbol, std::string(1, c)});
115 }
else if (std::string_view(
"=<>!").find(c) != std::string_view::npos) {
116 std::string_view two = sql.substr(i, 2);
117 if (two ==
"<=" || two ==
">=" || two ==
"!=" || two ==
"<>") {
118 tokens.push_back({TokenKind::Symbol, std::string(two)});
120 }
else if (c ==
'!') {
123 tokens.push_back({TokenKind::Symbol, std::string(1, c)});
128 std::size_t stop = i;
129 while (stop < sql.size()
133 tokens.push_back({TokenKind::Word, std::string(sql.substr(i, stop - i))});
137 tokens.push_back({TokenKind::End,
""});
146 switch (token.
kind) {
148 return "end of statement";
149 case TokenKind::String:
150 return std::format(
"'{}'", token.
text);
152 return std::format(
"\"{}\"", token.
text);
161 return op ==
"=" || op ==
"!=" || op ==
"<>" || op ==
"<" || op ==
"<=" || op ==
">" || op ==
">=";
169 explicit Parser(std::vector<Token> tokens) : tokens_(std::move(tokens)) {}
176 const std::string keyword =
toUpper(peek().text);
177 if (peek().kind != TokenKind::Word) {
180 if (keyword ==
"SELECT")
return parseSelect();
181 if (keyword ==
"INSERT")
return parseInsert();
182 if (keyword ==
"UPDATE")
return parseUpdate();
183 if (keyword ==
"DELETE")
return parseDelete();
184 if (keyword ==
"CREATE")
return parseCreateTable();
185 if (keyword ==
"COLUMN")
return parseColumn();
186 if (keyword ==
"DESCRIBE")
return parseDescribe();
187 throw SqlError(std::format(
"unknown command \"{}\"", peek().text));
194 std::size_t pos_ = 0;
201 Token next() {
return peek().
kind == TokenKind::End ? peek() : tokens_[pos_++]; }
206 [[noreturn]]
void fail(std::string_view expected)
const
208 throw SqlError(std::format(
"expected {} but found {}", expected,
describe(peek())));
216 if (peek().kind == TokenKind::Word &&
equalsIgnoreCase(peek().text, keyword)) {
228 if (!acceptKeyword(keyword)) {
238 if (peek().kind == TokenKind::Symbol && peek().text == symbol) {
250 if (!acceptSymbol(symbol)) {
251 fail(std::format(
"'{}'", symbol));
261 if (peek().kind != TokenKind::Word) {
272 if (peek().kind != TokenKind::Word && peek().kind != TokenKind::String) {
282 while (acceptSymbol(
";") || acceptSymbol(
"@")) {
284 if (peek().kind != TokenKind::End) {
285 fail(
"end of statement");
295 condition.
column = expectWord(
"a column name");
296 if (acceptKeyword(
"LIKE")) {
297 condition.
op =
"LIKE";
299 if (peek().kind != TokenKind::Symbol || !
isComparison(peek().text)) {
300 fail(
"a comparison (= != < <= > >= or LIKE)");
302 condition.
op = next().text;
304 condition.
value = expectValue();
305 if (peek().kind == TokenKind::Word) {
307 throw SqlError(std::format(
"WHERE takes one condition: {} is not supported",
toUpper(peek().text)));
309 std::string words = condition.
value;
310 while (peek().kind == TokenKind::Word) {
311 words +=
' ' + next().text;
313 throw SqlError(std::format(
"put a value with spaces in quotes, e.g. {} = '{}'", condition.
column, words));
323 expectKeyword(
"SELECT");
326 if (acceptSymbol(
"*")) {
327 stmt.
items.push_back({SelectItem::Kind::Star,
"*"});
328 }
else if (peek().kind == TokenKind::String) {
329 stmt.
items.push_back({SelectItem::Kind::Literal, next().text});
331 stmt.
items.push_back({SelectItem::Kind::Column, expectWord(
"a column name")});
333 }
while (acceptSymbol(
","));
335 expectKeyword(
"FROM");
336 stmt.
table = expectWord(
"a table name");
338 if (acceptKeyword(
"WHERE")) {
339 stmt.
where = parseCondition();
351 expectKeyword(
"INSERT");
352 expectKeyword(
"INTO");
354 stmt.
table = expectWord(
"a table name");
355 expectKeyword(
"VALUES");
359 if (peek().kind == TokenKind::String) {
360 stmt.
values.push_back(next().text);
364 while (peek().kind == TokenKind::Word) {
365 if (!value.empty()) {
368 value += next().text;
370 stmt.
values.push_back(std::move(value));
371 }
while (acceptSymbol(
","));
382 expectKeyword(
"UPDATE");
384 stmt.
table = expectWord(
"a table name");
385 expectKeyword(
"SET");
388 assignment.
column = expectWord(
"a column name");
390 assignment.
value = expectValue();
391 if (peek().kind == TokenKind::Word && !
equalsIgnoreCase(peek().text,
"WHERE")) {
392 throw SqlError(std::format(
"put a value with spaces in quotes, e.g. {} = '{} {}'",
393 assignment.
column, assignment.
value, peek().text));
396 }
while (acceptSymbol(
","));
397 if (!acceptKeyword(
"WHERE")) {
398 fail(
"WHERE (UPDATE always needs a condition, e.g. WHERE ID = 3)");
400 stmt.
where = parseCondition();
410 expectKeyword(
"DELETE");
411 expectKeyword(
"FROM");
413 stmt.
table = expectWord(
"a table name");
414 if (!acceptKeyword(
"WHERE")) {
415 fail(
"WHERE (DELETE always needs a condition, e.g. WHERE ID = 3)");
417 stmt.
where = parseCondition();
427 expectKeyword(
"CREATE");
428 expectKeyword(
"TABLE");
430 stmt.
table = expectWord(
"a table name");
434 column.
name = expectWord(
"a column name");
435 const std::string type =
toUpper(expectWord(
"a type (CHAR or NUMBER)"));
436 if (type ==
"CHAR") {
438 }
else if (type ==
"NUMBER") {
441 throw SqlError(std::format(
"unknown type \"{}\" for column {}: use CHAR(<width>) or NUMBER(<width>)",
445 const std::string width = expectWord(
"a width");
447 if (!value || *value < 1) {
448 throw SqlError(std::format(
"invalid width \"{}\" for column {}: expected a positive number",
449 width, column.
name));
451 column.
width =
static_cast<std::size_t
>(*value);
453 stmt.
columns.push_back(std::move(column));
454 }
while (acceptSymbol(
","));
465 expectKeyword(
"COLUMN");
467 stmt.
column = expectWord(
"a column name");
468 expectKeyword(
"FORMAT");
469 const std::string format = expectWord(
"a format such as A16");
470 auto width =
parseInteger(std::string_view(format).substr(1));
471 if (format.size() < 2 || (format[0] !=
'A' && format[0] !=
'a') || !width || *width < 1) {
472 throw SqlError(std::format(
"invalid format \"{}\", expected A<width> such as A16", format));
474 stmt.
width =
static_cast<std::size_t
>(*width);
484 expectKeyword(
"DESCRIBE");
486 if (peek().kind == TokenKind::Word) {
487 stmt.
table = next().text;
500 if (sql.empty() || sql.starts_with(
"--")) {
503 const std::string_view first = sql.substr(0, sql.find_first_of(
" \t"));
511 if (isComment(sql)) {
514 return Parser(tokenize(sql)).parse();
519 std::vector<std::string> statements;
520 std::size_t lineStart = 0;
521 while (lineStart <= script.size()) {
522 std::size_t lineEnd = script.find(
'\n', lineStart);
523 if (lineEnd == std::string_view::npos) {
524 lineEnd = script.size();
526 const std::string_view line = script.substr(lineStart, lineEnd - lineStart);
527 lineStart = lineEnd + 1;
529 if (isComment(line)) {
533 bool inQuotes =
false;
534 for (std::size_t i = 0; i < line.size(); ++i) {
535 const char c = line[i];
536 if (c ==
'\'' && !isApostrophe(line, i)) {
537 inQuotes = !inQuotes;
539 if (c ==
';' && !inQuotes) {
540 if (!
trim(current).empty()) {
541 statements.emplace_back(
trim(current));
548 if (!
trim(current).empty()) {
549 statements.emplace_back(
trim(current));
The parsed form of every statement, and the functions that build it from text.
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.
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 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.
A problem with one statement: bad syntax, unknown table, value too long...
A recursive-descent parser: one method per statement type, reading the tokens left to right.
void expectSymbol(std::string_view symbol)
Consumes a symbol that must come next.
Condition parseCondition()
Parses <column> <op> <value>, after the WHERE keyword; <op> is a comparison or LIKE.
ColumnFormatStmt parseColumn()
Parses COLUMN <col> FORMAT A<width>.
std::string expectWord(std::string_view what)
Consumes a word (a name) that must come next.
bool acceptKeyword(std::string_view keyword)
Consumes the next token if it is a given keyword.
UpdateStmt parseUpdate()
Parses UPDATE <table> SET <col> = <value>, ... WHERE ....
SelectStmt parseSelect()
Parses SELECT <items> FROM <table> [WHERE ...].
Statement parse()
Parses the whole statement, choosing the method from its first word.
void expectEnd()
Checks that the statement ends here, after any trailing ; or @.
Token next()
Consumes the next token (the End token is never consumed).
Parser(std::vector< Token > tokens)
Prepares to parse a statement.
bool acceptSymbol(std::string_view symbol)
Consumes the next token if it is a given symbol.
const Token & peek() const
Looks at the next token without consuming it.
InsertStmt parseInsert()
Parses INSERT INTO <table> VALUES (...). A value is a quoted string, or one or more bare words joined...
DescribeStmt parseDescribe()
Parses DESCRIBE [<table>].
std::vector< Token > tokens_
The tokens, ending with TokenKind::End.
DeleteStmt parseDelete()
Parses DELETE FROM <table> WHERE ....
void fail(std::string_view expected) const
Reports a syntax error at the current token.
void expectKeyword(std::string_view keyword)
Consumes a keyword that must come next.
std::string expectValue()
Consumes a value that must come next: a word or a quoted string.
CreateTableStmt parseCreateTable()
Parses CREATE TABLE <table> (<col> <type>(<width>), ...), where <type> is CHAR or NUMBER.
bool isComment(std::string_view sql)
Tells whether a line is a comment or empty.
bool isWordChar(char c)
Tells whether a character can stand next to an apostrophe inside a word.
bool isApostrophe(std::string_view text, std::size_t i)
Tells whether the ' at a position is an apostrophe inside a word, as in d'Asti, L'Aquila or dell'800.
std::string describe(const Token &token)
Describes a token for an error message.
constexpr std::string_view kDelimiters
The characters that end a word (a ' only when it is not an apostrophe, see isApostrophe()).
std::vector< Token > tokenize(std::string_view sql)
Splits a statement into tokens.
bool isComparison(std::string_view op)
Tells whether a symbol is a comparison operator.
TokenKind
The kinds of token the tokenizer produces.
@ Symbol
Punctuation or an operator: ( ) , * ; @ = != <> < <= > >=.
@ Word
A keyword, name or bare value, e.g. SELECT, EMPLOY, Torino, 42.
@ String
A 'quoted string', without its quotes.
@ End
The end of the statement.
One <column> = <value> of an UPDATE's SET list.
std::string column
The column to change.
std::string value
Its new value.
One column of a CREATE TABLE: <name> CHAR(<width>) or <name> NUMBER(<width>).
std::string name
The column name.
std::string type
S1 for CHAR, N1 for NUMBER: the codes SYSTEM_TABLE records.
std::size_t width
Number of characters the column occupies in every row.
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.
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.
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.
One token of a statement.
TokenKind kind
What kind of token this is.
std::string text
Its text.