/*************************************************************************
 *
 *	Function: sql_init_socket
 *
 *	Purpose: Establish connection to the db
 *
 *************************************************************************/
static int sql_init_socket(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
    rlm_sql_unixodbc_sock *unixodbc_sock;
    long err_handle;

	if (!handle->conn) {
		handle->conn = (rlm_sql_unixodbc_sock *)rad_malloc(sizeof(rlm_sql_unixodbc_sock));
		if (!handle->conn) {
			return -1;
		}
	}
	unixodbc_sock = handle->conn;
	memset(unixodbc_sock, 0, sizeof(*unixodbc_sock));

    /* 1. Allocate environment handle and register version */
    err_handle = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&unixodbc_sock->env_handle);
    if (sql_state(err_handle, handle, config))
    {
	radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate environment handle\n");
	return -1;
    }
    err_handle = SQLSetEnvAttr(unixodbc_sock->env_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
    if (sql_state(err_handle, handle, config))
    {
	radlog(L_ERR, "rlm_sql_unixodbc: Can't register ODBC version\n");
	SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
	return -1;
    }
    /* 2. Allocate connection handle */
    err_handle = SQLAllocHandle(SQL_HANDLE_DBC, unixodbc_sock->env_handle, &unixodbc_sock->dbc_handle);
    if (sql_state(err_handle, handle, config))
    {
	radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate connection handle\n");
	SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
	return -1;
    }

    /* 3. Connect to the datasource */
    err_handle = SQLConnect(unixodbc_sock->dbc_handle,
	(SQLCHAR*) config->sql_server, strlen(config->sql_server),
	(SQLCHAR*) config->sql_login, strlen(config->sql_login),
	(SQLCHAR*) config->sql_password, strlen(config->sql_password));
    if (sql_state(err_handle, handle, config))
    {
	radlog(L_ERR, "rlm_sql_unixodbc: Connection failed\n");
	SQLFreeHandle(SQL_HANDLE_DBC, unixodbc_sock->dbc_handle);
	SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
	return -1;
    }

    /* 4. Allocate the statement */
    err_handle = SQLAllocStmt(unixodbc_sock->dbc_handle, &unixodbc_sock->stmt_handle);
    if (sql_state(err_handle, handle, config))
    {
	radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate the statement\n");
	return -1;
    }

    return 0;
}
/*************************************************************************
 *
 *	Function: sql_socket_init
 *
 *	Purpose: Establish connection to the db
 *
 *************************************************************************/
static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
    rlm_sql_unixodbc_conn_t *conn;
    long err_handle;

    MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_unixodbc_conn_t));
    talloc_set_destructor((void *) conn, sql_socket_destructor);

    /* 1. Allocate environment handle and register version */
    err_handle = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&conn->env);
    if (sql_state(err_handle, handle, config)) {
        ERROR("rlm_sql_unixodbc: Can't allocate environment handle\n");
        return -1;
    }

    err_handle = SQLSetEnvAttr(conn->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
    if (sql_state(err_handle, handle, config)) {
        ERROR("rlm_sql_unixodbc: Can't register ODBC version\n");
        SQLFreeHandle(SQL_HANDLE_ENV, conn->env);
        return -1;
    }

    /* 2. Allocate connection handle */
    err_handle = SQLAllocHandle(SQL_HANDLE_DBC, conn->env, &conn->dbc);
    if (sql_state(err_handle, handle, config)) {
        ERROR("rlm_sql_unixodbc: Can't allocate connection handle\n");
        SQLFreeHandle(SQL_HANDLE_ENV, conn->env);
        return -1;
    }

    /* 3. Connect to the datasource */
    {
        SQLCHAR *odbc_server, *odbc_login, *odbc_password;

        memcpy(&odbc_server, &config->sql_server, sizeof(odbc_server));
        memcpy(&odbc_login, &config->sql_login, sizeof(odbc_login));
        memcpy(&odbc_password, &config->sql_password, sizeof(odbc_password));
        err_handle = SQLConnect(conn->dbc,
                                odbc_server, strlen(config->sql_server),
                                odbc_login, strlen(config->sql_login),
                                odbc_password, strlen(config->sql_password));
    }

    if (sql_state(err_handle, handle, config)) {
        ERROR("rlm_sql_unixodbc: Connection failed\n");
        SQLFreeHandle(SQL_HANDLE_DBC, conn->dbc);
        SQLFreeHandle(SQL_HANDLE_ENV, conn->env);
        return -1;
    }

    /* 4. Allocate the statement */
    err_handle = SQLAllocStmt(conn->dbc, &conn->statement);
    if (sql_state(err_handle, handle, config)) {
        ERROR("rlm_sql_unixodbc: Can't allocate the statement\n");
        return -1;
    }

    return 0;
}
/*************************************************************************
 *
 *	Function: sql_affected_rows
 *
 *	Purpose: Return the number of rows affected by the query (update,
 *               or insert)
 *
 *************************************************************************/
static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
    rlm_sql_unixodbc_sock *unixodbc_sock = handle->conn;
    long err_handle;
    SQLLEN affected_rows;

    err_handle = SQLRowCount(unixodbc_sock->stmt_handle, &affected_rows);
    if (sql_state(err_handle, handle, config))
	return -1;

    return affected_rows;
}
/*************************************************************************
 *
 *	Function: sql_num_fields
 *
 *	Purpose: database specific num_fields function. Returns number
 *               of columns from query
 *
 *************************************************************************/
static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
    rlm_sql_unixodbc_sock *unixodbc_sock = handle->conn;
    long err_handle;
    SQLSMALLINT num_fields = 0;

    err_handle = SQLNumResultCols(unixodbc_sock->stmt_handle,&num_fields);
    if (sql_state(err_handle, handle, config))
	return -1;

    return num_fields;
}
/*************************************************************************
 *
 *	Function: sql_affected_rows
 *
 *	Purpose: Return the number of rows affected by the query (update,
 *	       or insert)
 *
 *************************************************************************/
static int sql_affected_rows(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
	rlm_sql_unixodbc_conn_t *conn = handle->conn;
	long err_handle;
	SQLLEN affected_rows;

	err_handle = SQLRowCount(conn->statement, &affected_rows);
	if (sql_state(err_handle, handle, config)) {
		return -1;
	}

	return affected_rows;
}
/*************************************************************************
 *
 *	Function: sql_num_fields
 *
 *	Purpose: database specific num_fields function. Returns number
 *	       of columns from query
 *
 *************************************************************************/
static int sql_num_fields(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
	rlm_sql_unixodbc_conn_t *conn = handle->conn;
	long err_handle;
	SQLSMALLINT num_fields = 0;

	err_handle = SQLNumResultCols(conn->statement,&num_fields);
	if (sql_state(err_handle, handle, config)) {
		return -1;
	}

	return num_fields;
}
/*************************************************************************
 *
 *	Function: sql_query
 *
 *	Purpose: Issue a non-SELECT query (ie: update/delete/insert) to
 *               the database.
 *
 *************************************************************************/
static int sql_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char *querystr) {
    rlm_sql_unixodbc_sock *unixodbc_sock = handle->conn;
    long err_handle;
    int state;

    /* Executing query */
    err_handle = SQLExecDirect(unixodbc_sock->stmt_handle, (SQLCHAR *)querystr, strlen(querystr));
    if ((state = sql_state(err_handle, handle, config))) {
	if(state == SQL_DOWN)
	    DEBUG("rlm_sql_unixodbc: rlm_sql will attempt to reconnect\n");
	return state;
    }
    return 0;
}
/*************************************************************************
 *
 *	Function: sql_fetch_row
 *
 *	Purpose: database specific fetch_row. Returns a rlm_sql_row_t struct
 *               with all the data for the query in 'handle->row'. Returns
 *		 0 on success, -1 on failure, SQL_DOWN if 'database is down'.
 *
 *************************************************************************/
static int sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
    rlm_sql_unixodbc_sock *unixodbc_sock = handle->conn;
    long err_handle;
    int state;

    handle->row = NULL;

    err_handle = SQLFetch(unixodbc_sock->stmt_handle);
    if(err_handle == SQL_NO_DATA_FOUND)
	return 0;
    if ((state = sql_state(err_handle, handle, config))) {
	if(state == SQL_DOWN)
	    DEBUG("rlm_sql_unixodbc: rlm_sql will attempt to reconnect");
	return state;
    }
    handle->row = unixodbc_sock->row;
    return 0;
}
/*************************************************************************
 *
 *	Function: sql_query
 *
 *	Purpose: Issue a non-SELECT query (ie: update/delete/insert) to
 *	       the database.
 *
 *************************************************************************/
static sql_rcode_t sql_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query) {
	rlm_sql_unixodbc_conn_t *conn = handle->conn;
	long err_handle;
	int state;

	/* Executing query */
	{
		SQLCHAR *odbc_query;

		memcpy(&odbc_query, &query, sizeof(odbc_query));
		err_handle = SQLExecDirect(conn->statement, odbc_query, strlen(query));
	}
	if ((state = sql_state(err_handle, handle, config))) {
		if(state == RLM_SQL_RECONNECT) {
			DEBUG("rlm_sql_unixodbc: rlm_sql will attempt to reconnect");
		}
		return state;
	}
	return 0;
}
/*************************************************************************
 *
 *	Function: sql_fetch_row
 *
 *	Purpose: database specific fetch_row. Returns a rlm_sql_row_t struct
 *	       with all the data for the query in 'handle->row'. Returns
 *		 0 on success, -1 on failure, RLM_SQL_RECONNECT if 'database is down'.
 *
 *************************************************************************/
static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config) {
	rlm_sql_unixodbc_conn_t *conn = handle->conn;
	long err_handle;
	int state;

	handle->row = NULL;

	err_handle = SQLFetch(conn->statement);
	if(err_handle == SQL_NO_DATA_FOUND) {
		return 0;
	}

	if ((state = sql_state(err_handle, handle, config))) {
		if(state == RLM_SQL_RECONNECT) {
			DEBUG("rlm_sql_unixodbc: rlm_sql will attempt to reconnect");
		}

		return state;
	}

	handle->row = conn->row;
	return 0;
}