sql-api-private.h revision 9f0fc74e3387d3e496fb0c8f77633e27e48cc1ff
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen#ifndef SQL_API_PRIVATE_H
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen#define SQL_API_PRIVATE_H
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "sql-api.h"
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen#include "module-context.h"
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenenum sql_db_state {
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* not connected to database */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen SQL_DB_STATE_DISCONNECTED,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* waiting for connection attempt to succeed or fail */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen SQL_DB_STATE_CONNECTING,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* connected, allowing more queries */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen SQL_DB_STATE_IDLE,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* connected, no more queries allowed */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen SQL_DB_STATE_BUSY
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen};
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen/* Minimum delay between reconnecting to same server */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#define SQL_CONNECT_MIN_DELAY 1
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen/* Maximum time to avoiding reconnecting to same server */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#define SQL_CONNECT_MAX_DELAY (60*30)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen/* If no servers are connected but a query is requested, try reconnecting to
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen next server which has been disconnected longer than this (with a single
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen server setup this is really the "max delay" and the SQL_CONNECT_MAX_DELAY
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen is never used). */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#define SQL_CONNECT_RESET_DELAY 15
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen/* Abort connect() if it can't connect within this time. */
44e5c39db5002ab15db142dda6c42e0422a17437Timo Sirainen#define SQL_CONNECT_TIMEOUT_SECS 5
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen/* Abort queries after this many seconds */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#define SQL_QUERY_TIMEOUT_SECS 60
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen/* Default max. number of connections to create per host */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#define SQL_DEFAULT_CONNECTION_LIMIT 5
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#define SQL_DB_IS_READY(db) \
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen ((db)->state == SQL_DB_STATE_IDLE)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#define SQL_ERRSTR_NOT_CONNECTED "Not connected to database"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainenstruct sql_db_module_register {
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen unsigned int id;
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen};
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainenunion sql_db_module_context {
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen struct sql_db_module_register *reg;
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen};
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainenextern struct sql_db_module_register sql_db_module_register;
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstruct sql_transaction_query {
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen struct sql_transaction_query *next;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen struct sql_transaction_context *trans;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen const char *query;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen unsigned int *affected_rows;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen};
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainenstruct sql_db_vfuncs {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct sql_db *(*init)(const char *connect_string);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen void (*deinit)(struct sql_db *db);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
a0b89f3b1df99b3a32f44623f13ad1893118825bTimo Sirainen int (*connect)(struct sql_db *db);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen void (*disconnect)(struct sql_db *db);
13a8c553f293349248b161ff851743498916e26eTimo Sirainen const char *(*escape_string)(struct sql_db *db, const char *string);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen void (*exec)(struct sql_db *db, const char *query);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen void (*query)(struct sql_db *db, const char *query,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen sql_query_callback_t *callback, void *context);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen struct sql_result *(*query_s)(struct sql_db *db, const char *query);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen struct sql_transaction_context *(*transaction_begin)(struct sql_db *db);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen void (*transaction_commit)(struct sql_transaction_context *ctx,
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen sql_commit_callback_t *callback,
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen void *context);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen int (*transaction_commit_s)(struct sql_transaction_context *ctx,
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen const char **error_r);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen void (*transaction_rollback)(struct sql_transaction_context *ctx);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen void (*update)(struct sql_transaction_context *ctx, const char *query,
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen unsigned int *affected_rows);
b87761f9bbef949f31dae297e619ac3f5e9c2b2eTimo Sirainen const char *(*escape_blob)(struct sql_db *db,
b87761f9bbef949f31dae297e619ac3f5e9c2b2eTimo Sirainen const unsigned char *data, size_t size);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen struct sql_prepared_statement *
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen (*prepared_statement_init)(struct sql_db *db,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen const char *query_template);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen void (*prepared_statement_deinit)(struct sql_prepared_statement *prep_stmt);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen struct sql_statement *
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen (*statement_init)(struct sql_db *db, const char *query_template);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen struct sql_statement *
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen (*statement_init_prepared)(struct sql_prepared_statement *prep_stmt);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen void (*statement_abort)(struct sql_statement *stmt);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen void (*statement_set_timestamp)(struct sql_statement *stmt,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen const struct timespec *ts);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen void (*statement_bind_str)(struct sql_statement *stmt,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen unsigned int column_idx, const char *value);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen void (*statement_bind_binary)(struct sql_statement *stmt,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen unsigned int column_idx, const void *value,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen size_t value_size);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen void (*statement_bind_int64)(struct sql_statement *stmt,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen unsigned int column_idx, int64_t value);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen void (*statement_query)(struct sql_statement *stmt,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen sql_query_callback_t *callback, void *context);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen struct sql_result *(*statement_query_s)(struct sql_statement *stmt);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen void (*update_stmt)(struct sql_transaction_context *ctx,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen struct sql_statement *stmt,
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen unsigned int *affected_rows);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen};
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainenstruct sql_db {
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen const char *name;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen enum sql_db_flags flags;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen struct sql_db_vfuncs v;
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen ARRAY(union sql_db_module_context *) module_contexts;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen void (*state_change_callback)(struct sql_db *db,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen enum sql_db_state prev_state,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen void *context);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen void *state_change_context;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen enum sql_db_state state;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* last time we started connecting to this server
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen (which may or may not have succeeded) */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen time_t last_connect_try;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen unsigned int connect_delay;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen unsigned int connect_failure_count;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen struct timeout *to_reconnect;
4c9c55e15f35474f53f11659e796c63b1c34e884Timo Sirainen
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen bool no_reconnect:1;
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen};
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainenstruct sql_result_vfuncs {
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen void (*free)(struct sql_result *result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen int (*next_row)(struct sql_result *result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unsigned int (*get_fields_count)(struct sql_result *result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *(*get_field_name)(struct sql_result *result,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unsigned int idx);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen int (*find_field)(struct sql_result *result, const char *field_name);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *(*get_field_value)(struct sql_result *result,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unsigned int idx);
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen const unsigned char *
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen (*get_field_value_binary)(struct sql_result *result,
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen unsigned int idx,
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen size_t *size_r);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *(*find_field_value)(struct sql_result *result,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *field_name);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *const *(*get_values)(struct sql_result *result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *(*get_error)(struct sql_result *result);
402f9bcf48cbccc17fdb5f3ea411a7967aed0fd0Timo Sirainen void (*more)(struct sql_result **result, bool async,
402f9bcf48cbccc17fdb5f3ea411a7967aed0fd0Timo Sirainen sql_query_callback_t *callback, void *context);
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen};
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainenstruct sql_prepared_statement {
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen struct sql_db *db;
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen};
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainenstruct sql_statement {
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen struct sql_db *db;
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen pool_t pool;
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen const char *query_template;
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen ARRAY_TYPE(const_string) args;
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen};
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainen
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainenstruct sql_field_map {
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen enum sql_field_type type;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen size_t offset;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen};
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainenstruct sql_result {
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen struct sql_result_vfuncs v;
3656c91dcb8336814bebd4500e81c3dde25233e6Timo Sirainen int refcount;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen struct sql_db *db;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen const struct sql_field_def *fields;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen unsigned int map_size;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen struct sql_field_map *map;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen void *fetch_dest;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen size_t fetch_dest_size;
1601169d6f6004e0656238ed7691c16f3aab61aaTimo Sirainen enum sql_result_error_type error_type;
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen bool failed:1;
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen bool failed_try_retry:1;
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen bool callback:1;
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen};
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainenstruct sql_transaction_context {
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen struct sql_db *db;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* commit() must use this query list if head is non-NULL. */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen struct sql_transaction_query *head, *tail;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen};
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
8d80659e504ffb34bb0c6a633184fece35751b18Timo SirainenARRAY_DEFINE_TYPE(sql_drivers, const struct sql_db *);
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainenextern ARRAY_TYPE(sql_drivers) sql_drivers;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenextern struct sql_result sql_not_connected_result;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstruct sql_db *
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainendriver_sqlpool_init(const char *connect_string, const struct sql_db *driver);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenvoid sql_db_set_state(struct sql_db *db, enum sql_db_state state);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenvoid sql_transaction_add_query(struct sql_transaction_context *ctx, pool_t pool,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen const char *query, unsigned int *affected_rows);
9f0fc74e3387d3e496fb0c8f77633e27e48cc1ffTimo Sirainenconst char *sql_statement_get_query(struct sql_statement *stmt);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#endif