static int _sql_setparam(struct sql_table_helper* th,char* key, char* value) {
    char* dbi_errstr=NULL;
    dbi_driver driver;
    /* if not connected */
    if (! th->conn) {
        /* initialize some stuff */
        th->table_next=th->table_start;
        th->result=NULL;
        th->connected=0;
        /* initialize db */
        if (getenv("RRDDEBUGSQL")) {
            fprintf(stderr,"RRDDEBUGSQL: %li: initialize libDBI\n",time(NULL) );
        }
        dbi_initialize(NULL);
        /* load the driver */
        driver=dbi_driver_open(th->dbdriver);
        if (! driver) {
            rrd_set_error( "libdbi - no such driver: %s (possibly a dynamic link problem of the driver being linked without -ldbi)",th->dbdriver);
            return -1;
        }
        /* and connect to driver */
        th->conn=dbi_conn_open(driver);
        /* and handle errors */
        if (! th->conn) {
            rrd_set_error( "libdbi - could not open connection to driver %s",th->dbdriver);
            dbi_shutdown();
            return -1;
        }
    }
    if (th->connected) {
        rrd_set_error( "we are already connected - can not set parameter %s=%s",key,value);
        _sql_close(th);
        return -1;
    }
    if (getenv("RRDDEBUGSQL")) {
        fprintf(stderr,"RRDDEBUGSQL: %li: setting option %s to %s\n",time(NULL),key,value );
    }
    if (strcmp(key, "port") == 0) {
        if (dbi_conn_set_option_numeric(th->conn,key,atoi(value))) {
            dbi_conn_error(th->conn,(const char**)&dbi_errstr);
            rrd_set_error( "libdbi: problems setting %s to %d - %s",key,value,dbi_errstr);
            _sql_close(th);
            return -1;
        }
    } else {
        if (dbi_conn_set_option(th->conn,key,value)) {
            dbi_conn_error(th->conn,(const char**)&dbi_errstr);
            rrd_set_error( "libdbi: problems setting %s to %s - %s",key,value,dbi_errstr);
            _sql_close(th);
            return -1;
        }
    }
    return 0;
}
int
rrd_fetch_fn_libdbi(
    const char     *filename,  /* name of the rrd */
    enum cf_en     UNUSED(cf_idx), /* consolidation function */
    time_t         *start,
    time_t         *end,       /* which time frame do you want ?
			        * will be changed to represent reality */
    unsigned long  *step,      /* which stepsize do you want? 
				* will be changed to represent reality */
    unsigned long  *ds_cnt,    /* number of data sources in file */
    char           ***ds_namv, /* names of data_sources */
    rrd_value_t    **data)     /* two dimensional array containing the data */
{
  /* the separator used */
  char separator='/';
  /* a local copy of the filename - used for copying plus some pointer variables */
  char filenameworkcopy[10240];
  char *tmpptr=filenameworkcopy;
  char *nextptr=NULL;
  char *libdbiargs=NULL;
  char *sqlargs=NULL;
  /* the settings for the "works" of rrd */
  int fillmissing=0;
  unsigned long minstepsize=300;
  /* by default assume unixtimestamp */
  int isunixtime=1;
  /* the result-set */
  long r_timestamp,l_timestamp,d_timestamp;
  double r_value,l_value,d_value;
  int r_status;
  int rows;
  long idx;
  int derive=0;
  /* the libdbi connection data and the table_help structure */
  struct sql_table_helper table_help;
  char where[10240];
  table_help.conn=NULL;
  table_help.where=where;
  table_help.filename=filename;

  /* some loop variables */
  int i=0;

  /* check header */
  if (strncmp("sql",filename,3)!=0) { 
    rrd_set_error( "formatstring wrong - %s",filename );return -1; 
  }
  if (filename[3]!=filename[4]) { 
    rrd_set_error( "formatstring wrong - %s",filename );return -1; 
  }

  /* now make this the separator */
  separator=filename[3];

  /* copy filename for local modifications during parsing */
  strncpy(filenameworkcopy,filename+5,sizeof(filenameworkcopy));

  /* get the driver */
  table_help.dbdriver=tmpptr;
  libdbiargs=_find_next_separator(tmpptr,separator);
  if (! libdbiargs) { 
    /* error in argument */
    rrd_set_error( "formatstring wrong as we did not find \"%c\"- %s",separator,table_help.dbdriver);
    return -1; 
  }

  /* now find the next double separator - this defines the args to the database */
  sqlargs=_find_next_separator_twice(libdbiargs,separator);
  if (!sqlargs) {
    rrd_set_error( "formatstring wrong for db arguments as we did not find \"%c%c\" in \"%s\"",separator,separator,libdbiargs);
    return 1;
  }

  /* now we can start with the SQL Statement - best to start with this first, 
     as then the error-handling is easier, as we do not have to handle libdbi shutdown as well */

  /* parse the table(s) */
  table_help.table_start=sqlargs;
  nextptr=_find_next_separator(table_help.table_start,separator);
  if (! nextptr) { 
    /* error in argument */
    rrd_set_error( "formatstring wrong - %s",tmpptr);
    return -1; 
  }
  /* hex-unescape the value */
  if(_inline_unescape(table_help.table_start)) { return -1; }

  /* parse the unix timestamp column */
  table_help.timestamp=nextptr;
  nextptr=_find_next_separator(nextptr,separator);
  if (! nextptr) { 
    /* error in argument */
    rrd_set_error( "formatstring wrong - %s",tmpptr);
    return -1; 
  }
  /* if we have leading '*', then we have a TIMEDATE Field*/
  if (table_help.timestamp[0]=='*') { isunixtime=0; table_help.timestamp++; }
  /* hex-unescape the value */
  if(_inline_unescape(table_help.timestamp)) { return -1; }

  /* parse the value column */
  table_help.value=nextptr;
  nextptr=_find_next_separator(nextptr,separator);
  if (! nextptr) { 
    /* error in argument */
    rrd_set_error( "formatstring wrong - %s",tmpptr);
    return -1; 
  }
  /* hex-unescape the value */
  if(_inline_unescape(table_help.value)) { return -1; }
  
  /* now prepare WHERE clause as empty string*/
  where[0]=0;

  /* and the where clause */
  sqlargs=nextptr;
  while(sqlargs) {
    /* find next separator */
    nextptr=_find_next_separator(sqlargs,separator);
    /* now handle fields */
    if (strcmp(sqlargs,"derive")==0) { /* the derive option with the default allowed max delta */
      derive=600;
    } else if (strcmp(sqlargs,"prediction")==0) {
      rrd_set_error("argument prediction is no longer supported in a DEF - use new generic CDEF-functions instead");
      return -1;
    } else if (strcmp(sqlargs,"sigma")==0) {
      rrd_set_error("argument sigma is no longer supported in a DEF - use new generic CDEF-functions instead");
      return -1;
    } else if (*sqlargs==0) { /* ignore empty */
    } else { /* else add to where string */
      if (where[0]) {strcat(where," AND ");}
      strcat(where,sqlargs);
    }
    /* and continue loop with next pointer */
    sqlargs=nextptr;
  }
  /* and unescape */
  if(_inline_unescape(where)) { return -1; }

  /* now parse LIBDBI options - this start initializing libdbi and beyond this point we need to reset the db as well in case of errors*/
  while (libdbiargs) {
    /* find separator */
    nextptr=_find_next_separator(libdbiargs,separator);
    /* now find =, separating key from value*/
    tmpptr=_find_next_separator(libdbiargs,'=');
    if (! tmpptr) { 
      rrd_set_error( "formatstring wrong for db arguments as we did not find \"=\" in \"%s\"",libdbiargs);
      _sql_close(&table_help);
      return 1;
    }
    /* hex-unescape the value */
    if(_inline_unescape(tmpptr)) { return -1; }
    /* now handle the key/value pair */
    if (strcmp(libdbiargs,"rrdminstepsize")==0) { /* allow override for minstepsize */
      i=atoi(tmpptr);if (i>0) { minstepsize=i; }
    } else if (strcmp(libdbiargs,"rrdfillmissing")==0) { /* allow override for minstepsize */
      i=atoi(tmpptr);if (i>0) { fillmissing=i; }
    } else if (strcmp(libdbiargs,"rrdderivemaxstep")==0) { /* allow override for derived max delta */
      i=atoi(tmpptr);if (i>0) { if (derive) { derive=i; }}
    } else { /* store in libdbi, as these are parameters */
      if (_sql_setparam(&table_help,libdbiargs,tmpptr)) { return -1; }
    }
    /* and continue loop with next pointer */
    libdbiargs=nextptr;
  }
  
  /* and modify step if given */
  if (*step<minstepsize) {*step=minstepsize;}
  *start-=(*start)%(*step);
  *end-=(*end)%(*step);

  /* and append the SQL WHERE Clause for the timeframe calculated above (adding AND if required) */
  if (where[0]) {strcat(where," AND ");}
  i=strlen(where);
  if (isunixtime) {
    snprintf(where+i,sizeof(where)-1-i,"%li < %s AND %s < %li",*start,table_help.timestamp,table_help.timestamp,*end);
  } else {
    char tsstart[64];strftime(tsstart,sizeof(tsstart),"%Y-%m-%d %H:%M:%S",localtime(start));
    char tsend[64];strftime(tsend,sizeof(tsend),"%Y-%m-%d %H:%M:%S",localtime(end));
    snprintf(where+i,sizeof(where)-1-i,"'%s' < %s AND %s < '%s'",tsstart,table_help.timestamp,table_help.timestamp,tsend);
  }

  /* and now calculate the number of rows in the resultset... */
  rows=((*end)-(*start))/(*step)+2;
  
  /* define the result set variables/columns returned */
  *ds_cnt=5;
  *ds_namv=(char**)malloc((*ds_cnt)*sizeof(char*));
  for (i=0;i<(int)(*ds_cnt);i++) {
    tmpptr=(char*)malloc(sizeof(char) * DS_NAM_SIZE);
    (*ds_namv)[i]=tmpptr;
    /* now copy what is required */
    switch (i) {
    case 0: strncpy(tmpptr,"min",DS_NAM_SIZE-1); break;
    case 1: strncpy(tmpptr,"avg",DS_NAM_SIZE-1); break;
    case 2: strncpy(tmpptr,"max",DS_NAM_SIZE-1); break;
    case 3: strncpy(tmpptr,"count",DS_NAM_SIZE-1); break;
    case 4: strncpy(tmpptr,"sigma",DS_NAM_SIZE-1); break;
    }
  }

  /* allocate memory for resultset (with the following columns: min,avg,max,count,sigma) */
  i=(rows+1) * sizeof(rrd_value_t)*(*ds_cnt);
  if (((*data) = malloc(i))==NULL){
    /* and return error */
    rrd_set_error("malloc failed for %i bytes",i);
    return(-1);
  }
  /* and fill with NAN */
  for(i=0;i<rows;i++) {
    (*data)[i*(*ds_cnt)+0]=DNAN; /* MIN */
    (*data)[i*(*ds_cnt)+1]=DNAN; /* AVG */
    (*data)[i*(*ds_cnt)+2]=DNAN; /* MAX */
    (*data)[i*(*ds_cnt)+3]=0;    /* COUNT */
    (*data)[i*(*ds_cnt)+4]=DNAN; /* SIGMA */
  }
  /* and assign undefined values for last - in case of derived calculation */
  l_value=DNAN;l_timestamp=0;
  /* here goes the real work processing all data */
  while((r_status=_sql_fetchrow(&table_help,&r_timestamp,&r_value,derive))>0) {
    /* processing of value */
    /* calculate index for the timestamp */
    idx=(r_timestamp-(*start))/(*step);
    /* some out of bounds checks on idx */
    if (idx<0) { idx=0;}
    if (idx>rows) { idx=rows;}
    /* and calculate derivative if necessary */
    if (derive) {
      /* calc deltas */
      d_timestamp=r_timestamp-l_timestamp;
      d_value=r_value-l_value;
      /* assign current as last values */
      l_timestamp=r_timestamp;
      l_value=r_value;
      /* assign DNAN by default for value */
      r_value=DNAN;
      /* check for timestamp delta to be within an acceptable range */
      if ((d_timestamp>0)&&(d_timestamp<2*derive)) {
	/* only handle positive delta - avoid wrap-arrounds/counter resets showing up as spikes */
	if (d_value>0) {
	  /* and normalize to per second */
	  r_value=d_value/d_timestamp;
	}
      }
    }
    /* only add value if we have a value that is not NAN */
    if (! isnan(r_value)) {
      if ((*data)[idx*(*ds_cnt)+3]==0) { /* count is 0 so assign to overwrite DNAN */
	(*data)[idx*(*ds_cnt)+0]=r_value; /* MIN */
	(*data)[idx*(*ds_cnt)+1]=r_value; /* AVG */
	(*data)[idx*(*ds_cnt)+2]=r_value; /* MAX */
	(*data)[idx*(*ds_cnt)+3]=1;       /* COUNT */
	(*data)[idx*(*ds_cnt)+4]=r_value; /* SIGMA */
      } else {
	/* MIN */
	if ((*data)[idx*(*ds_cnt)+0]>r_value) { (*data)[idx*(*ds_cnt)+0]=r_value; }
        /* AVG - at this moment still sum - corrected in post processing */
	(*data)[idx*(*ds_cnt)+1]+=r_value;
        /* MAX */
	if ((*data)[idx*(*ds_cnt)+2]<r_value) { (*data)[idx*(*ds_cnt)+2]=r_value; }
        /* COUNT */
	(*data)[idx*(*ds_cnt)+3]++;
        /* SIGMA - at this moment still sum of squares - corrected in post processing */
	(*data)[idx*(*ds_cnt)+4]+=r_value*r_value;
      }
    }
  }
  /* and check for negativ status, pass back immediately */
  if (r_status==-1) { return -1; }

  /* post processing */
  for(idx=0;idx<rows;idx++) {
    long count=(*data)[idx*(*ds_cnt)+3];
    if (count>0) {
      /* calc deviation first */
      if (count>2) {
	r_value=count*(*data)[idx*(*ds_cnt)+4]-(*data)[idx*(*ds_cnt)+1]*(*data)[idx*(*ds_cnt)+1];
	if (r_value<0) { 
	  r_value=DNAN; 
	} else {
	  r_value=sqrt(r_value/(count*(count-1)));
	}
      }
      (*data)[idx*(*ds_cnt)+4]=r_value;
      /* now the average */
      (*data)[idx*(*ds_cnt)+1]/=count;
    }
  }

  /* and return OK */
  return 0;
}
static int _sql_fetchrow(struct sql_table_helper* th,time_t *timestamp, rrd_value_t *value,int ordered) {
  char* dbi_errstr=NULL;
  char sql[10240];
  time_t startt=0,endt=0;
  /*connect to the database if needed */
  if (! th->conn) {
      rrd_set_error( "libdbi no parameters set for libdbi",th->filename,dbi_errstr);
      return -1;
  }
  if (! th->connected) {
    /* and now connect */
    if (getenv("RRDDEBUGSQL")) { fprintf(stderr,"RRDDEBUGSQL: %li: connect to DB\n",time(NULL) ); }
    if (dbi_conn_connect(th->conn) <0) {
      dbi_conn_error(th->conn,(const char**)&dbi_errstr);
      rrd_set_error( "libdbi: problems connecting to db with connect string %s - error: %s",th->filename,dbi_errstr);
      _sql_close(th);
      return -1;
    }
    th->connected=1;
  }
  /* now find out regarding an existing result-set */
  if (! th->result) {
    /* return if table_next is NULL */
    if (th->table_next==NULL) { 
    if (getenv("RRDDEBUGSQL")) { fprintf(stderr,"RRDDEBUGSQL: %li: reached last table to connect to\n",time(NULL) ); }
      /* but first close connection */
      _sql_close(th);
      /* and return with end of data */
      return 0;
    }
    /* calculate the table to use next */
    th->table_start=th->table_next;
    th->table_next=_find_next_separator(th->table_start,'+');
    _inline_unescape(th->table_start);
    /* and prepare FULL SQL Statement */
    if (ordered) {
      snprintf(sql,sizeof(sql)-1,"SELECT %s as rrd_time, %s as rrd_value FROM %s WHERE %s ORDER BY %s",
  	       th->timestamp,th->value,th->table_start,th->where,th->timestamp);
    } else {
      snprintf(sql,sizeof(sql)-1,"SELECT %s as rrd_time, %s as rrd_value FROM %s WHERE %s",
  	       th->timestamp,th->value,th->table_start,th->where);
    }
    /* and execute sql */
    if (getenv("RRDDEBUGSQL")) { startt=time(NULL); fprintf(stderr,"RRDDEBUGSQL: %li: executing %s\n",startt,sql); }
    th->result=dbi_conn_query(th->conn,sql);
    if (startt) { endt=time(NULL);fprintf(stderr,"RRDDEBUGSQL: %li: timing %li\n",endt,endt-startt); }
    /* handle error case */
    if (! th->result) {
      dbi_conn_error(th->conn,(const char**)&dbi_errstr);      
      if (startt) { fprintf(stderr,"RRDDEBUGSQL: %li: error %s\n",endt,dbi_errstr); }
      rrd_set_error("libdbi: problems with query: %s - errormessage: %s",sql,dbi_errstr);
      _sql_close(th);
      return -1;
    }
  }
  /* and now fetch key and value */
  if (! dbi_result_next_row(th->result)) {
    /* free result */
    dbi_result_free(th->result);
    th->result=NULL;
    /* and call recursively - this will open the next table or close connection as a whole*/
    return _sql_fetchrow(th,timestamp,value,ordered);
  } 
  /* and return with flag for one value */
  *timestamp=rrd_fetch_dbi_long(th->result,1);
  *value=rrd_fetch_dbi_double(th->result,2);
  return 1;
}