第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);
}

一个实际的应用程序

聚合

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;   
}

运行结果

略。