SQLite Custom Data Types: Define and Use User-Defined Types

SQLite supports extending its built-in data types through extensions, allowing developers to define new data types tailored to specific requirements, such as dates, geographic locations, or enums. The core of custom data types lies in writing extension modules in C and registering them within SQLite as operable types.

Basic Steps for Defining Custom Data Types

  1. Write the Extension Module
    First, create a C extension module containing functions to define the custom type. For example, define a Date type with a structure comprising year, month, and day fields.
    // date.c
    #include <sqlite3.h>
    #include <stdio.h>
    
    typedef struct {
        int year;
        int month;
        int day;
    } Date;
    
    static int date_compare(const void *a, const void *b) {
        const Date *d1 = a;
        const Date *d2 = b;
        if (d1->year < d2->year) return -1;
        if (d1->year > d2->year) return 1;
        if (d1->month < d2->month) return -1;
        if (d1->month > d2->month) return 1;
        return d1->day - d2->day;
    }
    
    static int date_open(sqlite3 *db, const char *name, int flags, void **pppp) {
        *pppp = (void *)date;
        return SQLITE_OK;
    }
    
    static int date_close(sqlite3 *db, void *pppp) {
        return SQLITE_OK;
    }
    
    static int date_vdbopen(sqlite3 *db, const char *name, int flags, void **pppp) {
        *pppp = (void *)date;
        return SQLITE_OK;
    }
    
    static int date_vdbclose(sqlite3 *db, void *pppp) {
        return SQLITE_OK;
    }
    
  2. Register the Type
    Within the extension module, register the custom type into SQLite's type system. For example, register the Date type using the sqlite3_create_function function.
    sqlite3_create_function(db, "DATE", 1, SQLITE_UTF8, (sqlite3_api)date_open, (sqlite3_api)date_close, (sqlite3_api)date_vdbopen, (sqlite3_api)date_vdbclose);
    
  3. Use Custom Types in the Database
    When creating the database, utilize the custom type, for example:
    sqlite3_create_db("mydb", 10, "CREATE TABLE users (id INTEGER PRIMARY KEY, date DATE);");
    

Use Cases for Custom Data Types

Custom data types are suitable for scenarios requiring complex logic or interaction with external systems. Examples include:

  • Date Types: Create date fields using the DATE function, supporting sorting, comparison, and formatting.
  • Geographic Location Types: Define structures containing latitude and longitude to store geographic coordinates.
  • Enum Types: Use the ENUM function to define a limited range of values, such as STATUS ("active", "inactive").

Precautions

  • Registration Order: Ensure that custom types are positioned correctly in SQLite's type registration table; otherwise, the type may not be recognized.
  • Type Restrictions: Custom types cannot be directly used in query conditions (e.g., WHERE clauses) and must be converted to standard types via functions.
  • Performance Considerations: Custom types may increase memory overhead; balance performance requirements with functional needs.

Practical Application Example

Developers can implement more flexible data models using custom types. For instance, create a Location type to store latitude and longitude:

// location.c
typedef struct {
    double lat;
    double lon;
} Location;

static int location_compare(const void *a, const void *b) {
    const Location *l1 = a;
    const Location *l2 = b;
    if (l1->lat < l2->lat) return -1;
    if (l1->lat > l2->lat) return 1;
    return l1->lon - l2->lon;
}

sqlite3_create_function(db, "LOCATION", 1, SQLITE_UTF8, (sqlite3_api)location_open, (sqlite3_api)location_close, (sqlite3_api)location_vdbopen, (sqlite3_api)location_vdbclose);

Through the steps outlined above, developers can flexibly extend SQLite's data type capabilities to meet complex data storage requirements.