myTinySQL v0.2.0-2-g5000098
A tiny SQL interpreter over fixed-width text files
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1
7
8#include <unistd.h>
9
10#include <charconv>
11#include <filesystem>
12#include <format>
13#include <fstream>
14#include <iostream>
15#include <optional>
16#include <sstream>
17#include <string>
18#include <string_view>
19#include <vector>
20
21#include "Output.hpp"
22#include "Server.hpp"
23#include "Session.hpp"
24
25namespace {
26
27using namespace tinysql;
28
30constexpr std::string_view kUsage = R"(Usage: myTinySql [options]
31
32 --sqlfile=FILE run the statements in FILE ('-' for standard input)
33 --http start the browser server (default port 8080)
34 --port=N listen on port N instead of 8080
35 --allow=LIST comma-separated client IP prefixes the server accepts,
36 e.g. 127.,192.168. (default: everyone)
37 --data=DIR directory with SYSTEM_TABLE and the *_DATA files (default: data)
38 --web=DIR directory with the browser page's files (default: web)
39 --help show this help
40
41With no --sqlfile and no --http, statements are read from standard input.
42)";
43
45struct Options {
46 std::optional<std::string> sqlFile;
47 bool http = false;
48 std::optional<std::uint16_t> port;
49 std::optional<std::vector<std::string>> allow;
50 std::filesystem::path dataDir = "data";
51 std::filesystem::path webDir = "web";
52};
53
57std::vector<std::string> splitList(std::string_view text)
58{
59 std::vector<std::string> items;
60 while (!text.empty()) {
61 const std::size_t comma = text.find(',');
62 if (std::string_view item = text.substr(0, comma); !item.empty()) {
63 items.emplace_back(item);
64 }
65 text = comma == std::string_view::npos ? std::string_view{} : text.substr(comma + 1);
66 }
67 return items;
68}
69
75Options parseOptions(int argc, char** argv)
76{
77 Options options;
78 for (int i = 1; i < argc; ++i) {
79 std::string_view arg = argv[i];
80 std::string_view name = arg;
81 std::optional<std::string_view> value;
82 if (const std::size_t eq = arg.find('='); eq != std::string_view::npos) {
83 name = arg.substr(0, eq);
84 value = arg.substr(eq + 1);
85 }
86 auto requireValue = [&]() -> std::string_view {
87 if (value) {
88 return *value;
89 }
90 if (i + 1 >= argc) {
91 throw std::invalid_argument(std::format("{} needs a value", name));
92 }
93 return argv[++i];
94 };
95
96 if (name == "--help" || name == "-h") {
97 std::cout << kUsage;
98 std::exit(0);
99 } else if (name == "--sqlfile") {
100 options.sqlFile = std::string(requireValue());
101 } else if (name == "--http") {
102 options.http = true;
103 } else if (name == "--port") {
104 const std::string_view text = requireValue();
105 std::uint16_t port = 0;
106 auto [ptr, ec] = std::from_chars(text.data(), text.data() + text.size(), port);
107 if (ec != std::errc{} || ptr != text.data() + text.size() || port == 0) {
108 throw std::invalid_argument(std::format("invalid port '{}'", text));
109 }
110 options.port = port;
111 } else if (name == "--allow") {
112 options.allow = splitList(requireValue());
113 } else if (name == "--data") {
114 options.dataDir = std::string(requireValue());
115 } else if (name == "--web") {
116 options.webDir = std::string(requireValue());
117 } else {
118 throw std::invalid_argument(std::format("unknown option '{}'", arg));
119 }
120 }
121 if (options.sqlFile && options.http) {
122 throw std::invalid_argument("choose either --sqlfile or --http, not both");
123 }
124 return options;
125}
126
131int runScript(Session& session, std::istream& in)
132{
133 std::stringstream script;
134 script << in.rdbuf();
135 int exitCode = 0;
136 for (const StatementResult& result : session.executeScript(script.str())) {
137 std::cout << renderText(result);
138 if (result.failed) {
139 exitCode = 1;
140 }
141 }
142 return exitCode;
143}
144
149{
150 std::string line;
151 while (std::cout << "tinysql> " << std::flush, std::getline(std::cin, line)) {
152 for (const StatementResult& result : session.executeScript(line)) {
153 std::cout << renderText(result);
154 }
155 }
156 std::cout << '\n';
157 return 0;
158}
159
160} // namespace
161
167int main(int argc, char** argv)
168{
169 try {
170 const Options options = parseOptions(argc, argv);
171 if (!std::filesystem::is_directory(options.dataDir)) {
172 std::cerr << std::format("data directory '{}' not found (use --data=DIR)\n", options.dataDir.string());
173 return 2;
174 }
175
176 if (options.http) {
177 if (!std::filesystem::is_regular_file(options.webDir / "index.html")) {
178 std::cerr << std::format("web page '{}' not found (use --web=DIR)\n",
179 (options.webDir / "index.html").string());
180 return 2;
181 }
182 ServerConfig config{
183 .port = options.port.value_or(8080),
184 .dataDir = options.dataDir,
185 .webDir = options.webDir,
186 .allowedClients = options.allow.value_or(std::vector<std::string>{}),
187 };
188 runServer(config);
189 return 0;
190 }
191
192 Session session(options.dataDir);
193 if (!options.sqlFile || *options.sqlFile == "-") {
194 return ::isatty(STDIN_FILENO) && !options.sqlFile ? runInteractive(session) : runScript(session, std::cin);
195 }
196 std::ifstream file(*options.sqlFile);
197 if (!file) {
198 std::cerr << std::format("cannot open '{}'\n", *options.sqlFile);
199 return 2;
200 }
201 return runScript(session, file);
202 } catch (const std::invalid_argument& e) {
203 std::cerr << "myTinySql: " << e.what() << "\n\n" << kUsage;
204 return 2;
205 } catch (const std::exception& e) {
206 std::cerr << "myTinySql: " << e.what() << '\n';
207 return 1;
208 }
209}
Turning statement results into console text or JSON for the browser page.
std::string renderText(const StatementResult &result)
Formats one result for the console.
Definition Output.cpp:67
The small HTTP server behind --http: the page and its JSON API.
Running statements, and the results they produce.
Runs statements against the tables in one data directory.
Definition Session.hpp:42
std::vector< StatementResult > executeScript(std::string_view script)
Runs every statement of a script, in order.
Definition Session.cpp:123
int main(int argc, char **argv)
Program entry point.
Definition main.cpp:167
Options parseOptions(int argc, char **argv)
Parses the command line. Options take their value as --name=value or --name value.
Definition main.cpp:75
int runInteractive(Session &session)
Reads statements at a tinysql> prompt until end of input (Ctrl+D).
Definition main.cpp:148
constexpr std::string_view kUsage
The text printed by --help and after a wrong option.
Definition main.cpp:30
int runScript(Session &session, std::istream &in)
Runs a whole script and prints every result.
Definition main.cpp:131
std::vector< std::string > splitList(std::string_view text)
Splits a comma-separated list.
Definition main.cpp:57
The command-line options, after parsing.
Definition main.cpp:45
std::filesystem::path dataDir
--data: the directory holding the tables.
Definition main.cpp:50
std::optional< std::uint16_t > port
--port: the server port, if not the default.
Definition main.cpp:48
std::optional< std::vector< std::string > > allow
--allow: client IP prefixes, if given.
Definition main.cpp:49
std::filesystem::path webDir
--web: the directory holding the browser page.
Definition main.cpp:51
bool http
--http: start the browser server.
Definition main.cpp:47
std::optional< std::string > sqlFile
--sqlfile: the script to run (- for standard input).
Definition main.cpp:46
Settings of the browser server.
Definition Server.hpp:14
std::uint16_t port
TCP port to listen on.
Definition Server.hpp:15
What running one statement produced: a table, a message, or an error.
Definition Session.hpp:29