SQLite 自定義函數,聚合,排序規則
1.使用自定義函數, 聚合以及排序規則的基本方法是使用回調函數.
這些注冊的函數的生命周期只存在於應用程序中, 並不存儲在數據庫文件中, 因此需要在每個連接建立時注冊才可以在 SQL 中進行使用.
2.排序規則
SQLite 對結果集中的字段進行排序時, SQLite 使用比較操作符如 < 或 >= 在字段內對值進行比較, 第一件事就是根據存儲類對字段值進行排列.
然后在每種存儲類中, 根據該類指定的方法進行排序. 存儲類進行排序的順序從前往后如下所示:
NULL 值 < INTEGER 和 REAL 值 < TEXT 值 < BLOB 值
(1) NULL 值具有最低的類值, 一個具有 NULL 存儲類的值比所有其他的值都小(包括其他具有 NULL 存儲類的值). 在 NULL 值之間, 沒有具體的排序順序.
(2) INTEGER 或 REAL 存儲類值高於 NULL, 它們的類值相等. INTEGER 值和 REAL 值通過其數值進行比較.
(3) TEXT 存儲類的值比 INTEGER 和 REAL 髙. 數值永遠比字符串的值低. 當兩個 TEXT 值進行比較時, 其值大小由該值中定義的 “排序法” 決定.
(4) BLOB 存儲類具有最高的類值. 具有 BLOB 類的值大於其他所有類的值. BLOB 值之間在比較時使用 C 函數 memcmp.
對 TEXT 存儲類的數據, 可以在創建表時指定字段排序規則, 也可以直接在查詢中指定它們
CREATE TABLE Foo(x TEXT COLLATE NOCASE);
SELECT * FROM Foo ORDER BY x COLLATE NOCASE;
3.相關 API
(1) 注冊自定義函數, 聚合函數
int sqlite3_create_function_v2(
sqlite3* db,
const char* zFunctionName,
int nArg,
int eTextRep,
void* pApp,
void (*xFunc)(sqlite3_context* ctx, int argc, sqlite3_value** argv),
void (*xStep)(sqlite3_context* ctx, int argc, sqlite3_value** argv),
void (*xFinal)(sqlite3_context* ctx),
void (*xDestroy)(void* pApp)
);
db: 數據庫連接句柄, 函數和聚合的指定連接. 若要使用函數, 必須在連接上注冊
zFunctionName: 在 SQL 語句中使用的函數,聚合名稱, 長度限制 255 字節
nArg: 函數參數個數, 如果為-1, 表示可變長參數. SQLite 會強制檢查參數個數, 確保傳給自定義函數的參數格式是正確的
eTextRep: 首先文本編碼格式, 如 SQLITE_UTF8, SQLITE_UTF16
pApp: 傳遞給回調函數的應用程序數據, 可供在 xFunc, xStep, xFinal 指定的回調函數中使用, 但必須使用特殊的 API 函數獲取數據
xFunc: 回調函數
xStep: 聚合步驟函數, SQLite 處理聚合結果集中的每一行都要調用 xStep, 此函數內部處理聚合邏輯, 並保存到結果值中
xFinal: finalize 聚合函數, 處理完所有的行后 SQLite 調用該函數進行整個聚合匯總處理, 此函數內部一般設置聚合功能完成的結果
在自定義函數中,指定 xFunc, 同時 xStep 和 xFinal 設置為 nullptr. 自定義聚合函數則與前者剛好相反, 需設置 xStep 和 xFinal, 同時設置 xFunc 為 nullptr.
只要 nArg 指定參數的個數不同, 或 eTextRep 指定的編碼不同, 同一個 zFunctionName 指定的函數就可以注冊多個版本, SQLite 會自動選擇最佳版本
xDestroy: 清理函數, 用於釋放應用程序數據 pApp.
(2) int sqlite3_create_collation_v2(
sqlite3* db,
const char* zFunctionName,
int eTextRep,
void* pApp,
int (*xCompare)(void* pApp, int lLen, const void* lData, int rLen, const void* rData),
void (*xDestroy)(void* pApp)
);
各個參數的意義同 sqlite3_create_function_v2. 唯一不同的是比較函數指針 xCompare, 此參數指定用於比較的函數.
(3) void xFunc(sqlite3_context* ctx, int argc, sqlite3_value** argv);
ctx: 函數/聚合的上下文環境. 它保持特殊函數調用的實例狀態, 通過它可以獲得 sqlite3_create_function_v2 供的應用程序數據參數 pApp.
要獲取該數據可以使用 void* sqlite3_user_data(sqlite3_context* ctx);
argc: 參數個數
argv: 參數值數組, 使用前需配合 argc 驗證是否越界.
(4) int xCompare(void* pApp, int lLen, const void* lData, int rLen, const void* rData);
pApp: 在 sqlite3_create_collation_v2 設置的應用程序數據
lLen: 參數1的長度
lData: 參數1的指針
rLen: 參數2的長度
rData: 參數2的指針
PS: 參數1和參數2傳入 const void*, 轉換為 const char* 后, 字符串不一定是以 '\0' 結束. 因此類似以下的操作結果值不確定:
string lText{static_cast<const char*>(lData)};
應使用 string 的另一個構造函數
string lText{static_cast<const char*>(lData), 0, lLen};
(5) void *sqlite3_user_data(sqlite3_context* ctx);
取得 sqlite3_create_function_v2 調用時傳入的 pApp 參數值. 對所有的回調函數, 此函數返回的參數是共享的.
(6) void* sqlite3_aggregate_context(sqlite3_context* ctx, int nBytes);
此函數為每個特定的實例分配狀態, 第一次調用該函數時執行一個特殊的聚合函數, 分配 nBytes 字節內存, 並賦值為0.
注意, 分配內存后會賦值為0, 了解這一點非常重要, 這要求我們設置自定義類型保存聚合數據時, 必須是 POD 類型.
此后同一個 ctx 指定的上下文環境中的調用(同一個聚合的實例), 返回相同的數據, 即地址相同.
聚合函數必須要使用此函數, 因此聚合的目前, 就是可以在調用之間存儲狀態以便堆積數據.
當聚合完成 finalize()回調時, 由 SQLite 自動釋放內存, 無須手動釋放.
(7) Type sqlite3_value_Type(sqlite3_value* value);
返回 Type 類型的數據. 獲取標量值:
double sqlite3_value_double(sqlite3_value*);
int sqlite3_value_int(sqlite3_value*);
sqlite3_int64 sqlite3_value_int64(sqlite3_value*);
獲取數組值:
int sqlite3_value_bytes(sqlite3_value*); // 返回 blob 緩沖區中數據長度
const void* sqlite3_value_blob(sqlite3_value*); // 返回 blob 緩沖區中數據指針
const unsigned char* sqlite3_value_text(sqlite3_value*); // 注意返回值不是 const char*
檢查數據類型的函數:
int sqlite3_value_type(sqlite3_value*); // SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT
int sqlite3_value_numeric_type(sqlite3_value*);
配對使用 sqlite3_value_bytes 和 sqlite3_value_blob 函數, 就可以從結果中復制數據. 如:
int valueLen = sqlite3_value_bytes(argv[0]);
char* buf = static_cast<char*>(sqlite3_malloc(valueLen + 1));
(8) 設置結果, SQL 查詢語句中返回的值
void sqlite3_result_text(sqlite3_context* ctx, const char* data, int len, void(*free)(void* data));
void sqlite3_result_blob(sqlite3_context* ctx, const void* data, int bytes, void(*free)(void* data));
void sqlite3_result_double(sqlite3_context*, double);
void sqlite3_result_int64(sqlite3_context*, sqlite3_int64);
void sqlite3_result_null(sqlite3_context*);
其中 pCleaner 指向釋放內存的函數指針
設置出錯信息
void sqlite3_result_error(sqlite3_context*, const char*, int);
void sqlite3_result_error_toobig(sqlite3_context*);
void sqlite3_result_error_nomem(sqlite3_context*);
void sqlite3_result_error_code(sqlite3_context*, int);
清理內存
void free(void* p); 用戶自定義函數調用完成后, 調用指定的清理函數釋放內存. 即 sqlite3_result_text 和 sqlite3_result_blob 的實參 data
預定義清理函數
#define SQLITE_STATIC ((void(*)(void *))0)
#define SQLITE_TRANSIENT ((void(*)(void*))-l)
SQLITE_STATIC 意味着數組內存駐留在非托管空間, SQLite 不需要數據副本, 也不會試圖清理它.
SQLITE_TRANSIENT 提示數組數據有可能改變, SQLite 使用 sqlite3_malloc 為自己復制一份數據.
(9) 內存分配與釋放
void* sqlite3_malloc(int);
void* sqlite3_realloc(void*, int);
void sqlite3_free(void*);
////////////////////////////////////////////////////////////////////////// // 測試代碼 #include <cassert> #include <fstream> #include <iostream> #include <regex> #include <sstream> #include <string> #include "Memory.h" #include "Rand.h" #include "sqlite3.h" using namespace std; //#define OUTPUT_FILE #if defined(OUTPUT_FILE) #define ERR fout << "[" << __func__ << ":" << __LINE__ << "] Error! " #define INFO fout #define DEBUG fout #else #define ERR cerr << "[" << __func__ << ":" << __LINE__ << "] Error! " #define INFO cout #define DEBUG cerr #endif #define MEMINFO(msg) Memory::print(msg) #if defined(OUTPUT_FILE) fstream fout; #endif void initOutputStream() { #if defined(OUTPUT_FILE) fout.open("F:/Sqlite/mysql/log.txt", fstream::ios_base::out | fstream::ios_base::trunc); #endif } void testClean(void* p) { DEBUG << "clean:" << reinterpret_cast<int>(p) << endl; } string getString(sqlite3_value* value) { const char* text = reinterpret_cast<const char*>(sqlite3_value_text(value)); return text ? string{text} : ""; } /** * 將字符串拆分成非全數字組成的前綴,和全數字組成的后綴 */ bool splitName(const string& text, string& prefix, string& suffix) { regex reg("^(.*[^\\d]+)(\\d+)$|^(\\d+)$"); smatch match; if (regex_match(text, match, reg)) { prefix = match.str(1); suffix = prefix.empty() ? match.str(3) : match.str(2); return true; } prefix = text; return false; } /** * 去掉字符串前面的0 */ string trimPrefixZero(const string& text) { size_t pos = text.find_first_not_of('0'); if (string::npos != pos && 0 != pos) { return text.substr(pos); } return text; } /** * 比較兩個數字字符串序列的大小 */ int compareDigit(const string& lhs, const string& rhs) { string lText = trimPrefixZero(lhs); string rText = trimPrefixZero(rhs); if (lText.length() != rText.length()) { return lText.length() - rText.length(); } for (size_t i = 0; i < lText.length(); ++i) { if (lText.at(i) != rText.at(i)) { return lText.at(i) - rText.at(i); } } return 0; } sqlite3* openDB(const string& file) { sqlite3* db{}; int ret = sqlite3_open_v2(file.c_str(), &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, 0); if (SQLITE_OK != ret) { ERR << "sqlite3_open_v2 failed." << endl; return nullptr; } return db; } bool closeDB(sqlite3* db) { int ret = sqlite3_close_v2(db); if (SQLITE_OK != ret) { ERR << "sqlite3_close_v2 failed." << endl; return false; } return true; } bool execSQL(sqlite3* db, const string& sql) { char* errmsg{}; int ret = sqlite3_exec(db, sql.c_str(), 0, 0, &errmsg); if (SQLITE_OK != ret) { ERR << "sqlite3_exec failed." << errmsg << endl; } sqlite3_free(errmsg); return SQLITE_OK == ret; } bool printSQL(sqlite3* db, const string& sql) { int ret{}; sqlite3_stmt* stmt{}; ret = sqlite3_prepare_v2(db, sql.c_str(), sql.length(), &stmt, 0); if (SQLITE_OK != ret) { ERR << "sqlite3_prepare_v2 failed." << sqlite3_errmsg(db) << endl; return false; } INFO << sql << endl; int colCount = sqlite3_column_count(stmt); if (colCount > 0) { for (int col = 0; col < colCount; ) { INFO << sqlite3_column_name(stmt, col); if (++col != colCount) { INFO << '\t'; } } INFO << endl; } do { ret = sqlite3_step(stmt); if (SQLITE_ROW != ret) { break; } for (int col = 0; col < colCount; ) { const char* text = reinterpret_cast<const char*>(sqlite3_column_text(stmt, col)); INFO << (text ? text : ""); if (++col != colCount) { INFO << '\t'; } } INFO << endl; } while (true); if (SQLITE_DONE != ret) { ERR << "sqlite3_step failed." << sqlite3_errmsg(db) << endl; } ret = sqlite3_finalize(stmt); if (SQLITE_OK != ret) { ERR << "sqlite3_finalize failed." << sqlite3_errmsg(db) << endl; return false; } return true; } // SQLite 函數 void echo(sqlite3_context* ctx, int argc, sqlite3_value** argv) { assert(1 == argc); // DEBUG << "user data:" << reinterpret_cast<int>(sqlite3_user_data(ctx)) << endl; const char* text{reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))}; sqlite3_result_text(ctx, text, -1, 0); } void strcat_column(sqlite3_context* ctx, int argc, sqlite3_value** argv) { const char* delimiter{"\t"}; const int DELIMITER_LEN = strlen(delimiter); int len = 0; for (int i = 0; i < argc; ++i) { len += sqlite3_value_bytes(argv[i]); len += DELIMITER_LEN; } char* buf = static_cast<char*>(sqlite3_malloc(len)); int pos = 0; for (int i = 0; i < argc; ) { int bytes = sqlite3_value_bytes(argv[i]); memcpy(buf + pos, sqlite3_value_blob(argv[i]), bytes); pos += bytes; if (++i != argc) { memcpy(buf + pos, delimiter, DELIMITER_LEN); pos += DELIMITER_LEN; } } buf[pos] = 0; sqlite3_result_text(ctx, buf, -1, 0); } /** * 找出值為匹配前綴,並且后綴在指定范圍內的值 */ void peekName(sqlite3_context* ctx, int argc, sqlite3_value** argv) { assert(4 == argc); bool isFound{false}; string vPrefix; string vSuffix; string value = getString(argv[0]); if (splitName(value, vPrefix, vSuffix)) { string prefix = getString(argv[1]); if (vPrefix == prefix) { string suffixFrom = getString(argv[2]); string suffixTo = getString(argv[3]); if (compareDigit(vSuffix, suffixFrom) >= 0 && compareDigit(vSuffix, suffixTo) <= 0) { isFound = true; } } } sqlite3_result_int(ctx, isFound); } // SQLite 聚合函數 struct AggregateCharData { int len{}; char* buf{}; }; void strcat_step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { assert(2 == argc); AggregateCharData* pData{static_cast<AggregateCharData*>(sqlite3_aggregate_context(ctx, sizeof(AggregateCharData)))}; if (!pData) { ERR << "Alloc AggregateData failed!" << endl; return; } int valueLen = sqlite3_value_bytes(argv[0]); if (!pData->buf) { pData->buf = static_cast<char*>(sqlite3_malloc(valueLen + 1)); } else { int delimiterLen = sqlite3_value_bytes(argv[1]); int len = valueLen + delimiterLen + pData->len + 1; pData->buf = static_cast<char*>(sqlite3_realloc(pData->buf, len)); memcpy(pData->buf + pData->len, sqlite3_value_blob(argv[1]), delimiterLen); pData->len += delimiterLen; } memcpy(pData->buf + pData->len, sqlite3_value_blob(argv[0]), valueLen); pData->len += valueLen; } void strcat_final(sqlite3_context* ctx) { AggregateCharData* pData{static_cast<AggregateCharData*>(sqlite3_aggregate_context(ctx, sizeof(AggregateCharData)))}; if (!pData || !pData->buf) { sqlite3_result_text(ctx, pData->buf, pData->len, sqlite3_free); } } /** * SQLite 排序示例,將值拆分成非全數字組成的前綴,和全數字組成的后綴,前綴不同時,按字典排序;前綴相同時,后綴按數字大小排序 **/ int compareName(void*, int lhsLen, const void* lhsData, int rhsLen, const void* rhsData) { string lText(static_cast<const char*>(lhsData), 0, lhsLen); string lPrefix; string lSuffix; if (!splitName(lText, lPrefix, lSuffix)) { return 0; } string rText(static_cast<const char*>(rhsData), 0, rhsLen); string rPrefix; string rSuffix; if (!splitName(rText, rPrefix, rSuffix)) { return 0; } if (lPrefix != rPrefix) { return lPrefix.compare(rPrefix); } return compareDigit(lSuffix, rSuffix); } string randNumString() { static const string NUMBERS{"012345678900"}; string ret; int len = Rand::rand(3, 10); for (int i = 0; i < len; ++i) { ret.push_back(NUMBERS.at(Rand::rand(0, NUMBERS.length() - 1))); } return ret; } string randPrefix() { static const string PREFIX_STR{"01234567890123456789012345678901234567890123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-"}; string ret; int len = Rand::rand(3, 8); for (int i = 0; i < len; ++i) { ret.push_back(PREFIX_STR.at(Rand::rand(0, PREFIX_STR.length() - 1))); } return ret; } void insertTestData(sqlite3* db) { execSQL(db, "INSERT INTO Foo VALUES(null, 1, 2);"); execSQL(db, "INSERT INTO Foo VALUES(null, 10.5, 20.1);"); execSQL(db, "INSERT INTO Foo VALUES(null, 'abc', 'hehe');"); vector<string> prefixs{randPrefix(), randPrefix(), randPrefix(), randPrefix()}; ostringstream stm; for (int i = 0; i < 30; ++i) { string prefix = prefixs.at(Rand::rand(0, prefixs.size() - 1)); stm.str(""); stm << "INSERT INTO Foo VALUES(null, '" << prefix << randNumString() << "', " << Rand::rand(1, 999) << ");"; execSQL(db, stm.str()); stm.str(""); stm << "INSERT INTO Foo VALUES(null, '" << prefix << randNumString() << "', " << Rand::rand(1, 999) << ");"; execSQL(db, stm.str()); stm.str(""); stm << "INSERT INTO Foo VALUES(null, '" << prefix << randNumString() << "', " << Rand::rand(1, 999) << ");"; execSQL(db, stm.str()); } } int main(int, char**) { initOutputStream(); MEMINFO("start"); sqlite3* db = openDB("Foo.db"); int ret{}; MEMINFO("create table"); execSQL(db, "CREATE TABLE IF NOT EXISTS Foo" "(_id INTEGER PRIMARY KEY, name TEXT, info TEXT);"); // insertTestData(db); MEMINFO("query all"); printSQL(db, "SELECT * FROM Foo;"); MEMINFO("set echo"); ret = sqlite3_create_function_v2(db, "echo", 1, SQLITE_UTF8, reinterpret_cast<void*>(1), echo, 0, 0, testClean); if (SQLITE_OK != ret) { ERR << "sqlite3_create_function_v2 -> echo." << sqlite3_errmsg(db) << endl; } MEMINFO("query using echo"); printSQL(db, "SELECT echo('Hello SQLite!') AS replay;"); printSQL(db, "SELECT _id, echo(name) AS name FROM Foo;"); MEMINFO("set strcat_column"); ret = sqlite3_create_function_v2(db, "strcat_column", -1, SQLITE_UTF8, 0, strcat_column, 0, 0, 0); if (SQLITE_OK != ret) { ERR << "sqlite3_create_function_v2 -> strcat_column." << sqlite3_errmsg(db) << endl; } MEMINFO("query using strcat_column"); printSQL(db, "SELECT strcat_column(_id, name, info) AS list FROM Foo;"); MEMINFO("set strcat"); ret = sqlite3_create_function_v2(db, "strcat", 2, SQLITE_UTF8, 0, 0, strcat_step, strcat_final, 0); MEMINFO("query using strcat"); if (SQLITE_OK != ret) { ERR << "sqlite3_create_function_v2 -> strcat_column." << sqlite3_errmsg(db) << endl; } printSQL(db, "SELECT strcat(_id, ',') AS ids FROM Foo;"); printSQL(db, "SELECT strcat(name, ',') AS names FROM Foo;"); printSQL(db, "SELECT strcat(info, ',') AS infos FROM Foo;"); MEMINFO("set peekName"); ret = sqlite3_create_function_v2(db, "peekName", 4, SQLITE_UTF8, 0, peekName, 0, 0, 0); MEMINFO("query using peekName"); if (SQLITE_OK != ret) { ERR << "sqlite3_create_function_v2 -> peekName." << sqlite3_errmsg(db) << endl; } printSQL(db, "SELECT * FROM Foo WHERE peekName(name, '9Y58h', 293, 62804);"); MEMINFO("set compareName"); ret = sqlite3_create_collation_v2(db, "compareName", SQLITE_UTF8, reinterpret_cast<void*>(2), compareName, testClean); MEMINFO("query using compareName"); if (SQLITE_OK != ret) { ERR << "sqlite3_create_collation_v2 -> compareName." << sqlite3_errmsg(db) << endl; } printSQL(db, "SELECT * FROM Foo ORDER BY name collate compareName;"); MEMINFO("close db"); closeDB(db); MEMINFO("end"); return 0; }
////////////////////////////////////////////////////////////////////////// // 隨機數 #ifndef RAND_H #define RAND_H #include <random> class Rand { public: static int rand(int minValue, int maxValue); private: static std::default_random_engine DEFAULT_ENGINE; }; #endif // RAND_H #include "Rand.h" #include <ctime> std::default_random_engine Rand::DEFAULT_ENGINE(time(0)); int Rand::rand(int minValue, int maxValue) { std::uniform_int_distribution<int> d(minValue, maxValue); return d(DEFAULT_ENGINE); }
////////////////////////////////////////////////////////////////////////// // 內存打印工具 #ifndef MEMORY_H #define MEMORY_H #include <string> using std::string; class Memory { public: static void print(const string& tag = ""); private: static string format(long long); static long long m_prevAvaiPhys; }; #endif // MEMORY_H #include "Memory.h" #include <iostream> #include <sstream> #include "windows.h" long long Memory::m_prevAvaiPhys{}; void Memory::print(const string& tag) { MEMORYSTATUSEX mem{}; mem.dwLength = sizeof(MEMORYSTATUSEX); GlobalMemoryStatusEx(&mem); std::cout << "virtual:" << format(mem.ullAvailVirtual) << "/" << format(mem.ullTotalVirtual) << " physical:" << format(mem.ullAvailPhys) << "/" << format(mem.ullTotalPhys); if (0 != m_prevAvaiPhys) { std::cout << ", reduce physical:" << (long long)(m_prevAvaiPhys - mem.ullAvailPhys) << "B"; } m_prevAvaiPhys = mem.ullAvailPhys; std::cout << " [" << tag << "]" << std::endl; } string Memory::format(long long bytes) { std::ostringstream stm; //stm << (bytes >> 20) << "MB"; stm << (bytes >> 10) << "KB"; return stm.str(); }