diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 384a340..57de58b 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2569,4 +2569,13 @@ target_link_libraries(step409_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step410_test tests/step410_test.cpp) +target_include_directories(step410_test PRIVATE src) +target_link_libraries(step410_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/CompactAST.h b/editor/src/CompactAST.h index bc8faae..75d8176 100644 --- a/editor/src/CompactAST.h +++ b/editor/src/CompactAST.h @@ -8,6 +8,7 @@ #include "ast/Serialization.h" #include "ast/Annotation.h" #include "ast/HostBoundary.h" +#include "ast/SqlNodes.h" #include "ASTUtils.h" #include "EnvironmentSpec.h" #include @@ -431,7 +432,6 @@ inline std::string getNodeName(const ASTNode* node) { return static_cast(node)->directive; if (ct == "MacroDefinition") return static_cast(node)->name; - // Enum/Namespace/TypeAlias (Step 338) if (ct == "EnumDeclaration") return static_cast(node)->name; if (ct == "EnumMember") @@ -440,6 +440,24 @@ inline std::string getNodeName(const ASTNode* node) { return static_cast(node)->name; if (ct == "TypeAlias") return static_cast(node)->aliasName; + if (ct == "TableDeclaration") + return static_cast(node)->name; + if (ct == "ColumnDefinition") + return static_cast(node)->name; + if (ct == "SelectQuery") + return "select"; + if (ct == "InsertStatement") + return static_cast(node)->tableName; + if (ct == "UpdateStatement") + return static_cast(node)->tableName; + if (ct == "DeleteStatement") + return static_cast(node)->tableName; + if (ct == "JoinClause") + return static_cast(node)->tableName; + if (ct == "WhereClause") + return "where"; + if (ct == "IndexDefinition") + return static_cast(node)->name; return ""; } diff --git a/editor/src/ast/Serialization.h b/editor/src/ast/Serialization.h index 0e0d714..2de4d69 100644 --- a/editor/src/ast/Serialization.h +++ b/editor/src/ast/Serialization.h @@ -19,6 +19,7 @@ #include "AsyncNodes.h" #include "PreprocessorNodes.h" #include "EnumNamespaceNodes.h" +#include "SqlNodes.h" using json = nlohmann::json; @@ -546,6 +547,50 @@ inline json propertiesToJson(const ASTNode* node) { props["targetType"] = n->targetType; if (!n->isUsing) props["isUsing"] = n->isUsing; } + // SQL nodes (Step 410) + else if (ct == "TableDeclaration") { + auto* n = static_cast(node); + if (!n->name.empty()) props["name"] = n->name; + if (!n->schema.empty()) props["schema"] = n->schema; + } + else if (ct == "ColumnDefinition") { + auto* n = static_cast(node); + if (!n->name.empty()) props["name"] = n->name; + if (!n->dataType.empty()) props["dataType"] = n->dataType; + props["nullable"] = n->nullable; + if (!n->defaultValue.empty()) props["defaultValue"] = n->defaultValue; + } + else if (ct == "SelectQuery") { + auto* n = static_cast(node); + if (n->distinct) props["distinct"] = n->distinct; + } + else if (ct == "InsertStatement") { + auto* n = static_cast(node); + if (!n->tableName.empty()) props["tableName"] = n->tableName; + } + else if (ct == "UpdateStatement") { + auto* n = static_cast(node); + if (!n->tableName.empty()) props["tableName"] = n->tableName; + } + else if (ct == "DeleteStatement") { + auto* n = static_cast(node); + if (!n->tableName.empty()) props["tableName"] = n->tableName; + } + else if (ct == "JoinClause") { + auto* n = static_cast(node); + if (!n->joinType.empty()) props["joinType"] = n->joinType; + if (!n->tableName.empty()) props["tableName"] = n->tableName; + } + else if (ct == "WhereClause") { + auto* n = static_cast(node); + if (!n->expression.empty()) props["expression"] = n->expression; + } + else if (ct == "IndexDefinition") { + auto* n = static_cast(node); + if (!n->name.empty()) props["name"] = n->name; + if (!n->tableName.empty()) props["tableName"] = n->tableName; + if (n->unique) props["unique"] = n->unique; + } // NullLiteral, ListLiteral, IndexAccess, Block, Assignment, IfStatement, // WhileLoop, Return, ExpressionStatement, ListType, SetType, MapType, // TupleType, ArrayType, OptionalType — no extra properties @@ -716,6 +761,15 @@ inline ASTNode* createNode(const std::string& conceptName) { if (conceptName == "EnumMember") return new EnumMember(); if (conceptName == "NamespaceDeclaration") return new NamespaceDeclaration(); if (conceptName == "TypeAlias") return new TypeAlias(); + if (conceptName == "TableDeclaration") return new TableDeclaration(); + if (conceptName == "ColumnDefinition") return new ColumnDefinition(); + if (conceptName == "SelectQuery") return new SelectQuery(); + if (conceptName == "InsertStatement") return new InsertStatement(); + if (conceptName == "UpdateStatement") return new UpdateStatement(); + if (conceptName == "DeleteStatement") return new DeleteStatement(); + if (conceptName == "JoinClause") return new JoinClause(); + if (conceptName == "WhereClause") return new WhereClause(); + if (conceptName == "IndexDefinition") return new IndexDefinition(); return nullptr; } @@ -1279,6 +1333,50 @@ inline void setPropertiesFromJson(ASTNode* node, const json& props) { if (props.contains("targetType")) n->targetType = props["targetType"].get(); if (props.contains("isUsing")) n->isUsing = props["isUsing"].get(); } + // SQL nodes (Step 410) + else if (ct == "TableDeclaration") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + if (props.contains("schema")) n->schema = props["schema"].get(); + } + else if (ct == "ColumnDefinition") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + if (props.contains("dataType")) n->dataType = props["dataType"].get(); + if (props.contains("nullable")) n->nullable = props["nullable"].get(); + if (props.contains("defaultValue")) n->defaultValue = props["defaultValue"].get(); + } + else if (ct == "SelectQuery") { + auto* n = static_cast(node); + if (props.contains("distinct")) n->distinct = props["distinct"].get(); + } + else if (ct == "InsertStatement") { + auto* n = static_cast(node); + if (props.contains("tableName")) n->tableName = props["tableName"].get(); + } + else if (ct == "UpdateStatement") { + auto* n = static_cast(node); + if (props.contains("tableName")) n->tableName = props["tableName"].get(); + } + else if (ct == "DeleteStatement") { + auto* n = static_cast(node); + if (props.contains("tableName")) n->tableName = props["tableName"].get(); + } + else if (ct == "JoinClause") { + auto* n = static_cast(node); + if (props.contains("joinType")) n->joinType = props["joinType"].get(); + if (props.contains("tableName")) n->tableName = props["tableName"].get(); + } + else if (ct == "WhereClause") { + auto* n = static_cast(node); + if (props.contains("expression")) n->expression = props["expression"].get(); + } + else if (ct == "IndexDefinition") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + if (props.contains("tableName")) n->tableName = props["tableName"].get(); + if (props.contains("unique")) n->unique = props["unique"].get(); + } } inline std::string generateNodeId() { diff --git a/editor/src/ast/SqlNodes.h b/editor/src/ast/SqlNodes.h new file mode 100644 index 0000000..c1e443f --- /dev/null +++ b/editor/src/ast/SqlNodes.h @@ -0,0 +1,112 @@ +#pragma once + +#include "ASTNode.h" +#include + +// SQL-specific AST nodes (Step 410) + +class TableDeclaration : public ASTNode { +public: + std::string name; + std::string schema; + TableDeclaration() { conceptType = "TableDeclaration"; } + TableDeclaration(const std::string& id_, const std::string& n, const std::string& s = "") + : name(n), schema(s) { + id = id_; + conceptType = "TableDeclaration"; + } + // children: "columns" (ColumnDefinition), "indexes" (IndexDefinition) +}; + +class ColumnDefinition : public ASTNode { +public: + std::string name; + std::string dataType; + bool nullable = true; + std::string defaultValue; + ColumnDefinition() { conceptType = "ColumnDefinition"; } + ColumnDefinition(const std::string& id_, const std::string& n, + const std::string& dt, bool isNullable = true) + : name(n), dataType(dt), nullable(isNullable) { + id = id_; + conceptType = "ColumnDefinition"; + } +}; + +class SelectQuery : public ASTNode { +public: + bool distinct = false; + SelectQuery() { conceptType = "SelectQuery"; } + // children: "columns", "from", "joins", "where" +}; + +class InsertStatement : public ASTNode { +public: + std::string tableName; + InsertStatement() { conceptType = "InsertStatement"; } + InsertStatement(const std::string& id_, const std::string& table) : tableName(table) { + id = id_; + conceptType = "InsertStatement"; + } + // children: "columns", "values" +}; + +class UpdateStatement : public ASTNode { +public: + std::string tableName; + UpdateStatement() { conceptType = "UpdateStatement"; } + UpdateStatement(const std::string& id_, const std::string& table) : tableName(table) { + id = id_; + conceptType = "UpdateStatement"; + } + // children: "set", "where" +}; + +class DeleteStatement : public ASTNode { +public: + std::string tableName; + DeleteStatement() { conceptType = "DeleteStatement"; } + DeleteStatement(const std::string& id_, const std::string& table) : tableName(table) { + id = id_; + conceptType = "DeleteStatement"; + } + // children: "where" +}; + +class JoinClause : public ASTNode { +public: + std::string joinType; // INNER/LEFT/RIGHT/FULL + std::string tableName; + JoinClause() { conceptType = "JoinClause"; } + JoinClause(const std::string& id_, const std::string& jt, const std::string& tn) + : joinType(jt), tableName(tn) { + id = id_; + conceptType = "JoinClause"; + } + // children: "on" +}; + +class WhereClause : public ASTNode { +public: + std::string expression; + WhereClause() { conceptType = "WhereClause"; } + WhereClause(const std::string& id_, const std::string& expr) : expression(expr) { + id = id_; + conceptType = "WhereClause"; + } +}; + +class IndexDefinition : public ASTNode { +public: + std::string name; + std::string tableName; + bool unique = false; + IndexDefinition() { conceptType = "IndexDefinition"; } + IndexDefinition(const std::string& id_, const std::string& n, + const std::string& table, bool isUnique = false) + : name(n), tableName(table), unique(isUnique) { + id = id_; + conceptType = "IndexDefinition"; + } + // children: "columns" +}; diff --git a/editor/tests/step410_test.cpp b/editor/tests/step410_test.cpp new file mode 100644 index 0000000..b513386 --- /dev/null +++ b/editor/tests/step410_test.cpp @@ -0,0 +1,202 @@ +// Step 410: SQL AST Nodes (12 tests) +// Tests construction + serialization roundtrip for SQL-specific node types. + +#include +#include + +#include "ast/SqlNodes.h" +#include "ast/Serialization.h" +#include "CompactAST.h" + +int main() { + int passed = 0; + + // 1. TableDeclaration construction + { + auto* t = new TableDeclaration("t1", "users", "public"); + assert(t->conceptType == "TableDeclaration"); + assert(t->name == "users"); + assert(t->schema == "public"); + delete t; + std::cout << "Test 1 PASSED: TableDeclaration construction\n"; + passed++; + } + + // 2. ColumnDefinition construction + { + auto* c = new ColumnDefinition("c1", "id", "INTEGER", false); + c->defaultValue = "0"; + assert(c->conceptType == "ColumnDefinition"); + assert(c->name == "id"); + assert(c->dataType == "INTEGER"); + assert(!c->nullable); + assert(c->defaultValue == "0"); + delete c; + std::cout << "Test 2 PASSED: ColumnDefinition construction\n"; + passed++; + } + + // 3. SelectQuery + clauses children + { + auto* q = new SelectQuery(); + q->id = "q1"; + q->distinct = true; + q->addChild("joins", new JoinClause("j1", "INNER", "orders")); + q->setChild("where", new WhereClause("w1", "id > 10")); + assert(q->distinct); + assert(q->getChildren("joins").size() == 1); + assert(q->getChild("where") != nullptr); + delete q; + std::cout << "Test 3 PASSED: SelectQuery with clauses\n"; + passed++; + } + + // 4. InsertStatement construction + { + auto* ins = new InsertStatement("i1", "users"); + ins->addChild("columns", new ColumnDefinition("c2", "name", "TEXT")); + assert(ins->conceptType == "InsertStatement"); + assert(ins->tableName == "users"); + assert(ins->getChildren("columns").size() == 1); + delete ins; + std::cout << "Test 4 PASSED: InsertStatement construction\n"; + passed++; + } + + // 5. UpdateStatement construction + { + auto* up = new UpdateStatement("u1", "users"); + up->setChild("where", new WhereClause("w2", "id = 1")); + assert(up->conceptType == "UpdateStatement"); + assert(up->tableName == "users"); + assert(up->getChild("where") != nullptr); + delete up; + std::cout << "Test 5 PASSED: UpdateStatement construction\n"; + passed++; + } + + // 6. DeleteStatement construction + { + auto* del = new DeleteStatement("d1", "logs"); + del->setChild("where", new WhereClause("w3", "created_at < now()")); + assert(del->conceptType == "DeleteStatement"); + assert(del->tableName == "logs"); + assert(del->getChild("where") != nullptr); + delete del; + std::cout << "Test 6 PASSED: DeleteStatement construction\n"; + passed++; + } + + // 7. IndexDefinition construction + { + auto* idx = new IndexDefinition("x1", "idx_users_email", "users", true); + idx->addChild("columns", new ColumnDefinition("c3", "email", "TEXT")); + assert(idx->conceptType == "IndexDefinition"); + assert(idx->name == "idx_users_email"); + assert(idx->tableName == "users"); + assert(idx->unique); + delete idx; + std::cout << "Test 7 PASSED: IndexDefinition construction\n"; + passed++; + } + + // 8. Table with columns + index JSON roundtrip + { + auto* t = new TableDeclaration("t2", "accounts", "public"); + t->addChild("columns", new ColumnDefinition("c4", "id", "BIGINT", false)); + t->addChild("columns", new ColumnDefinition("c5", "email", "TEXT", false)); + t->addChild("indexes", new IndexDefinition("x2", "idx_accounts_email", "accounts", true)); + json j = toJson(t); + auto* r = fromJson(j); + assert(r->conceptType == "TableDeclaration"); + auto* rt = static_cast(r); + assert(rt->name == "accounts"); + assert(rt->getChildren("columns").size() == 2); + assert(rt->getChildren("indexes").size() == 1); + delete t; + delete r; + std::cout << "Test 8 PASSED: table JSON roundtrip\n"; + passed++; + } + + // 9. SelectQuery JSON roundtrip + { + auto* q = new SelectQuery(); + q->id = "q2"; + q->distinct = true; + q->addChild("joins", new JoinClause("j2", "LEFT", "profiles")); + q->setChild("where", new WhereClause("w4", "active = true")); + json j = toJson(q); + auto* r = fromJson(j); + assert(r->conceptType == "SelectQuery"); + auto* rq = static_cast(r); + assert(rq->distinct); + assert(rq->getChildren("joins").size() == 1); + assert(rq->getChild("where") != nullptr); + delete q; + delete r; + std::cout << "Test 9 PASSED: select JSON roundtrip\n"; + passed++; + } + + // 10. CompactAST names + { + auto* t = new TableDeclaration("t3", "sessions"); + auto* c = new ColumnDefinition("c6", "token", "TEXT"); + auto* q = new SelectQuery(); + q->id = "q3"; + auto* d = new DeleteStatement("d2", "sessions"); + auto* w = new WhereClause("w5", "expired = true"); + auto* x = new IndexDefinition("x3", "idx_sessions_token", "sessions"); + assert(getNodeName(t) == "sessions"); + assert(getNodeName(c) == "token"); + assert(getNodeName(q) == "select"); + assert(getNodeName(d) == "sessions"); + assert(getNodeName(w) == "where"); + assert(getNodeName(x) == "idx_sessions_token"); + delete t; delete c; delete q; delete d; delete w; delete x; + std::cout << "Test 10 PASSED: CompactAST SQL names\n"; + passed++; + } + + // 11. createNode dispatch for SQL concepts + { + ASTNode* t = createNode("TableDeclaration"); + ASTNode* c = createNode("ColumnDefinition"); + ASTNode* s = createNode("SelectQuery"); + ASTNode* i = createNode("InsertStatement"); + ASTNode* u = createNode("UpdateStatement"); + ASTNode* d = createNode("DeleteStatement"); + ASTNode* j = createNode("JoinClause"); + ASTNode* w = createNode("WhereClause"); + ASTNode* x = createNode("IndexDefinition"); + assert(t && c && s && i && u && d && j && w && x); + delete t; delete c; delete s; delete i; delete u; delete d; delete j; delete w; delete x; + std::cout << "Test 11 PASSED: createNode SQL dispatch\n"; + passed++; + } + + // 12. Full query graph roundtrip + { + auto* q = new SelectQuery(); + q->id = "q4"; + q->addChild("columns", new ColumnDefinition("c7", "id", "INTEGER")); + q->setChild("from", new TableDeclaration("t4", "users")); + q->addChild("joins", new JoinClause("j3", "INNER", "orders")); + q->setChild("where", new WhereClause("w6", "users.id = orders.user_id")); + json j = toJson(q); + auto* r = fromJson(j); + assert(r->conceptType == "SelectQuery"); + assert(r->getChildren("columns").size() == 1); + assert(r->getChild("from") != nullptr); + assert(r->getChildren("joins").size() == 1); + assert(r->getChild("where") != nullptr); + delete q; + delete r; + std::cout << "Test 12 PASSED: full query graph roundtrip\n"; + passed++; + } + + std::cout << "\nResults: " << passed << "/12 tests passed\n"; + return (passed == 12) ? 0 : 1; +} diff --git a/progress.md b/progress.md index 1433100..c370319 100644 --- a/progress.md +++ b/progress.md @@ -3861,6 +3861,60 @@ Semanno annotation behavior across all 3 .NET-targeted outputs. - `editor/src/MCPServer.h` (`1679` > `600`) - `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`) +## Phase 17b: SQL Dialect Support + +### Step 410: SQL AST Nodes +**Status:** PASS (12/12 tests) + +Added first-class SQL AST node types and wired JSON serialization/deserialization +and compact-node naming support so SQL structures can participate in existing +pipeline tooling. + +**Files created:** +- `editor/src/ast/SqlNodes.h` — SQL node definitions: + - `TableDeclaration` + - `ColumnDefinition` + - `SelectQuery` + - `InsertStatement` + - `UpdateStatement` + - `DeleteStatement` + - `JoinClause` + - `WhereClause` + - `IndexDefinition` +- `editor/tests/step410_test.cpp` — 12 tests covering: + 1. table construction + 2. column construction + 3. select query + clause children + 4. insert statement construction + 5. update statement construction + 6. delete statement construction + 7. index construction + 8. table+columns+index JSON roundtrip + 9. select query JSON roundtrip + 10. compact AST naming for SQL nodes + 11. `createNode` SQL concept dispatch + 12. full query graph roundtrip + +**Files modified:** +- `editor/src/ast/Serialization.h` — SQL node property serialization, + `createNode` mapping, and deserialization +- `editor/src/CompactAST.h` — SQL node name mapping in `getNodeName` +- `editor/CMakeLists.txt` — `step410_test` target + +**Verification run:** +- `step410_test` — PASS (12/12) new step coverage +- `step409_test` — PASS (8/8) regression coverage +- `step408_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ast/SqlNodes.h` within header-size limit (`112` <= `600`) +- `editor/tests/step410_test.cpp` within test-file size guidance (`202` lines) +- `editor/src/CompactAST.h` at header-size limit (`600` <= `600`) +- Legacy oversized headers persist: + - `editor/src/ast/Serialization.h` (`1427` > `600`) + - `editor/src/MCPServer.h` (`1679` > `600`) + - `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`) + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)