SQLite3使用exec出现Error 21的C++登录功能修复咨询
首先,明确错误码21对应的是SQLITE_MISUSE,这个错误表示你错误使用了SQLite API。结合你的代码和描述,我整理了几个核心问题和修复方案:
1. 优先排查:数据库连接句柄是否有效
sqlite3_exec返回SQLITE_MISUSE最常见的原因之一是传入的数据库句柄m_database.db无效(比如未成功打开数据库,或者句柄为NULL)。
你需要确保在调用login方法前,已经成功打开了数据库:
// 示例:初始化数据库连接的代码 bool LoginManager::initDatabase(const std::string& dbPath) { int rc = sqlite3_open(dbPath.c_str(), &m_database.db); if (rc != SQLITE_OK) { std::cerr << "无法打开数据库: " << sqlite3_errmsg(m_database.db) << std::endl; sqlite3_close(m_database.db); m_database.db = nullptr; return false; } return true; }
调用login前,必须确认initDatabase返回true,且m_database.db不为NULL。
2. SQL语句的语法错误(致命问题)
你生成的SQL语句使用了双引号包裹字符串:
SELECT * FROM users WHERE username = "username" AND password = "password";
在SQLite中,双引号默认是用来标识标识符(比如表名、列名)的,而非字符串字面量。这会导致SQL解析错误,进而触发API使用错误。
临时修复:改用单引号并转义特殊字符
如果暂时用字符串拼接的方式,需要用单引号包裹用户输入,同时转义输入中的单引号(避免语法错误):
// 辅助函数:转义SQL字符串中的单引号 std::string escapeSqlString(const std::string& str) { std::string escaped; for (char c : str) { if (c == '\'') { escaped += "''"; // 单引号转义为两个单引号 } else { escaped += c; } } return escaped; } // 修改login方法中的SQL拼接 std::string sqlStatement = "SELECT * FROM users WHERE username = '" + escapeSqlString(username) + "' AND password = '" + escapeSqlString(password) + "';";
推荐方案:使用参数化查询(避免SQL注入+语法错误)
直接拼接用户输入存在严重的SQL注入风险,而且容易出现语法问题,强烈建议改用sqlite3_prepare_v2进行参数绑定:
int LoginManager::login(std::string username, std::string password) { bool loginSuccess = false; sqlite3_stmt* stmt = nullptr; // 使用占位符?代替直接拼接 const char* sql = "SELECT * FROM users WHERE username = ? AND password = ?;"; // 准备SQL语句 int rc = sqlite3_prepare_v2(m_database.db, sql, -1, &stmt, nullptr); if (rc != SQLITE_OK) { std::cerr << "准备SQL语句失败: " << sqlite3_errmsg(m_database.db) << std::endl; return 0; } // 绑定参数(索引从1开始) sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, password.c_str(), -1, SQLITE_TRANSIENT); // 执行查询 rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { // 查询到匹配用户,登录成功 loginSuccess = true; m_loggedUsers.push_back(LoggedUser(username)); } else if (rc == SQLITE_DONE) { // 无匹配用户 loginSuccess = false; } else { std::cerr << "查询失败: " << sqlite3_errmsg(m_database.db) << std::endl; } // 清理资源 sqlite3_finalize(stmt); return loginSuccess ? 1 : 0; }
这种方式不需要回调函数,逻辑更直观,也彻底避免了SQL注入和语法错误。
3. 未获取错误信息导致排查困难
你调用sqlite3_exec时最后一个参数传了nullptr,无法获取具体的错误描述,这会让你难以定位问题。修改为:
char* errMsg = nullptr; int res = sqlite3_exec(m_database.db, sqlStatement.c_str(), loginCallBack, ifLoginSucceded, &errMsg); if (res != SQLITE_OK) { std::cerr << "SQL执行错误: " << errMsg << std::endl; sqlite3_free(errMsg); // 必须手动释放错误信息内存 } std::cout << "返回码: " << res << std::endl;
这样就能看到具体的错误原因,比如SQL语法错误、连接失败等。
4. 回调函数的潜在问题
如果你坚持使用回调函数,需要注意两个点:
- 初始化布尔指针:你用
new bool创建的指针未初始化,如果查询没有返回行,回调函数不会被调用,指针的值是随机的,会导致未定义行为:bool* ifLoginSucceded = new bool(false); // 初始化为false - 回调函数触发条件:只有当查询返回行时,回调函数才会被调用;如果无匹配行,回调不会执行,此时需要依赖初始值判断登录失败。
测试建议
先执行最简单的SELECT * FROM users;语句,用上述获取错误信息的方式查看具体错误,确认数据库连接和表是否正常。如果这条语句也报错,大概率是数据库连接句柄无效的问题。
内容的提问来源于stack exchange,提问作者lby

