OpenSIPS 3.1 开发手册(五)--SQL数据库API

https://www.opensips.org/Documentation/Development-Manual

目录

12.  SQL 数据库 API

13.  NoSQL API

14.  Event Interface API


12.  SQL 数据库 API

        OpenSIPS封装了一套数据库API,模块开发者可以用它操作常见的SQL查询。其优势在于:

  • 编写后台无关代码,因为DB API对实现后端相关的模块实际代码解耦
  • 让无SQL能力的后端提供类SQL的特性(比如说:db_flatstore 模块直接操作纯文本文件,但开发者可以使用SQL语句插入数据)


        db/db.h 中声明了大部分数据库相关的函数。启动时,开发者仅持有它需要连接的数据库URL。通过调用db_bind_mod ,OpenSIPS DB API 将尝试自动定位实际支持特定后端技术的DB模块,并返回所有后端操作所需要的函数。

/**                                        
 * \brief Bind database module functions                            
 *                                                                      
 * This function is special, it's only purpose is to call find_export function in
 * the core and find the addresses of all other database related functions. The
 * db_func_t callback given as parameter is updated with the found addresses.
 *                                                            
 * This function must be called before any other database API call!
 *                                                              
 * The database URL is of the form "mysql://username:password@host:port/database" or
 * "mysql" (database module name).
 * In the case of a database connection URL, this function looks only at the first
 * token (the database protocol). In the example above that would be "mysql":
 * \see db_func_t                                                            
 * \param mod database connection URL or a database module name
 * \param dbf database module callbacks to be further used                  
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */                                      
int db_bind_mod(const str* mod, db_func_t* dbf);

typedef struct db_func {
      unsigned int           cap;           /* Capability vector of the database transport */
      db_use_table_f         use_table;     /* Specify table name */
      db_init_f              init;          /* Initialize database connection */
      db_close_f             close;         /* Close database connection */
      db_query_f             query;         /* query a table */
      db_fetch_result_f      fetch_result;  /* fetch result */
      db_raw_query_f         raw_query;     /* Raw query - SQL */
      db_free_result_f       free_result;   /* Free a query result */
      db_insert_f            insert;        /* Insert into table */
      db_delete_f            delete;        /* Delete from table */
      db_update_f            update;        /* Update table */
      db_replace_f           replace;       /* Replace row in a table */
      db_last_inserted_id_f  last_inserted_id;  /* Retrieve the last inserted ID
                                                    in a table */
      db_insert_update_f insert_update;     /* Insert into table, update on duplicate key */
} db_func_t;

/* Example of usage below */
db_func_t sql_functions;
db_url = str_init("mysql://root:vlad@localhost/opensips");

if (db_bind_mod(db_url, &sql_functions) < 0){
      /* most likely the db_mysql modules was not loaded, or it was loaded after our module */
      LM_ERR("Unable to bind to a database driver\n");
      return -1;
}

        与模块绑定成功之后,开发者必须保证从后端脚本提取的URL也具备后续处理的能力(比如说,操作纯文本文件时,没有提供db_last_inserted_id_f 函数,因此,如果C代码中调用它,模块就会crash)。这可以用DB_CAPABILITY 宏完成。

/**
 * Returns true if all the capabilities in cpv are supported by module
 * represented by dbf, false otherwise
 */
#define DB_CAPABILITY(dbf, cpv) (((dbf).cap & (cpv)) == (cpv))

/**
 * Represents the capabilities that a database driver supports.
 */
typedef enum db_cap {
        DB_CAP_QUERY =     1 << 0,  /**< driver can perform queries                                     */
        DB_CAP_RAW_QUERY = 1 << 1,  /**< driver can perform raw queries                                 */
        DB_CAP_INSERT =    1 << 2,  /**< driver can insert data                                         */
        DB_CAP_DELETE =    1 << 3,  /**< driver can delete data                                         */
        DB_CAP_UPDATE =    1 << 4,  /**< driver can update data                                         */
        DB_CAP_REPLACE =   1 << 5,  /**< driver can replace (also known as INSERT OR UPDATE) data       */
        DB_CAP_FETCH   =   1 << 6,  /**< driver supports fetch result queries                           */
        DB_CAP_LAST_INSERTED_ID = 1 << 7,  /**< driver can return the ID of the last insert operation   */
        DB_CAP_INSERT_UPDATE = 1 << 8, /**< driver can insert data into database and update on duplicate */
        DB_CAP_MULTIPLE_INSERT = 1 << 9 /**< driver can insert multiple rows at once */
} db_cap_t;


/**
 * All database capabilities except raw_query, replace, insert_update and
 * last_inserted_id which should be checked separately when needed
 */
#define DB_CAP_ALL (DB_CAP_QUERY | DB_CAP_INSERT | DB_CAP_DELETE | DB_CAP_UPDATE)

/* Example of usage below */
if (!DB_CAPABILITY(sql_functions, DB_CAP_ALL)) {
      LM_CRIT("Database modules does not "
            "provide all functions needed by our module\n");
      return -1;
}


        现在,我们已经绑定所需的后端模块,也确信它支持所需要的能力,那么,我们可以继续连接后台,即调用绑定函数中的init函数:

/**
 * \brief Initialize database connection and obtain the connection handle.
 *
 * This function initialize the database API and open a new database
 * connection. This function must be called after db_bind_mod but before any
 * other database API function is called.
 *
 * The function takes one parameter, the parameter must contain the database
 * connection URL. The URL is of the form
 * mysql://username:password\@host:port/database where:
 *
 * username: Username to use when logging into database (optional).
 * password: password if it was set (optional)
 * host:     Hosname or IP address of the host where database server lives (mandatory)
 * port:     Port number of the server if the port differs from default value (optional)
 * database: If the database server supports multiple databases, you must specify the
 * name of the database (optional).
 * \see bind_dbmod
 * \param _sqlurl database connection URL
 * \return returns a pointer to the db_con_t representing the connection if it was
 * successful, otherwise 0 is returned
 */
typedef db_con_t* (*db_init_f) (const str* _sqlurl);

/* Example of usage below */
static db_con_t* db_connection;

if ((db_connection = sql_functions.init(db_url)) == NULL) {
      LM_ERR("Failed to connect to the database \n");
      return -1;
}
扫描二维码关注公众号,回复: 11576874 查看本文章

   对于大部分后端技术来说(比如说MySQL、Postgres) ,不支持多进程共享连接。基于这样的事实,开发者必须保证为每个进程创建独立的数据库连接,在模块开发上下文中,需要在child_init 函数中打开连接。


        init()函数的输出将是后续DB交互的handler。当连接不再需要时,需要调用close 方法关闭:

/**
 * \brief Close a database connection and free all memory used.
 *
 * The function closes previously open connection and frees all previously
 * allocated memory. The function db_close must be the very last function called.
 * \param _h db_con_t structure representing the database connection
 */
typedef void (*db_close_f) (db_con_t* _h);


        在向后端执行查询之前,为了确保你的代码不会运行在旧的数据库结构之上,通常的做法是对表做版本控制。db/db.h里提供了db_check_table_version用于此目的,它检查缺省OpenSIPS 数据库里的version 这张表:
 

/*              
Parameters :
      dbf - the functions to be used for running the version query
      dbh - the connection to run the version query
      table - str containing the table name we want to check for version
      version - the version we expect to find
Returns :
      0 means table version was successfully validated, negative in case of error ( internal error or older version found )
 */
int db_check_table_version(db_func_t* dbf, db_con_t* dbh, const str* table, const unsigned int version);


在通过API执行查询之前,需要选择执行查询的表:

/**
 * \brief Specify table name that will be used for subsequent operations.
 *
 * The function db_use_table takes a table name and stores it db_con_t structure.
 * All subsequent operations (insert, delete, update, query) are performed on
 * that table.
 * \param _h database connection handle
 * \param _t table name
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
typedef int (*db_use_table_f)(db_con_t* _h, const str * _t);

    所有查询之前必须调用use_table 函数。OpenSIPS 内部维护连接池,在多模块请求连接同一数据库时,连接会在模块间共享。因此,在一个进程的上下文中,同一连接可能用于不同模块,永远不要假定连接是某个模块独占的。


要执行一次 SELECT 查询,你应当调用 query 函数。其原型是:

/**
 * \brief Query table for specified rows.
 *
 * This function implements the SELECT SQL directive.
 * If _k and _v parameters are NULL and _n is zero, you will get the whole table.
 *
 * if _c is NULL and _nc is zero, you will get all table columns in the result.
 * _r will point to a dynamically allocated structure, it is neccessary to call
 * db_free_result function once you are finished with the result.
 *
 * If _op is 0, equal (=) will be used for all key-value pairs comparisons.
 *
 * Strings in the result are not duplicated, they will be discarded if you call
 * db_free_result, make a copy yourself if you need to keep it after db_free_result.
 *
 * You must call db_free_result before you can call db_query again!
 * \see db_free_result
 *
 * \param _h database connection handle
 * \param _k array of column names that will be compared and their values must match
 * \param _op array of operators to be used with key-value pairs
 * \param _v array of values, columns specified in _k parameter must match these values
 * \param _c array of column names that you are interested in
 * \param _n number of key-value pairs to match in _k and _v parameters
 * \param _nc number of columns in _c parameter
 * \param _o order by statement for query
 * \param _r address of variable where pointer to the result will be stored
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
typedef int (*db_query_f) (const db_con_t* _h, const db_key_t* _k, const db_op_t* _op,
                                const db_val_t* _v, const db_key_t* _c, const int _n, const int _nc,
                                const db_key_t _o, db_res_t** _r);


查询执行成功后,开发者必须处理输出的db_res_t才能使用查询结果数据集。以下是用于解释查询结果数据集的结构:

/**
 * This type represents a result returned by db_query function (see below). The
 * result can consist of zero or more rows (see db_row_t description).
 *
 * Note: A variable of type db_res_t returned by db_query function uses dynamicaly
 * allocated memory, don't forget to call db_free_result if you don't need the
 * variable anymore. You will encounter memory leaks if you fail to do this!
 *
 * In addition to zero or more rows, each db_res_t object contains also an array
 * of db_key_t objects. The objects represent keys (names of columns). *
 */
typedef struct db_res {
        struct {
                db_key_t* names;   /**< Column names                    */
                db_type_t* types;  /**< Column types                    */
                int n;             /**< Number of columns               */
        } col;
        struct db_row* rows;   /**< Rows                            */
        int n;                 /**< Number of rows in current fetch */
        int res_rows;          /**< Number of total rows in query   */
        int last_row;          /**< Last row                        */
} db_res_t;

/**
 * Structure holding the result of a query table function.
 * It represents one row in a database table. In other words, the row is an
 * array of db_val_t variables, where each db_val_t variable represents exactly
 * one cell in the table.
 */
typedef struct db_row {
        db_val_t* values;  /**< Columns in the row */
        int n;             /**< Number of columns in the row */
} db_row_t;

/**
 * This structure represents a value in the database. Several datatypes are
 * recognized and converted by the database API. These datatypes are automaticaly
 * recognized, converted from internal database representation and stored in the
 * variable of corresponding type.
 *
 * Module that want to use this values needs to copy them to another memory
 * location, because after the call to free_result there are not more available.
 *
 * If the structure holds a pointer to a string value that needs to be freed
 * because the module allocated new memory for it then the free flag must
 * be set to a non-zero value. A free flag of zero means that the string
 * data must be freed internally by the database driver.
 */
typedef struct {
        db_type_t type; /**< Type of the value                              */
        int nul;                /**< Means that the column in database has no value */
        int free;               /**< Means that the value should be freed */
        /** Column value structure that holds the actual data in a union.  */
        union {
                int           int_val;    /**< integer value              */
                long long     bigint_val; /**< big integer value          */
                double        double_val; /**< double value               */
                time_t        time_val;   /**< unix time_t value          */
                const char*   string_val; /**< zero terminated string     */
                str           str_val;    /**< str type string value      */
                str           blob_val;   /**< binary object data         */
                unsigned int  bitmap_val; /**< Bitmap data type           */
        } val;
} db_val_t;


为了帮助编码和阅读代码,定义了许多宏:

/* Macros below work on result sets ( db_res_t )
/** Return the column names */
#define RES_NAMES(re) ((re)->col.names)
/** Return the column types */
#define RES_TYPES(re) ((re)->col.types)
/** Return the number of columns */
#define RES_COL_N(re) ((re)->col.n)
/** Return the result rows */
#define RES_ROWS(re)  ((re)->rows)
/** Return the number of current result rows */
#define RES_ROW_N(re) ((re)->n)
/** Return the last row of the result */
#define RES_LAST_ROW(re)  ((re)->last_row)
/** Return the number of total result rows */
#define RES_NUM_ROWS(re) ((re)->res_rows)

/* Macros below work on rows */
/** Return the columns in the row */
#define ROW_VALUES(rw) ((rw)->values)
/** Return the number of colums */
#define ROW_N(rw)      ((rw)->n)

/* Macros below work on values */
/**
 * Use this macro if you need to set/get the type of the value.
 */
#define VAL_TYPE(dv)   ((dv)->type)
/**
 * Use this macro if you need to set/get the null flag. A non-zero flag means that
 * the corresponding cell in the database contains no data (a NULL value in MySQL
 * terminology).
 */
#define VAL_NULL(dv)   ((dv)->nul)
/**
 * Use this macro if you need to access the integer value in the db_val_t structure.
 */
#define VAL_INT(dv)    ((dv)->val.int_val)
/**
 * Use this macro if you need to access the str structure in the db_val_t structure.
 */
#define VAL_STR(dv)    ((dv)->val.str_val)


下面是完整实例,从查询开始到查询结束:

/* we will work on 'mytable' table with just two columns, keyname and value.
The select query we will run is 'select value from mytable where keyname='abc';'
*/
db_key_t key;
db_val_t val;
db_key_t col;
db_res_t* db_res = NULL;
db_row_t * rows;
db_val_t * values;

#define KEY_COL "keyname"
#define VALUE_COL "value"
str key_column = str_init(KEY_COL);
str value_column = str_init(VALUE_COL);
str db_table = str_init("mytable");

val.type = DB_STR;
val.nul = 0;
val.val.str_val.s = "abc";
val.val.str_val.len = 3;

key = &key_column;
col = &value_column;

if (sql_functions.use_table(db_handle, &db_table) < 0) {
      LM_ERR("sql use_table failed\n");
      return -1;
}

if(sql_functions.query(db_handle, &key, NULL, &val, &col, 1, 1, NULL, &db_res) < 0) {
       LM_ERR("failed to query database\n");
       return -1;
}

nr_rows = RES_ROW_N(db_res);
rows = RES_ROWS(db_res);


if (nr_rows <= 0) {
      LM_DBG("no rows found\n");
      sql_functions.free_result(db_handle, db_res);
      return -1;
}

for (i=0;i<nr_rows;i++) {
      values = ROW_VALUES(rows + i);
      if (VAL_NULL(values)) {
            LM_WARN("Column value should not be null - skipping \n");
            continue;
      }

      LM_DBG("We have feteched %s\n",VAL_STRING(values));
      /* do further rows processing here */
}

sql_functions.free_result(db_handle, db_res);
return 0;


        从上面例子我们可以看到,调用query 函数查询成功之后,必须调用 free_result API 函数释放返回的数据集。

/**
 * \brief Free a result allocated by db_query.
 *
 * This function frees all memory allocated previously in db_query. Its
 * neccessary to call this function on a db_res_t structure if you don't need the
 * structure anymore. You must call this function before you call db_query again!
 * \param _h database connection handle
 * \param _r pointer to db_res_t structure to destroy
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
typedef int (*db_free_result_f) (db_con_t* _h, db_res_t* _r);

       


有些时候,特别是查询一个大数据表的时候,一次获取所有记录行可能不现实,因为这可能导致OpenSIPS私有内存开销太大。这时在,强烈推荐使用 fetch_result API。


/**                                                              
 * \brief Fetch a number of rows from a result.                                
 *
 * The function fetches a number of rows from a database result. If the number
 * of wanted rows is zero, the function returns anything with a result of zero.
 * \param _h structure representing database connection
 * \param _r structure for the result
 * \param _n the number of rows that should be fetched            
 * \return returns 0 if everything is OK, otherwise returns value < 0        
 */
typedef int (*db_fetch_result_f) (const db_con_t* _h, db_res_t** _r, const int _n);


以下代码是使用fetch_result 的完整示例:

       /* check if our used DB driver supports fetching a limited number of rows */
        if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
                /* run our query as usual, but DO NOT provide a result set pointer ( last parameter 0 ) */
                if ( dr_dbf->query( db_hdl, 0, 0, 0, columns, 0, db_cols, 0, 0 ) < 0) {
                        LM_ERR("DB query failed\n");
                        goto error;
                }
                /* estimate how many rows we can fit into our current PKG memory */
                no_rows = estimate_available_rows( 4+32+15+4+32+4+128+4+32+4, db_cols);
                if (no_rows==0) no_rows = 10;
                /* try to fetch our rows */
                if(dr_dbf->fetch_result(db_hdl, &res, no_rows )<0) {
                        LM_ERR("Error fetching rows\n");
                        goto error;
                }
        } else {
                /* no fetching rows support - fallback to full rows loading */
                if ( dr_dbf->query(db_hdl,0,0,0,columns,0,db_cols,0,&res) < 0) {
                        LM_ERR("DB query failed\n");
                        goto error;
                }
        }

        do {
                for(i=0; i < RES_ROW_N(res); i++) {
                        row = RES_ROWS(res) + i;
                        /* start processing our loaded rows */
                }

                if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
                        /* any more rows to fetch ? */
                        if(dr_dbf->fetch_result(db_hdl, &res, no_rows)<0) {
                                LM_ERR( "fetching rows (1)\n");
                                goto error;
                        }
                        /* success in fetching more rows - continue the loop */
                } else {
                        /* we were not supporting fetching rows in the first place, processed everything */
                        break;
                }
        } while(RES_ROW_N(res)>0);

        dr_dbf->free_result(db_hdl, res);


        调用insert API函数可以插入数据记录:

/**
 * \brief Insert a row into the specified table.
 *
 * This function implements INSERT SQL directive, you can insert one or more
 * rows in a table using this function.
 * \param _h database connection handle
 * \param _k array of keys (column names)
 * \param _v array of values for keys specified in _k parameter
 * \param _n number of keys-value pairs int _k and _v parameters
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
typedef int (*db_insert_f) (const db_con_t* _h, const db_key_t* _k,
                                const db_val_t* _v, const int _n);


        删除数据则调用delete API :

/**
 * \brief Delete a row from the specified table.
 *
 * This function implements DELETE SQL directive, it is possible to delete one or
 * more rows from a table.
 * If _k is NULL and _v is NULL and _n is zero, all rows are deleted, the
 * resulting table will be empty.
 * If _o is NULL, the equal operator "=" will be used for the comparison.
 *
 * \param _h database connection handle
 * \param _k array of keys (column names) that will be matched
 * \param _o array of operators to be used with key-value pairs
 * \param _v array of values that the row must match to be deleted
 * \param _n number of keys-value parameters in _k and _v parameters
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
typedef int (*db_delete_f) (const db_con_t* _h, const db_key_t* _k, const db_op_t* _o,
                                const db_val_t* _v, const int _n);


        更新数据调用update API :

/**
 * \brief Update some rows in the specified table.
 *
 * The function implements UPDATE SQL directive. It is possible to modify one
 * or more rows in a table using this function.
 * \param _h database connection handle
 * \param _k array of keys (column names) that will be matched
 * \param _o array of operators to be used with key-value pairs
 * \param _v array of values that the row must match to be modified
 * \param _uk array of keys (column names) that will be modified
 * \param _uv new values for keys specified in _k parameter
 * \param _n number of key-value pairs in _k and _v parameters
 * \param _un number of key-value pairs in _uk and _uv parameters
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
typedef int (*db_update_f) (const db_con_t* _h, const db_key_t* _k, const db_op_t* _o,
                                const db_val_t* _v, const db_key_t* _uk, const db_val_t* _uv,
                                const int _n, const int _un);


        替换数据库中的行可以调用replace API:

/**
 * \brief Insert a row and replace if one already exists.
 *
 * The function implements the REPLACE SQL directive. It is possible to insert
 * a row and replace if one already exists. The old row will be deleted before
 * the insertion of the new data.
 * \param _h structure representing database connection
 * \param _k key names
 * \param _v values of the keys
 * \param _n number of key=value pairs
 * \return returns 0 if everything is OK, otherwise returns value < 0
*/
typedef int (*db_replace_f) (const db_con_t* handle, const db_key_t* keys,
                                const db_val_t* vals, const int n);


        有时候,为了优化数据库操作,了解自增主键值对插入/更新记录会很有帮助。调用last_inserted_id API可以获取:

/**
 * \brief Retrieve the last inserted ID in a table.
 *
 * The function returns the value generated for an AUTO_INCREMENT column by the
 * previous INSERT or UPDATE  statement. Use this function after you have
 * performed an INSERT statement into a table that contains an AUTO_INCREMENT
 * field.
 * \param _h structure representing database connection
 * \return returns the ID as integer or returns 0 if the previous statement
 * does not use an AUTO_INCREMENT value.
 */
typedef int (*db_last_inserted_id_f) (const db_con_t* _h);


        此外,如果我们想在插入冲突时变为变更操作,可以调用 insert_update API:

/**
 * \brief Insert a row into specified table, update on duplicate key.
 *
 * The function implements the INSERT ON DUPLICATE KEY UPDATE SQL directive.
 * It is possible to insert a row and update if one already exists.
 * The old row will not deleted before the insertion of the new data.
 * \param _h structure representing database connection
 * \param _k key names
 * \param _v values of the keys
 * \param _n number of key=value pairs
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
typedef int (*db_insert_update_f) (const db_con_t* _h, const db_key_t* _k,
                                const db_val_t* _v, const int _n);


        执行其它数据库查询,可以调用raw_query API:

/**
 * \brief Raw SQL query.
 *
 * This function can be used to do database specific queries. Please
 * use this function only if needed, as this creates portability issues
 * for the different databases. Also keep in mind that you need to
 * escape all external data sources that you use. You could use the
 * escape_common and unescape_common functions in the core for this task.
 * \see escape_common
 * \see unescape_common
 * \param _h structure representing database connection
 * \param _s the SQL query
 * \param _r structure for the result
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */
typedef int (*db_raw_query_f) (const db_con_t* _h, const str* _s, db_res_t** _r);

13.  NoSQL API

14.  Event Interface API

猜你喜欢

转载自blog.csdn.net/yetyongjin/article/details/106619137
3.1