@@ -814,6 +814,225 @@ void create_model::createModelFromSqlite3(
814814}
815815#endif
816816
817+ // DuckDB support implementation [dq 2025-11-19]
818+ #if USE_DUCKDB
819+ void create_model::createModelClassFromDuckdb (
820+ const std::string &path,
821+ const DbClientPtr &client,
822+ const std::string &tableName,
823+ const Json::Value &restfulApiConfig,
824+ const std::vector<Relationship> &relationships,
825+ const std::vector<ConvertMethod> &convertMethods)
826+ {
827+ HttpViewData data;
828+ auto className = nameTransform (tableName, true );
829+ data[" className" ] = className;
830+ data[" tableName" ] = toLower (tableName);
831+ data[" hasPrimaryKey" ] = (int )0 ;
832+ data[" primaryKeyName" ] = " " ;
833+ data[" dbName" ] = std::string (" duckdb" );
834+ data[" rdbms" ] = std::string (" duckdb" );
835+ data[" relationships" ] = relationships;
836+ data[" convertMethods" ] = convertMethods;
837+ std::vector<ColumnInfo> cols;
838+
839+ // 使用 PRAGMA table_info 查询表结构(DuckDB 兼容 SQLite)
840+ std::string sql = " PRAGMA table_info(" + tableName + " );" ;
841+ *client << sql << Mode::Blocking >> [&](const Result &result) {
842+ size_t index = 0 ;
843+ for (auto &row : result)
844+ {
845+ bool notnull = row[" notnull" ].as <bool >();
846+ bool primary = row[" pk" ].as <int >();
847+ auto type = row[" type" ].as <std::string>();
848+ std::transform (type.begin (),
849+ type.end (),
850+ type.begin (),
851+ [](unsigned char c) { return tolower (c); });
852+ ColumnInfo info;
853+ info.index_ = index++;
854+ info.dbType_ = " duckdb" ; // 标记为 DuckDB 类型
855+ info.colName_ = row[" name" ].as <std::string>();
856+ info.colTypeName_ = nameTransform (info.colName_ , true );
857+ info.colValName_ = nameTransform (info.colName_ , false );
858+ info.notNull_ = notnull;
859+ info.colDatabaseType_ = type;
860+ info.isPrimaryKey_ = primary;
861+
862+ // DuckDB 自动增长检测
863+ if (primary)
864+ {
865+ // 查询表的 DDL 来检测 GENERATED BY DEFAULT AS IDENTITY
866+ *client << " SELECT sql FROM duckdb_tables() WHERE table_name=?"
867+ << tableName << Mode::Blocking >>
868+ [&](bool isNull, std::string sql) {
869+ if (!isNull)
870+ {
871+ std::transform (sql.begin (),
872+ sql.end (),
873+ sql.begin (),
874+ [](unsigned char c) {
875+ return tolower (c);
876+ });
877+ // DuckDB 使用 GENERATED BY DEFAULT AS IDENTITY
878+ if (sql.find (" generated" ) != std::string::npos &&
879+ sql.find (" identity" ) != std::string::npos)
880+ {
881+ info.isAutoVal_ = true ;
882+ }
883+ }
884+ } >>
885+ [](const DrogonDbException &e) {
886+ // 忽略错误
887+ };
888+ }
889+
890+ auto defaultVal = row[" dflt_value" ].as <std::string>();
891+ if (!defaultVal.empty ())
892+ {
893+ info.hasDefaultVal_ = true ;
894+ }
895+
896+ // DuckDB 类型映射到 C++ 类型
897+ if (type.find (" bigint" ) != std::string::npos)
898+ {
899+ info.colType_ = " int64_t" ;
900+ info.colLength_ = 8 ;
901+ }
902+ else if (type.find (" int" ) != std::string::npos)
903+ {
904+ // DuckDB 的 INTEGER 默认是 64 位
905+ info.colType_ = " int64_t" ;
906+ info.colLength_ = 8 ;
907+ }
908+ else if (type.find (" smallint" ) != std::string::npos)
909+ {
910+ info.colType_ = " int16_t" ;
911+ info.colLength_ = 2 ;
912+ }
913+ else if (type.find (" tinyint" ) != std::string::npos)
914+ {
915+ info.colType_ = " int8_t" ;
916+ info.colLength_ = 1 ;
917+ }
918+ else if (type.find (" varchar" ) != std::string::npos ||
919+ type == " text" || type == " string" )
920+ {
921+ info.colType_ = " std::string" ;
922+ }
923+ else if (type.find (" double" ) != std::string::npos)
924+ {
925+ info.colType_ = " double" ;
926+ info.colLength_ = sizeof (double );
927+ }
928+ else if (type == " real" || type == " float" )
929+ {
930+ info.colType_ = " float" ;
931+ info.colLength_ = sizeof (float );
932+ }
933+ else if (type.find (" decimal" ) != std::string::npos ||
934+ type.find (" numeric" ) != std::string::npos)
935+ {
936+ // 使用字符串存储精确小数,避免浮点误差
937+ info.colType_ = " std::string" ;
938+ }
939+ else if (type == " boolean" || type == " bool" )
940+ {
941+ info.colType_ = " bool" ;
942+ info.colLength_ = 1 ;
943+ }
944+ else if (type == " blob" )
945+ {
946+ info.colType_ = " std::vector<char>" ;
947+ }
948+ else if (type == " date" || type == " datetime" ||
949+ type.find (" timestamp" ) != std::string::npos)
950+ {
951+ info.colType_ = " ::trantor::Date" ;
952+ }
953+ else
954+ {
955+ // 未知类型默认使用字符串
956+ info.colType_ = " std::string" ;
957+ }
958+ cols.push_back (std::move (info));
959+ }
960+ } >> [](const DrogonDbException &e) {
961+ std::cerr << e.base ().what () << std::endl;
962+ exit (1 );
963+ };
964+
965+ // 处理主键
966+ std::vector<std::string> pkNames, pkTypes, pkValNames;
967+ for (auto const &col : cols)
968+ {
969+ if (col.isPrimaryKey_ )
970+ {
971+ pkNames.push_back (col.colName_ );
972+ pkTypes.push_back (col.colType_ );
973+ pkValNames.push_back (nameTransform (col.colName_ , false ));
974+ }
975+ }
976+ data[" hasPrimaryKey" ] = (int )pkNames.size ();
977+ if (pkNames.size () == 1 )
978+ {
979+ data[" primaryKeyName" ] = pkNames[0 ];
980+ data[" primaryKeyType" ] = pkTypes[0 ];
981+ }
982+ else if (pkNames.size () > 1 )
983+ {
984+ // 复合主键不支持自动增长
985+ for (auto &col : cols)
986+ {
987+ col.isAutoVal_ = false ;
988+ }
989+ data[" primaryKeyName" ] = pkNames;
990+ data[" primaryKeyType" ] = pkTypes;
991+ data[" primaryKeyValNames" ] = pkValNames;
992+ }
993+
994+ data[" columns" ] = cols;
995+ std::ofstream headerFile (path + " /" + className + " .h" , std::ofstream::out);
996+ std::ofstream sourceFile (path + " /" + className + " .cc" ,
997+ std::ofstream::out);
998+ auto templ = DrTemplateBase::newTemplate (" model_h.csp" );
999+ headerFile << templ->genText (data);
1000+ templ = DrTemplateBase::newTemplate (" model_cc.csp" );
1001+ sourceFile << templ->genText (data);
1002+ createRestfulAPIController (data, restfulApiConfig);
1003+ }
1004+
1005+ void create_model::createModelFromDuckdb (
1006+ const std::string &path,
1007+ const DbClientPtr &client,
1008+ const Json::Value &restfulApiConfig,
1009+ std::map<std::string, std::vector<Relationship>> &relationships,
1010+ std::map<std::string, std::vector<ConvertMethod>> &convertMethods)
1011+ {
1012+ // 查询所有用户表(排除系统表)
1013+ *client << " SELECT table_name FROM information_schema.tables "
1014+ " WHERE table_schema = 'main' AND table_type = 'BASE TABLE' "
1015+ " ORDER BY table_name"
1016+ << Mode::Blocking >>
1017+ [&](bool isNull, std::string &&tableName) mutable {
1018+ if (!isNull)
1019+ {
1020+ std::cout << " table name:" << tableName << std::endl;
1021+ createModelClassFromDuckdb (path,
1022+ client,
1023+ tableName,
1024+ restfulApiConfig,
1025+ relationships[tableName],
1026+ convertMethods[tableName]);
1027+ }
1028+ } >>
1029+ [](const DrogonDbException &e) {
1030+ std::cerr << e.base ().what () << std::endl;
1031+ exit (1 );
1032+ };
1033+ }
1034+ #endif
1035+
8171036void create_model::createModel (const std::string &path,
8181037 const Json::Value &config,
8191038 const std::string &singleModelName)
@@ -1119,6 +1338,82 @@ void create_model::createModel(const std::string &path,
11191338 std::cerr << " Drogon does not support Sqlite3, please install Sqlite3 "
11201339 " development environment before installing drogon"
11211340 << std::endl;
1341+ #endif
1342+ }
1343+ // DuckDB database type handling [dq 2025-11-19]
1344+ else if (dbType == " duckdb" )
1345+ {
1346+ #if USE_DUCKDB
1347+ auto filename = config.get (" filename" , " " ).asString ();
1348+ if (filename == " " )
1349+ {
1350+ std::cerr << " Please configure filename in " << path
1351+ << " /model.json " << std::endl;
1352+ exit (1 );
1353+ }
1354+ std::string connStr = " filename=" + escapeConnString (filename);
1355+ DbClientPtr client =
1356+ drogon::orm::DbClient::newDuckDbClient (connStr, 1 );
1357+ std::cout << " Connect to DuckDB..." << std::endl;
1358+ if (forceOverwrite_)
1359+ {
1360+ std::this_thread::sleep_for (1s);
1361+ }
1362+ else
1363+ {
1364+ std::cout << " Source files in the " << path
1365+ << " folder will be overwritten, continue(y/n)?\n " ;
1366+ auto in = getchar ();
1367+ (void )getchar (); // get the return key
1368+ if (in != ' Y' && in != ' y' )
1369+ {
1370+ std::cout << " Abort!" << std::endl;
1371+ exit (0 );
1372+ }
1373+ }
1374+
1375+ if (singleModelName.empty ())
1376+ {
1377+ auto tables = config[" tables" ];
1378+ if (!tables || tables.size () == 0 )
1379+ createModelFromDuckdb (path,
1380+ client,
1381+ restfulApiConfig,
1382+ relationships,
1383+ convertMethods);
1384+ else
1385+ {
1386+ for (int i = 0 ; i < (int )tables.size (); ++i)
1387+ {
1388+ auto tableName = tables[i].asString ();
1389+ std::transform (tableName.begin (),
1390+ tableName.end (),
1391+ tableName.begin (),
1392+ [](unsigned char c) { return tolower (c); });
1393+ std::cout << " table name:" << tableName << std::endl;
1394+ createModelClassFromDuckdb (path,
1395+ client,
1396+ tableName,
1397+ restfulApiConfig,
1398+ relationships[tableName],
1399+ convertMethods[tableName]);
1400+ }
1401+ }
1402+ }
1403+ else
1404+ {
1405+ createModelClassFromDuckdb (path,
1406+ client,
1407+ singleModelName,
1408+ restfulApiConfig,
1409+ relationships[singleModelName],
1410+ convertMethods[singleModelName]);
1411+ }
1412+
1413+ #else
1414+ std::cerr << " Drogon does not support DuckDB, please install DuckDB "
1415+ " development environment before installing drogon"
1416+ << std::endl;
11221417#endif
11231418 }
11241419 else if (dbType == " no dbms" )
@@ -1129,7 +1424,9 @@ void create_model::createModel(const std::string &path,
11291424 }
11301425 else
11311426 {
1132- std::cerr << " Does not support " << dbType << std::endl;
1427+ std::cerr << " Does not support " << dbType
1428+ << " . Supported databases: postgresql, mysql, sqlite3, duckdb"
1429+ << std::endl;
11331430 exit (1 );
11341431 }
11351432}
0 commit comments