Step 412: add PostgreSQL generator and pipeline routing
This commit is contained in:
@@ -2587,4 +2587,13 @@ target_link_libraries(step411_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step412_test tests/step412_test.cpp)
|
||||
target_include_directories(step412_test PRIVATE src)
|
||||
target_link_libraries(step412_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)
|
||||
|
||||
@@ -190,6 +190,9 @@ public:
|
||||
} else if (language == "vbnet" || language == "vb" || language == "vb.net") {
|
||||
VBNetGenerator gen;
|
||||
return gen.generate(ast);
|
||||
} else if (language == "postgresql" || language == "postgres") {
|
||||
PostgreSQLGenerator gen;
|
||||
return gen.generate(ast);
|
||||
} else if (language == "c") {
|
||||
CGenerator gen;
|
||||
return gen.generate(ast);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "CSharpGenerator.h"
|
||||
#include "FSharpGenerator.h"
|
||||
#include "VBNetGenerator.h"
|
||||
#include "PostgreSQLGenerator.h"
|
||||
#include "CGenerator.h"
|
||||
#include "WatGenerator.h"
|
||||
#include "CommonLispGenerator.h"
|
||||
|
||||
412
editor/src/ast/PostgreSQLGenerator.h
Normal file
412
editor/src/ast/PostgreSQLGenerator.h
Normal file
@@ -0,0 +1,412 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProjectionGenerator.h"
|
||||
#include "SqlNodes.h"
|
||||
#include "../SemannoAnnotationImpl.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
class PostgreSQLGenerator : public ProjectionGenerator,
|
||||
public SemannoAnnotationImpl<PostgreSQLGenerator> {
|
||||
public:
|
||||
std::string commentPrefix() const { return "-- "; }
|
||||
|
||||
std::string generate(const ASTNode* node) override {
|
||||
return dispatchGenerate(this, node, "-- Unknown concept: ");
|
||||
}
|
||||
|
||||
std::string visitModule(const Module* module) override {
|
||||
std::ostringstream oss;
|
||||
oss << "-- Module: " << module->name << "\n\n";
|
||||
for (const auto* stmt : module->getChildren("statements")) {
|
||||
oss << generate(stmt) << "\n";
|
||||
}
|
||||
for (const auto* fn : module->getChildren("functions")) {
|
||||
oss << generate(fn) << "\n";
|
||||
}
|
||||
return trimTrailingNewlines(oss.str());
|
||||
}
|
||||
|
||||
std::string visitFunction(const Function* function) override {
|
||||
std::ostringstream oss;
|
||||
for (const auto* anno : function->getChildren("annotations")) {
|
||||
oss << generate(anno) << "\n";
|
||||
}
|
||||
|
||||
oss << "CREATE FUNCTION " << function->name << "(";
|
||||
auto params = function->getChildren("parameters");
|
||||
for (size_t i = 0; i < params.size(); ++i) {
|
||||
if (i > 0) oss << ", ";
|
||||
oss << generate(params[i]);
|
||||
}
|
||||
oss << ")\nRETURNS VOID\nLANGUAGE plpgsql\nAS $$\nBEGIN\n";
|
||||
for (const auto* stmt : function->getChildren("body")) {
|
||||
oss << " " << generate(stmt) << "\n";
|
||||
}
|
||||
oss << "END;\n$$;";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitVariable(const Variable* variable) override {
|
||||
std::ostringstream oss;
|
||||
oss << variable->name;
|
||||
auto* init = variable->getChild("initializer");
|
||||
if (init) oss << " = " << generate(init);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitParameter(const Parameter* parameter) override {
|
||||
auto* type = parameter->getChild("type");
|
||||
return parameter->name + " " + (type ? generate(type) : "TEXT");
|
||||
}
|
||||
|
||||
std::string visitAssignment(const Assignment* assignment) override {
|
||||
auto* t = assignment->getChild("target");
|
||||
auto* v = assignment->getChild("value");
|
||||
return (t ? generate(t) : "column") + " = " + (v ? generate(v) : "NULL");
|
||||
}
|
||||
|
||||
std::string visitReturn(const Return* ret) override {
|
||||
auto* value = ret->getChild("value");
|
||||
return value ? "RETURN " + generate(value) + ";" : "RETURN;";
|
||||
}
|
||||
|
||||
std::string visitBinaryOperation(const BinaryOperation* binOp) override {
|
||||
auto* l = binOp->getChild("left");
|
||||
auto* r = binOp->getChild("right");
|
||||
return (l ? generate(l) : "NULL") + " " + binOp->op + " " + (r ? generate(r) : "NULL");
|
||||
}
|
||||
|
||||
std::string visitVariableReference(const VariableReference* varRef) override {
|
||||
return varRef->variableName;
|
||||
}
|
||||
|
||||
std::string visitIntegerLiteral(const IntegerLiteral* lit) override {
|
||||
return std::to_string(lit->value);
|
||||
}
|
||||
|
||||
std::string visitFloatLiteral(const FloatLiteral* lit) override {
|
||||
return lit->value;
|
||||
}
|
||||
|
||||
std::string visitStringLiteral(const StringLiteral* lit) override {
|
||||
return "'" + lit->value + "'";
|
||||
}
|
||||
|
||||
std::string visitBooleanLiteral(const BooleanLiteral* lit) override {
|
||||
return lit->value ? "TRUE" : "FALSE";
|
||||
}
|
||||
|
||||
std::string visitNullLiteral(const NullLiteral*) override {
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
std::string visitIfStatement(const IfStatement* stmt) override {
|
||||
std::ostringstream oss;
|
||||
auto* cond = stmt->getChild("condition");
|
||||
oss << "IF " << (cond ? generate(cond) : "TRUE") << " THEN";
|
||||
for (const auto* s : stmt->getChildren("thenBranch")) {
|
||||
oss << "\n " << generate(s);
|
||||
}
|
||||
auto elseBranch = stmt->getChildren("elseBranch");
|
||||
if (!elseBranch.empty()) {
|
||||
oss << "\nELSE";
|
||||
for (const auto* s : elseBranch) {
|
||||
oss << "\n " << generate(s);
|
||||
}
|
||||
}
|
||||
oss << "\nEND IF;";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitWhileLoop(const WhileLoop* loop) override {
|
||||
std::ostringstream oss;
|
||||
auto* cond = loop->getChild("condition");
|
||||
oss << "WHILE " << (cond ? generate(cond) : "TRUE") << " LOOP";
|
||||
for (const auto* s : loop->getChildren("body")) {
|
||||
oss << "\n " << generate(s);
|
||||
}
|
||||
oss << "\nEND LOOP;";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitForLoop(const ForLoop* loop) override {
|
||||
std::ostringstream oss;
|
||||
auto* iter = loop->getChild("iterable");
|
||||
oss << "FOR " << (loop->iteratorName.empty() ? "item" : loop->iteratorName)
|
||||
<< " IN " << (iter ? generate(iter) : "SELECT 1") << " LOOP";
|
||||
for (const auto* s : loop->getChildren("body")) {
|
||||
oss << "\n " << generate(s);
|
||||
}
|
||||
oss << "\nEND LOOP;";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitExpressionStatement(const ExpressionStatement* stmt) override {
|
||||
auto* expr = stmt->getChild("expression");
|
||||
return expr ? generate(expr) + ";" : ";";
|
||||
}
|
||||
|
||||
std::string visitUnaryOperation(const UnaryOperation* unOp) override {
|
||||
auto* operand = unOp->getChild("operand");
|
||||
return unOp->op + (operand ? generate(operand) : "");
|
||||
}
|
||||
|
||||
std::string visitFunctionCall(const FunctionCall* call) override {
|
||||
std::ostringstream oss;
|
||||
oss << call->functionName << "(";
|
||||
auto args = call->getChildren("arguments");
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
if (i > 0) oss << ", ";
|
||||
oss << generate(args[i]);
|
||||
}
|
||||
oss << ")";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitBlock(const Block* block) override {
|
||||
std::ostringstream oss;
|
||||
for (const auto* s : block->getChildren("statements")) {
|
||||
oss << generate(s) << "\n";
|
||||
}
|
||||
return trimTrailingNewlines(oss.str());
|
||||
}
|
||||
|
||||
std::string visitListLiteral(const ListLiteral* lit) override {
|
||||
std::ostringstream oss;
|
||||
oss << "ARRAY[";
|
||||
auto elems = lit->getChildren("elements");
|
||||
for (size_t i = 0; i < elems.size(); ++i) {
|
||||
if (i > 0) oss << ", ";
|
||||
oss << generate(elems[i]);
|
||||
}
|
||||
oss << "]";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitIndexAccess(const IndexAccess* access) override {
|
||||
auto* t = access->getChild("target");
|
||||
auto* i = access->getChild("index");
|
||||
return (t ? generate(t) : "") + "[" + (i ? generate(i) : "1") + "]";
|
||||
}
|
||||
|
||||
std::string visitMemberAccess(const MemberAccess* access) override {
|
||||
auto* target = access->getChild("target");
|
||||
return (target ? generate(target) : "") + "." + access->memberName;
|
||||
}
|
||||
|
||||
std::string visitPrimitiveType(const PrimitiveType* type) override {
|
||||
if (type->kind == "int") return "INTEGER";
|
||||
if (type->kind == "float" || type->kind == "double") return "DOUBLE PRECISION";
|
||||
if (type->kind == "string") return "TEXT";
|
||||
if (type->kind == "bool") return "BOOLEAN";
|
||||
if (type->kind == "void") return "VOID";
|
||||
return type->kind;
|
||||
}
|
||||
|
||||
std::string visitListType(const ListType* type) override {
|
||||
auto* e = type->getChild("elementType");
|
||||
return (e ? generate(e) : "TEXT") + "[]";
|
||||
}
|
||||
|
||||
std::string visitSetType(const SetType* type) override {
|
||||
auto* e = type->getChild("elementType");
|
||||
return (e ? generate(e) : "TEXT") + "[]";
|
||||
}
|
||||
|
||||
std::string visitMapType(const MapType*) override {
|
||||
return "JSONB";
|
||||
}
|
||||
|
||||
std::string visitTupleType(const TupleType*) override {
|
||||
return "RECORD";
|
||||
}
|
||||
|
||||
std::string visitArrayType(const ArrayType* type) override {
|
||||
auto* e = type->getChild("elementType");
|
||||
return (e ? generate(e) : "TEXT") + "[]";
|
||||
}
|
||||
|
||||
std::string visitOptionalType(const OptionalType* type) override {
|
||||
auto* inner = type->getChild("innerType");
|
||||
return inner ? generate(inner) : "TEXT";
|
||||
}
|
||||
|
||||
std::string visitCustomType(const CustomType* type) override {
|
||||
return type->typeName;
|
||||
}
|
||||
|
||||
std::string visitDerefStrategy(const DerefStrategy* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitOptimizationLock(const OptimizationLock* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitLangSpecific(const LangSpecific* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitDeallocateAnnotation(const DeallocateAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitLifetimeAnnotation(const LifetimeAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitReclaimAnnotation(const ReclaimAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitOwnerAnnotation(const OwnerAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitAllocateAnnotation(const AllocateAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitHotColdAnnotation(const HotColdAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitInlineAnnotation(const InlineAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitPureAnnotation(const PureAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
std::string visitConstExprAnnotation(const ConstExprAnnotation* a) override { return commentPrefix() + SemannoEmitter::emit(a); }
|
||||
|
||||
std::string visitTableDeclaration(const ASTNode* node) override {
|
||||
auto* table = static_cast<const TableDeclaration*>(node);
|
||||
std::ostringstream oss;
|
||||
oss << "CREATE TABLE " << table->name << " (";
|
||||
|
||||
auto cols = table->getChildren("columns");
|
||||
if (cols.empty()) {
|
||||
oss << "\n id SERIAL PRIMARY KEY";
|
||||
} else {
|
||||
for (size_t i = 0; i < cols.size(); ++i) {
|
||||
oss << "\n " << generate(cols[i]);
|
||||
if (i + 1 < cols.size()) oss << ",";
|
||||
}
|
||||
}
|
||||
oss << "\n);";
|
||||
|
||||
for (const auto* idx : table->getChildren("indexes")) {
|
||||
oss << "\n" << generate(idx);
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitColumnDefinition(const ASTNode* node) override {
|
||||
auto* col = static_cast<const ColumnDefinition*>(node);
|
||||
std::ostringstream oss;
|
||||
oss << col->name << " " << (col->dataType.empty() ? "TEXT" : col->dataType);
|
||||
if (!col->nullable) oss << " NOT NULL";
|
||||
if (!col->defaultValue.empty()) oss << " DEFAULT " << col->defaultValue;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitSelectQuery(const ASTNode* node) override {
|
||||
auto* q = static_cast<const SelectQuery*>(node);
|
||||
std::ostringstream oss;
|
||||
oss << "SELECT ";
|
||||
if (q->distinct) oss << "DISTINCT ";
|
||||
|
||||
auto cols = q->getChildren("columns");
|
||||
if (cols.empty()) {
|
||||
oss << "*";
|
||||
} else {
|
||||
for (size_t i = 0; i < cols.size(); ++i) {
|
||||
if (i > 0) oss << ", ";
|
||||
oss << generate(cols[i]);
|
||||
}
|
||||
}
|
||||
|
||||
auto* from = q->getChild("from");
|
||||
if (from) oss << " FROM " << generate(from);
|
||||
for (const auto* join : q->getChildren("joins")) {
|
||||
oss << " " << generate(join);
|
||||
}
|
||||
auto* where = q->getChild("where");
|
||||
if (where) oss << " " << generate(where);
|
||||
oss << ";";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitInsertStatement(const ASTNode* node) override {
|
||||
auto* ins = static_cast<const InsertStatement*>(node);
|
||||
std::ostringstream oss;
|
||||
oss << "INSERT INTO " << ins->tableName;
|
||||
|
||||
auto cols = ins->getChildren("columns");
|
||||
if (!cols.empty()) {
|
||||
oss << " (";
|
||||
for (size_t i = 0; i < cols.size(); ++i) {
|
||||
if (i > 0) oss << ", ";
|
||||
auto* col = static_cast<const ColumnDefinition*>(cols[i]);
|
||||
oss << col->name;
|
||||
}
|
||||
oss << ")";
|
||||
}
|
||||
|
||||
auto values = ins->getChildren("values");
|
||||
if (!values.empty()) {
|
||||
oss << " VALUES (";
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
if (i > 0) oss << ", ";
|
||||
oss << generate(values[i]);
|
||||
}
|
||||
oss << ")";
|
||||
}
|
||||
|
||||
oss << ";";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitUpdateStatement(const ASTNode* node) override {
|
||||
auto* up = static_cast<const UpdateStatement*>(node);
|
||||
std::ostringstream oss;
|
||||
oss << "UPDATE " << up->tableName << " SET ";
|
||||
|
||||
auto sets = up->getChildren("set");
|
||||
if (sets.empty()) {
|
||||
oss << "/* STUB: set clause missing */";
|
||||
} else {
|
||||
for (size_t i = 0; i < sets.size(); ++i) {
|
||||
if (i > 0) oss << ", ";
|
||||
oss << generate(sets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
auto* where = up->getChild("where");
|
||||
if (where) oss << " " << generate(where);
|
||||
oss << ";";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitDeleteStatement(const ASTNode* node) override {
|
||||
auto* del = static_cast<const DeleteStatement*>(node);
|
||||
std::ostringstream oss;
|
||||
oss << "DELETE FROM " << del->tableName;
|
||||
auto* where = del->getChild("where");
|
||||
if (where) oss << " " << generate(where);
|
||||
oss << ";";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitJoinClause(const ASTNode* node) override {
|
||||
auto* join = static_cast<const JoinClause*>(node);
|
||||
std::ostringstream oss;
|
||||
oss << (join->joinType.empty() ? "INNER" : join->joinType)
|
||||
<< " JOIN " << join->tableName;
|
||||
auto* on = join->getChild("on");
|
||||
if (on) oss << " ON " << generate(on);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string visitWhereClause(const ASTNode* node) override {
|
||||
auto* where = static_cast<const WhereClause*>(node);
|
||||
return "WHERE " + where->expression;
|
||||
}
|
||||
|
||||
std::string visitIndexDefinition(const ASTNode* node) override {
|
||||
auto* idx = static_cast<const IndexDefinition*>(node);
|
||||
std::ostringstream oss;
|
||||
oss << "CREATE ";
|
||||
if (idx->unique) oss << "UNIQUE ";
|
||||
oss << "INDEX " << idx->name << " ON " << idx->tableName;
|
||||
auto cols = idx->getChildren("columns");
|
||||
if (!cols.empty()) {
|
||||
oss << " (";
|
||||
for (size_t i = 0; i < cols.size(); ++i) {
|
||||
if (i > 0) oss << ", ";
|
||||
auto* col = static_cast<const ColumnDefinition*>(cols[i]);
|
||||
oss << col->name;
|
||||
}
|
||||
oss << ")";
|
||||
}
|
||||
oss << ";";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string trimTrailingNewlines(std::string s) {
|
||||
while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) s.pop_back();
|
||||
return s;
|
||||
}
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "HostBoundary.h"
|
||||
#include "PreprocessorNodes.h"
|
||||
#include "EnumNamespaceNodes.h"
|
||||
#include "SqlNodes.h"
|
||||
|
||||
class ProjectionGenerator : public virtual AnnotationVisitorExtended {
|
||||
public:
|
||||
@@ -84,6 +85,16 @@ public:
|
||||
virtual std::string visitEnumDeclaration(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitNamespaceDeclaration(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitTypeAlias(const ASTNode* node) { return ""; }
|
||||
// SQL visitors (Step 412)
|
||||
virtual std::string visitTableDeclaration(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitColumnDefinition(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitSelectQuery(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitInsertStatement(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitUpdateStatement(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitDeleteStatement(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitJoinClause(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitWhereClause(const ASTNode* node) { return ""; }
|
||||
virtual std::string visitIndexDefinition(const ASTNode* node) { return ""; }
|
||||
|
||||
// Host boundary visitors
|
||||
virtual std::string visitHostCall(const HostCall* node) { return ""; }
|
||||
@@ -373,6 +384,24 @@ std::string dispatchGenerate(Gen* gen, const ASTNode* node, const std::string& u
|
||||
return gen->visitNamespaceDeclaration(node);
|
||||
} else if (node->conceptType == "TypeAlias") {
|
||||
return gen->visitTypeAlias(node);
|
||||
} else if (node->conceptType == "TableDeclaration") {
|
||||
return gen->visitTableDeclaration(node);
|
||||
} else if (node->conceptType == "ColumnDefinition") {
|
||||
return gen->visitColumnDefinition(node);
|
||||
} else if (node->conceptType == "SelectQuery") {
|
||||
return gen->visitSelectQuery(node);
|
||||
} else if (node->conceptType == "InsertStatement") {
|
||||
return gen->visitInsertStatement(node);
|
||||
} else if (node->conceptType == "UpdateStatement") {
|
||||
return gen->visitUpdateStatement(node);
|
||||
} else if (node->conceptType == "DeleteStatement") {
|
||||
return gen->visitDeleteStatement(node);
|
||||
} else if (node->conceptType == "JoinClause") {
|
||||
return gen->visitJoinClause(node);
|
||||
} else if (node->conceptType == "WhereClause") {
|
||||
return gen->visitWhereClause(node);
|
||||
} else if (node->conceptType == "IndexDefinition") {
|
||||
return gen->visitIndexDefinition(node);
|
||||
}
|
||||
|
||||
return unknownPrefix + node->conceptType;
|
||||
|
||||
203
editor/tests/step412_test.cpp
Normal file
203
editor/tests/step412_test.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
// Step 412: PostgreSQL Generator Tests (12 tests)
|
||||
|
||||
#include "ast/PostgreSQLGenerator.h"
|
||||
#include "ast/PostgreSQLParser.h"
|
||||
#include "Pipeline.h"
|
||||
#include "ast/SqlNodes.h"
|
||||
#include "ast/Annotation.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
void test_generate_create_table_with_columns() {
|
||||
TEST(generate_create_table_with_columns);
|
||||
TableDeclaration t("t1", "users");
|
||||
t.addChild("columns", new ColumnDefinition("c1", "id", "SERIAL", false));
|
||||
t.addChild("columns", new ColumnDefinition("c2", "name", "TEXT", false));
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&t);
|
||||
CHECK(out.find("CREATE TABLE users") != std::string::npos, "expected create table");
|
||||
CHECK(out.find("id SERIAL NOT NULL") != std::string::npos, "expected id column");
|
||||
CHECK(out.find("name TEXT NOT NULL") != std::string::npos, "expected name column");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_select_distinct_join_where() {
|
||||
TEST(generate_select_distinct_join_where);
|
||||
SelectQuery q;
|
||||
q.id = "q1";
|
||||
q.distinct = true;
|
||||
q.addChild("columns", new ColumnDefinition("c1", "id", "INTEGER"));
|
||||
q.setChild("from", new TableDeclaration("t1", "users"));
|
||||
auto* j = new JoinClause("j1", "LEFT", "orders");
|
||||
j->setChild("on", new WhereClause("w1", "users.id = orders.user_id"));
|
||||
q.addChild("joins", j);
|
||||
q.setChild("where", new WhereClause("w2", "users.active = TRUE"));
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&q);
|
||||
CHECK(out.find("SELECT DISTINCT") != std::string::npos, "expected distinct");
|
||||
CHECK(out.find("LEFT JOIN orders") != std::string::npos, "expected join");
|
||||
CHECK(out.find("WHERE users.active = TRUE") != std::string::npos, "expected where");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_insert_statement() {
|
||||
TEST(generate_insert_statement);
|
||||
InsertStatement ins("i1", "users");
|
||||
ins.addChild("columns", new ColumnDefinition("c1", "id", "INTEGER"));
|
||||
ins.addChild("columns", new ColumnDefinition("c2", "name", "TEXT"));
|
||||
ins.addChild("values", new IntegerLiteral("v1", 1));
|
||||
ins.addChild("values", new StringLiteral("v2", "alice"));
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&ins);
|
||||
CHECK(out.find("INSERT INTO users") != std::string::npos, "expected insert target");
|
||||
CHECK(out.find("(id, name)") != std::string::npos, "expected column list");
|
||||
CHECK(out.find("VALUES (1, 'alice')") != std::string::npos, "expected values list");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_update_statement() {
|
||||
TEST(generate_update_statement);
|
||||
UpdateStatement up("u1", "users");
|
||||
auto* set = new Assignment();
|
||||
set->id = "a1";
|
||||
set->setChild("target", new VariableReference("vr1", "name"));
|
||||
set->setChild("value", new StringLiteral("s1", "bob"));
|
||||
up.addChild("set", set);
|
||||
up.setChild("where", new WhereClause("w1", "id = 1"));
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&up);
|
||||
CHECK(out.find("UPDATE users SET") != std::string::npos, "expected update");
|
||||
CHECK(out.find("name = 'bob'") != std::string::npos, "expected set assignment");
|
||||
CHECK(out.find("WHERE id = 1") != std::string::npos, "expected where");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_delete_statement() {
|
||||
TEST(generate_delete_statement);
|
||||
DeleteStatement del("d1", "users");
|
||||
del.setChild("where", new WhereClause("w1", "id = 7"));
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&del);
|
||||
CHECK(out == "DELETE FROM users WHERE id = 7;", "expected delete sql");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_index_definition() {
|
||||
TEST(generate_index_definition);
|
||||
IndexDefinition idx("x1", "idx_users_email", "users", true);
|
||||
idx.addChild("columns", new ColumnDefinition("c1", "email", "TEXT"));
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&idx);
|
||||
CHECK(out.find("CREATE UNIQUE INDEX idx_users_email ON users") != std::string::npos,
|
||||
"expected unique index");
|
||||
CHECK(out.find("(email)") != std::string::npos, "expected index column");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_module_with_statements() {
|
||||
TEST(generate_module_with_statements);
|
||||
Module mod;
|
||||
mod.id = "m1";
|
||||
mod.name = "sql_mod";
|
||||
mod.addChild("statements", new DeleteStatement("d1", "logs"));
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&mod);
|
||||
CHECK(out.find("-- Module: sql_mod") != std::string::npos, "expected module header");
|
||||
CHECK(out.find("DELETE FROM logs;") != std::string::npos, "expected statement content");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_where_clause_passthrough() {
|
||||
TEST(generate_where_clause_passthrough);
|
||||
WhereClause where("w1", "status = 'ok'");
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&where);
|
||||
CHECK(out == "WHERE status = 'ok'", "expected where passthrough");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_comment_prefix_and_semanno_output() {
|
||||
TEST(comment_prefix_and_semanno_output);
|
||||
Function fn("f1", "process");
|
||||
auto* risk = new RiskAnnotation();
|
||||
risk->id = "r1";
|
||||
risk->level = "medium";
|
||||
fn.addChild("annotations", risk);
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&fn);
|
||||
CHECK(gen.commentPrefix() == "-- ", "expected postgres comment prefix");
|
||||
CHECK(out.find("@semanno:risk") != std::string::npos, "expected semanno output");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_pipeline_generate_routing_aliases() {
|
||||
TEST(pipeline_generate_routing_aliases);
|
||||
DeleteStatement del("d1", "audit");
|
||||
Pipeline p;
|
||||
std::string o1 = p.generate(&del, "postgresql");
|
||||
std::string o2 = p.generate(&del, "postgres");
|
||||
CHECK(o1 == "DELETE FROM audit;", "postgresql route");
|
||||
CHECK(o2 == "DELETE FROM audit;", "postgres route");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_parser_to_generator_create_table_flow() {
|
||||
TEST(parser_to_generator_create_table_flow);
|
||||
auto mod = PostgreSQLParser::parsePostgreSQL(
|
||||
"CREATE TABLE events (id SERIAL, payload JSONB, tags TEXT[]);\n");
|
||||
CHECK(mod != nullptr, "module null");
|
||||
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(mod.get());
|
||||
CHECK(out.find("CREATE TABLE events") != std::string::npos, "expected create table");
|
||||
CHECK(out.find("payload JSONB") != std::string::npos, "expected jsonb column");
|
||||
CHECK(out.find("tags TEXT[]") != std::string::npos, "expected array column");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_select_edge_case_defaults_to_star() {
|
||||
TEST(generate_select_edge_case_defaults_to_star);
|
||||
SelectQuery q;
|
||||
q.id = "q2";
|
||||
PostgreSQLGenerator gen;
|
||||
std::string out = gen.generate(&q);
|
||||
CHECK(out == "SELECT *;", "expected default select star");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 412: PostgreSQL Generator Tests\n";
|
||||
|
||||
test_generate_create_table_with_columns(); // 1
|
||||
test_generate_select_distinct_join_where(); // 2
|
||||
test_generate_insert_statement(); // 3
|
||||
test_generate_update_statement(); // 4
|
||||
test_generate_delete_statement(); // 5
|
||||
test_generate_index_definition(); // 6
|
||||
test_generate_module_with_statements(); // 7
|
||||
test_generate_where_clause_passthrough(); // 8
|
||||
test_comment_prefix_and_semanno_output(); // 9
|
||||
test_pipeline_generate_routing_aliases(); // 10
|
||||
test_parser_to_generator_create_table_flow(); // 11
|
||||
test_generate_select_edge_case_defaults_to_star(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||
<< " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
55
progress.md
55
progress.md
@@ -3964,6 +3964,61 @@ and procedural patterns.
|
||||
- `editor/src/MCPServer.h` (`1679` > `600`)
|
||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`)
|
||||
|
||||
### Step 412: PostgreSQL Generator
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Added a dedicated PostgreSQL generator and end-to-end generation coverage over
|
||||
the SQL AST nodes introduced in Step 410 and parsed in Step 411.
|
||||
|
||||
**Files created:**
|
||||
- `editor/src/ast/PostgreSQLGenerator.h` — PostgreSQL code generation support:
|
||||
- SQL DDL/DML emitters for:
|
||||
- `TableDeclaration`
|
||||
- `ColumnDefinition`
|
||||
- `SelectQuery`
|
||||
- `InsertStatement`
|
||||
- `UpdateStatement`
|
||||
- `DeleteStatement`
|
||||
- `JoinClause`
|
||||
- `WhereClause`
|
||||
- `IndexDefinition`
|
||||
- module/function support for SQL-oriented output
|
||||
- annotation output via Semanno mixin with PostgreSQL comment prefix (`-- `)
|
||||
- `editor/tests/step412_test.cpp` — 12 tests covering:
|
||||
1. CREATE TABLE generation with columns
|
||||
2. SELECT DISTINCT + JOIN + WHERE generation
|
||||
3. INSERT generation with columns and values
|
||||
4. UPDATE generation with SET and WHERE
|
||||
5. DELETE generation
|
||||
6. UNIQUE INDEX generation
|
||||
7. module-level SQL statement emission
|
||||
8. WHERE clause passthrough behavior
|
||||
9. PostgreSQL comment prefix + semanno annotation output
|
||||
10. pipeline generate routing aliases (`postgresql`, `postgres`)
|
||||
11. parser-to-generator flow for CREATE TABLE
|
||||
12. edge case: empty SELECT defaults to `SELECT *;`
|
||||
|
||||
**Files modified:**
|
||||
- `editor/src/ast/ProjectionGenerator.h` — SQL visitor hooks and dispatch branches
|
||||
- `editor/src/ast/Generator.h` — include `PostgreSQLGenerator.h`
|
||||
- `editor/src/Pipeline.h` — generate routing for `postgresql`, `postgres`
|
||||
- `editor/CMakeLists.txt` — `step412_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `step412_test` — PASS (12/12) new step coverage
|
||||
- `step411_test` — PASS (12/12) regression coverage
|
||||
- `step410_test` — PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/ast/PostgreSQLGenerator.h` within header-size limit (`412` <= `600`)
|
||||
- `editor/tests/step412_test.cpp` within test-file size guidance (`203` lines)
|
||||
- `editor/src/Pipeline.h` within header-size limit (`254` <= `600`)
|
||||
- `editor/src/ast/ProjectionGenerator.h` within header-size limit (`408` <= `600`)
|
||||
- Legacy oversized headers persist:
|
||||
- `editor/src/ast/Serialization.h` (`1427` > `600`)
|
||||
- `editor/src/MCPServer.h` (`1679` > `600`)
|
||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2629` > `600`)
|
||||
|
||||
# Roadmap Planning — Sprints 12-25+
|
||||
|
||||
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
||||
|
||||
Reference in New Issue
Block a user