ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 第7章 扩充C API 本章介绍SQLite的新技巧。前一章涉及一般的数据库操作,本章将开始创新。扩充API提供3种基本方法来扩展(或说定制)SQLite,包括:创建用户自定义函数、聚合和排序序列。 用户自定义函数是编写用于特定应用的SQL函数。一旦注册,就可以在SQL中被调用。 本章将涉及所有这3个用户定义的扩展工具及与之相关的API函数。你会看到,当与其它工具,如触发器和冲突解决等结合在一起时,用户定义的扩充API是强有力的,并能为SQLite创造非凡特色。 空注:本章内容对编程还是很有用的,但我对这部分内容只是略做浏览。关心这部分内容的兄弟还是得自己看原文。 ## API 用户自定义聚合、函数和排序法的生命同期是基于连接的。它们不存储在数据库中。有时你可能会把它们当成存储过程看待,而忘记了它们是在数据库之外的。它们存在于程序库(librarie)中,其生命周期严格地限制在你的程序之内。I ## 注册函数 ## 步进函数 自定义函数和聚合的步进函数是一样的,可如下定义: ``` void fn(sqlite3_context* ctx, int nargs, sqlite3_value** values) ``` ## 返回值 ## 函数 ## 返回值 ## 一个完整的例子 Listing 7-2\. The main Function ``` int main(int argc, char **argv) {     int rc;     sqlite3 *db;     const char* sql;     sqlite3_open("test.db", &db);     sqlite3_create_function( db, "function", -1, SQLITE_UTF8, NULL,                              function, NULL, NULL);     /* Turn on SQL logging */     //log_sql(db, 1);     /* Call function with one text argument. */     execute(db, "select function(1)");     /* Call function with several arguments of various types. */     execute(db, "select function(1, 2.71828)");     /* Call function with variable arguments, the first argument’s value     ** being 'fail'. This will trigger the function to call     ** sqlite3_result_error(). */     execute(db, "select function('fail', 1, 2.71828, 'three', X'0004', NULL)");     /* Done */     sqlite3_close(db);     return 0;    } ``` Listing 7-3\. A Vanilla User-Defined Function ``` void function(sqlite3_context* ctx, int nargs, sqlite3_value** values) {     int i; const char *msg;     fprintf(stdout, "function() : Called with %i arguments\n", nargs);     for(i=0; i < nargs; i++) {         fprintf( stdout, "    arg %i: value=%-7s type=%i\n", i,                  sqlite3_value_text(values[i]),                  sqlite3_value_type(values[i]));     }     if(strcmp((const char *)sqlite3_value_text(values[0]), "fail") == 0) {         msg = "function() : Failing because you told me to.";         sqlite3_result_error(ctx, msg, strlen(msg));         fprintf(stdout, "\n");         return;     }     fprintf(stdout, "\n");     sqlite3_result_int(ctx, 0); } ``` ## 一个实际的应用程序 ## 聚合 ![](https://box.kancloud.cn/2016-05-17_573b066d5636b.jpg) Figure 7-1\. Query processing with aggregates ## 一个实际的例子 Listing 7-9\. The sum_int() Test Program ``` int main(int argc, char **argv) {     int rc;     sqlite3 *db;     char *sql;     rc = sqlite3_open("test.db", &db);     if(rc) {         print_error(db, "Can't open database");         exit(1);     }     /* Create aggregate table, add records. */     setup(db);     /* Register aggregate. */     fprintf(stdout, "Registering aggregate sum_int()\n");     log_sql(db, 1);     sqlite3_create_function( db, "sum_int", 1, SQLITE_UTF8, db,                              NULL, step, finalize);     /* Test it. */     fprintf(stdout, "\nRunning query: \n");     sql = "select sum_int(id) from aggregate";     print_sql_result(db, sql);     /* Done. */     sqlite3_close(db);     return 0;    } ``` ### 步进函数 The step() function is shown in Listing 7-10\. Listing 7-10\. The sum_int() Step Function ``` void step(sqlite3_context* ctx, int ncols, sqlite3_value** values) {     sum* s;     int x;     s = (sum*)sqlite3_aggregate_context(ctx, sizeof(sum));     if(sqlite3_aggregate_count(ctx) == 1) {         s->x = 0;     }     x = sqlite3_value_int(values[0]);;     s->x += x;     fprintf(stdout, "step()     : value=%i, total=%i\n", x, s->x); } The value sum is a struct that is specific to this example and is defined as follows: typedef struct {     int x; } sum; ``` ### 聚合的Context ### Finalize函数 Listing 7-11\. The sum_int() Finalize Function ``` void finalize(sqlite3_context* ctx) {     sum* s;     s = (sum*)sqlite3_aggregate_context(ctx, sizeof(sum));     sqlite3_result_int(ctx, s->x);     fprintf(stdout, "finalize() : total=%i\n\n", s->x); } ``` ## 排序法 ## 排序法定义 ### 排序法如何工作 ### 标准的排序法类型 ## 一个简单的例子 ### Compare函数 Listing 7-12\. The Political Collation Function ``` int political_collation( void* data, int l1, const void* s1,                                      int l2, const void* s2 ) {     int value, opinion; struct tm* t; time_t rt;     /* Get the unpolitical value */        value = strcmp(s1,s2);     /* Get the date and time */     time(&rt);     t = localtime(&rt);     /* Form an opinion */        switch(t->tm_wday) {         case 0: /* Monday yes    */             opinion = value;             break;         case 1: /* Tueday no     */             opinion = -value;             break;         case 2: /* Wednesday bigger is better */             opinion = l1 >= l2 ? -1:1;             break;         case 3: /* Thursday strongly no   */             opinion = -100;             break;         case 4: /* Friday strongly yes    */             opinion = 100;             break;         case 5: /* Saturday golf, everything's the same */             opinion = 0;             break;         default: /* Sunday - Meet the Press, opinion changes                     by the hour */             opinion = 2*(int)sin(t->tm_hour*180);     }     /* Now change it on a whim */     opinion = rand()-(RAND_MAX/2) > 0 ? -1:1;     return opinion; } ``` ### 测试程序 Listing 7-13\. The Political Collation Test Program ``` int main(int argc, char **argv) {     int rc;     sqlite3 *db;     char *sql;     /* For forming more consistent political opinions. */     srand((unsigned)time(NULL));     rc = sqlite3_open("test.db", &db);     if(rc) {         print_error(db, "Can't open database");         exit(1);     }     /* Create issues table, add records. */     setup(db);     /* Register collating sequence. */     fprintf(stdout, "1. Register political collating sequence\n\n");     sqlite3_create_collation( db, "POLITICAL",                               SQLITE_UTF8, db,                               political_collation );     /* Turn SQL logging on. */     log_sql(db, 1);     /* Test default collation. */     fprintf(stdout, "2. Select records using default collation.\n");     sql = "select * from issues order by issue";     print_sql_result(db, sql);     /* Test Oracle collation. */     fprintf(stdout, "\nSelect records using political collation. \n");     sql = "select * from issues order by issue collate POLITICAL";     print_sql_result(db, sql);     /* Done. */     sqlite3_close(db);     return 0;    } ``` ## 按需排序(Collation on Demand) Listing 7-14\. Collation Registration Function ``` void crf( void* data, sqlite3* db,     int eTextRep, const char* cname) {     if(strcmp(collation_name, "POLITICAL") == 0) {         /* Political collation has not been registered and is now needed */         sqlite3_create_collation( db, "POLITICAL",             SQLITE_UTF8, db,             political_collation );     } else {         /* Punt: Use some default comparison function this collation. */         sqlite3_create_collation( db, collation_name,             SQLITE_UTF8, db,             default_collation );     } } ``` ## 一个实际的应用程序 ### 比较函数 Listing 7-15\. Oracle Date Collation Function ``` int oracle_date_collation( void* data,                            int len1, const void* arg1,                            int len2, const void* arg2 ) {     int len;     date d1;     date d2;     char zDate1[25];     char zDate2[25];     /* Copy date 1 */     if(len1 > 24) {         len = 24;     } else {         len = len1;     }     strncpy(&zDate1[0], arg1, len);     zDate1[len] = '\0';     /* Copy date 2 */     if(len2 > 24) {         len = 24;     } else {         len = len2;     }     strncpy(&zDate2[0], arg2, len);     zDate2[len] = '\0';     /* Convert dates to date struct */     oracle_date_str_to_struct(zDate1, &d1);     oracle_date_str_to_struct(zDate2, &d2);     fprintf(stdout, "collate_fn() : date1=%s, date2=%s\n", zDate1, zDate2);     /* Compare structs */     if(d1.year < d2.year)     {         return -1;     }     else if(d1.year > d2.year)     {         return 1;     }     /* If this far, years are equal. */     if(d1.month < d2.month)     {         return -1;     }     else if(d1.month > d2.month)     {         return 1;     }     /* If this far, months are equal. */     if(d1.day < d2.day)     {         return -1;     }     else if(d1.day > d2.day)     {         return 1;     }     /* If this far, dates are equal. */     return 0; } ``` ### 日期解析 Listing 7-16\. The Oracle Date Parsing Function ``` int oracle_date_str_to_struct(const char* value, date* d) {     const char* date, *tmp;     char *start, *end, zDay[3], zMonth[4], zYear[3];     date = get_date(value);     if(date == NULL) {         fprintf(stderr, "Invalid date\n");         return -1;     }     /* Find first '-' */     start = strchr(date,'-');     /* Find last '-' */     end   = strchr(start+1,'-');     /* Extract day part, convert to int*/     strncpy(zDay, date,2);     zDay[2] = '\0';     d->day = atoi(zDay);     /* Extract month part, convert to int*/     strncpy(zMonth, start+1,3);     zMonth[3] = 0;     tmp = uppercase(zMonth);     d->month = month_num(tmp);     free((void*)tmp);     /* Extract year part, convert to int*/     strncpy(zYear, end+1,2);     zYear[2] = '\0';     d->year = atoi(zYear);     free((void*)date);     return 0; } ``` Listing 7-17\. The get_date() Function ``` #define ORACLE_DATE_REGEX "[0-9]{1,2}-[a-zA-Z]{3,3}-[0-9]{2,2}"; const char* get_date(const char* value) {     pcre *re;     const char *error, *pattern;     int erroffset;     int ovector[3];     int value_length;     int rc, substring_length;     char* result, *substring_start;     pattern = ORACLE_DATE_REGEX;     re = pcre_compile(         pattern,              /* the pattern */         0,                    /* default options */         &error,               /* for error message */         &erroffset,           /* for error offset */         NULL);                /* use default character tables */     /* Compilation failed */     if (re == NULL) {         return NULL;     }     value_length = (int)strlen(value);     rc = pcre_exec(         re,           /* the compiled pattern */         NULL,         /* no extra data - we didn't study the pattern */         value,        /* the value string */         value_length, /* the length of the value */         0,            /* start at offset 0 in the value */         0,            /* default options */         ovector,      /* output vector for substring information */         3);           /* number of elements in the output vector */     if (rc < 0) {         /* Match error */         return NULL;     }     /* Match succeded */     substring_start = (char*)value + ovector[0];     substring_length = ovector[1] - ovector[0];     //printf("%.*s\n", substring_length, substring_start);     result = malloc(substring_length+1);     strncpy(result, substring_start, substring_length);     result[substring_length] = '\0';     return result; } ``` ### 测试程序 All three of the above functions work together to collate Oracle dates in chronological order. Our example program is shown in Listing 7-18\. Listing 7-18\. The Oracle Collation Test Program ``` int main(int argc, char **argv) {     int rc;     sqlite3 *db;     char *sql;     rc = sqlite3_open("test.db", &db);     if(rc) {         print_error(db, "Can't open database");         exit(1);     }     /* Install oracle related date functions. */     install_date_functions(db);     /* Register collating sequence. */     fprintf(stdout, "Registering collation sequence oracle_date\n");     sqlite3_create_collation( db, "oracle_date",                               SQLITE_UTF8, db,                               oracle_date_collation );     /* Create dates table, add records. */     setup(db);     /* Install date */     install_date_triggers(db);     /* Turn SQL logging on. */     log_sql(db, 1);     /* Test default collation. */     fprintf(stdout, "Select records. Use default collation.\n");     sql = "select * from dates order by date";     print_sql_result(db, sql);     /* Test Oracle collation. */     fprintf(stdout, "\nSelect records. Use Oracle data collation. \n");     sql = "select * from dates order by date collate oracle_date";     print_sql_result(db, sql);     /* Get ISO Date from Oracle date. */     fprintf(stdout, "\nConvert Oracle date to ISO format.\n");     sql = "select iso_from_oradate('01-APR-05') as 'ISO Date'";     print_sql_result(db, sql);     /* Validate Oracle date. */     fprintf(stdout, "\nValidate Oracle format. Should fail.\n");     sql = "select validate_oradate('01-NOT-2005')";     execute(db, sql);     /* Test Oracle date triggers. */     fprintf(stdout, "\nTest Oracle insert trigger -- should fail.\n");     sql = "insert into dates (date) values ('01-NOT-2005')";     execute(db, sql);     fprintf(stdout, "\nTest Oracle update trigger -- should succeed.\n");     sql = "update dates set date='01-JAN-2005'";     execute(db, sql);     print_sql_result(db, "select * from dates");     /* Done. */     sqlite3_close(db);     return 0;    } ``` ### 运行结果 略。