Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,18 @@ list;

`dodoc` compiles `.do` files to SQL without DuckDB. No dependencies, single binary.

### Install from release

Download the binary for your platform from [GitHub Releases](https://github.com/codedthinking/dodo/releases), then:
### Install

```bash
tar xzf dodoc-macos-arm64.tar.gz
sudo install dodoc /usr/local/bin/
# one-liner (macOS / Linux)
curl -fsSL https://getdodo.dev/install.sh | sh

# or via Homebrew
brew install codedthinking/tap/dodoc
```

Or download manually from [GitHub Releases](https://github.com/codedthinking/dodo/releases).

### Build from source

```bash
Expand Down
7 changes: 3 additions & 4 deletions src/cli/dodoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ static void print_usage(const char *prog) {
}

struct CliOptions {
std::string input_file; // empty = stdin
std::string output_file; // empty = stdout
std::string input_file; // empty = stdin
std::string output_file; // empty = stdout
bool annotate = false;
bool terminal = false;
};
Expand Down Expand Up @@ -57,8 +57,7 @@ static CliOptions parse_args(int argc, char *argv[]) {
}

// Process lines from an input stream (file or stdin)
static void process_stream(std::istream &input, dodo::DodoState &state,
std::vector<std::string> &side_effect_sql,
static void process_stream(std::istream &input, dodo::DodoState &state, std::vector<std::string> &side_effect_sql,
const CliOptions &opts) {
std::string line;
bool in_block_comment = false;
Expand Down
203 changes: 101 additions & 102 deletions src/core/dodo_core.cpp

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions src/core/dodo_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct DodoCommand {
std::string arguments;
std::string condition;
std::string options;
std::string bysort_partition; // PARTITION BY vars, comma-separated
std::string bysort_partition; // PARTITION BY vars, comma-separated
std::string bysort_order; // ORDER BY vars, comma-separated
};

Expand All @@ -27,10 +27,10 @@ struct DodoCommand {
//===--------------------------------------------------------------------===//
struct DodoState {
std::vector<std::string> cte_steps;
std::vector<std::string> cte_commands; //! Original command text for each step
std::vector<std::string> cte_commands; //! Original command text for each step
int step_counter = 0;
std::string current_source;
std::string pending_command; //! Set before ProcessCommand, consumed by AddStep
std::string pending_command; //! Set before ProcessCommand, consumed by AddStep

//! Redo stack: (command_text, cte_sql) pairs popped by undo
std::vector<std::pair<std::string, std::string>> redo_stack;
Expand All @@ -50,8 +50,8 @@ struct DodoState {
std::vector<std::string> tempfile_tables;

//! Panel structure (set by xtset/tsset)
std::string panel_var; // empty if pure time-series
std::string time_var; // empty if not set
std::string panel_var; // empty if pure time-series
std::string time_var; // empty if not set

//! Whether dodo._current table exists (materialized use)
bool materialized = false;
Expand Down
168 changes: 145 additions & 23 deletions src/core/string_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ inline void Trim(std::string &s) {

inline std::string Lower(const std::string &s) {
std::string result = s;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::tolower(c); });
return result;
}

Expand Down Expand Up @@ -75,27 +74,150 @@ inline std::vector<std::string> Split(const std::string &s, char delim) {
// SQL keywords that require quoting when used as identifiers
inline bool IsSQLKeyword(const std::string &s) {
static const std::unordered_set<std::string> KEYWORDS = {
"abort", "action", "add", "after", "all", "alter", "always",
"analyze", "and", "as", "asc", "attach", "autoincrement",
"before", "begin", "between", "by", "cascade", "case", "cast",
"check", "collate", "column", "commit", "conflict", "constraint","create",
"cross", "current", "current_date", "current_time", "current_timestamp",
"database", "default", "deferrable","deferred", "delete", "desc", "detach",
"distinct", "do", "drop", "each", "else", "end", "escape",
"except", "exclude", "exclusive", "exists", "explain", "fail", "filter",
"first", "following", "for", "foreign", "from", "full", "glob",
"group", "groups", "having", "if", "ignore", "immediate", "in",
"index", "indexed", "initially", "inner", "insert", "instead", "intersect",
"into", "is", "isnull", "join", "key", "last", "left",
"like", "limit", "match", "materialized", "natural","no", "not",
"nothing", "notnull", "null", "nulls", "of", "offset", "on",
"or", "order", "outer", "over", "partition", "plan", "pragma",
"preceding", "primary", "query", "raise", "range", "recursive", "references",
"regexp", "reindex", "release", "rename", "replace", "restrict", "returning",
"right", "rollback", "row", "rows", "savepoint", "select", "set",
"table", "temp", "temporary", "then", "ties", "to", "transaction",
"trigger", "unbounded", "union", "unique", "update", "using", "vacuum",
"values", "view", "virtual", "when", "where", "window", "with",
"abort",
"action",
"add",
"after",
"all",
"alter",
"always",
"analyze",
"and",
"as",
"asc",
"attach",
"autoincrement",
"before",
"begin",
"between",
"by",
"cascade",
"case",
"cast",
"check",
"collate",
"column",
"commit",
"conflict",
"constraint",
"create",
"cross",
"current",
"current_date",
"current_time",
"current_timestamp",
"database",
"default",
"deferrable",
"deferred",
"delete",
"desc",
"detach",
"distinct",
"do",
"drop",
"each",
"else",
"end",
"escape",
"except",
"exclude",
"exclusive",
"exists",
"explain",
"fail",
"filter",
"first",
"following",
"for",
"foreign",
"from",
"full",
"glob",
"group",
"groups",
"having",
"if",
"ignore",
"immediate",
"in",
"index",
"indexed",
"initially",
"inner",
"insert",
"instead",
"intersect",
"into",
"is",
"isnull",
"join",
"key",
"last",
"left",
"like",
"limit",
"match",
"materialized",
"natural",
"no",
"not",
"nothing",
"notnull",
"null",
"nulls",
"of",
"offset",
"on",
"or",
"order",
"outer",
"over",
"partition",
"plan",
"pragma",
"preceding",
"primary",
"query",
"raise",
"range",
"recursive",
"references",
"regexp",
"reindex",
"release",
"rename",
"replace",
"restrict",
"returning",
"right",
"rollback",
"row",
"rows",
"savepoint",
"select",
"set",
"table",
"temp",
"temporary",
"then",
"ties",
"to",
"transaction",
"trigger",
"unbounded",
"union",
"unique",
"update",
"using",
"vacuum",
"values",
"view",
"virtual",
"when",
"where",
"window",
"with",
"without",
};
return KEYWORDS.count(Lower(s)) > 0;
Expand Down
25 changes: 11 additions & 14 deletions src/extension/dodo_extension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ static ParserExtensionParseResult dodo_parse(ParserExtensionInfo *info, const st
// parser_override: handles commands that conflict with SQL keywords
//===--------------------------------------------------------------------===//
static ParserOverrideResult dodo_parser_override(ParserExtensionInfo *info, const string &query,
ParserOptions &options) {
ParserOptions &options) {
auto &state = dynamic_cast<DodoStateInfo &>(*info);

// Split the full query into individual statements by ';'
Expand All @@ -96,7 +96,8 @@ static ParserOverrideResult dodo_parser_override(ParserExtensionInfo *info, cons
continue;
}

if (StringUtil::StartsWith(trimmed, "use ") && (trimmed.find('"') != string::npos || trimmed.find('\'') != string::npos)) {
if (StringUtil::StartsWith(trimmed, "use ") &&
(trimmed.find('"') != string::npos || trimmed.find('\'') != string::npos)) {
has_conflict_commands = true;
}
if (StringUtil::StartsWith(trimmed, "import ")) {
Expand Down Expand Up @@ -206,7 +207,7 @@ static ParserOverrideResult dodo_parser_override(ParserExtensionInfo *info, cons
// plan_function: generate SQL, store parsed statement, throw to redirect
//===--------------------------------------------------------------------===//
static ParserExtensionPlanResult dodo_plan(ParserExtensionInfo *info, ClientContext &context,
unique_ptr<ParserExtensionParseData> parse_data) {
unique_ptr<ParserExtensionParseData> parse_data) {
auto &dodo_data = dynamic_cast<DodoParseData &>(*parse_data);
auto &state = dynamic_cast<DodoStateInfo &>(*info);

Expand Down Expand Up @@ -234,8 +235,7 @@ static ParserExtensionPlanResult dodo_plan(ParserExtensionInfo *info, ClientCont
//===--------------------------------------------------------------------===//
// OperatorExtension::Bind — picks up stored statement and binds it
//===--------------------------------------------------------------------===//
BoundStatement dodo_bind(ClientContext &context, Binder &binder, OperatorExtensionInfo *info,
SQLStatement &statement) {
BoundStatement dodo_bind(ClientContext &context, Binder &binder, OperatorExtensionInfo *info, SQLStatement &statement) {
auto bind_state = context.registered_state->Get<DodoBindState>("dodo_bind");
if (!bind_state || !bind_state->statement) {
return BoundStatement();
Expand Down Expand Up @@ -273,15 +273,12 @@ static void LoadInternal(ExtensionLoader &loader) {
OperatorExtension::Register(config, operator_ext);

config.AddExtensionOption(
"dodo_live_view",
"Create/replace _dodo_data view after each transformation (for DuckDB UI)",
LogicalType::BOOLEAN,
Value::BOOLEAN(false),
[](ClientContext &context, SetScope scope, Value &parameter) {
if (g_dodo_state) {
g_dodo_state->live_view_enabled = parameter.GetValue<bool>();
g_dodo_state->core.live_view_enabled = parameter.GetValue<bool>();
}
"dodo_live_view", "Create/replace _dodo_data view after each transformation (for DuckDB UI)",
LogicalType::BOOLEAN, Value::BOOLEAN(false), [](ClientContext &context, SetScope scope, Value &parameter) {
if (g_dodo_state) {
g_dodo_state->live_view_enabled = parameter.GetValue<bool>();
g_dodo_state->core.live_view_enabled = parameter.GetValue<bool>();
}
});
}

Expand Down
14 changes: 8 additions & 6 deletions src/extension/dodo_extension.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ struct DodoStateInfo : public ParserExtensionInfo {
bool live_view_enabled = false;

// Convenience forwarders for the extension glue
bool HasData() const { return core.HasData(); }
std::string LatestStep() const { return core.LatestStep(); }
bool HasData() const {
return core.HasData();
}
std::string LatestStep() const {
return core.LatestStep();
}
};

//===--------------------------------------------------------------------===//
Expand All @@ -45,8 +49,7 @@ struct DodoParseData : public ParserExtensionParseData {
//===--------------------------------------------------------------------===//
class DodoBindState : public ClientContextState {
public:
explicit DodoBindState(unique_ptr<SQLStatement> stmt)
: statement(std::move(stmt)) {
explicit DodoBindState(unique_ptr<SQLStatement> stmt) : statement(std::move(stmt)) {
}

void QueryEnd() override {
Expand All @@ -59,8 +62,7 @@ class DodoBindState : public ClientContextState {
//===--------------------------------------------------------------------===//
// Operator Extension
//===--------------------------------------------------------------------===//
BoundStatement dodo_bind(ClientContext &context, Binder &binder, OperatorExtensionInfo *info,
SQLStatement &statement);
BoundStatement dodo_bind(ClientContext &context, Binder &binder, OperatorExtensionInfo *info, SQLStatement &statement);

class DodoOperatorExtension : public OperatorExtension {
public:
Expand Down
Loading