This repository has been archived on 2024-07-15. You can view files and clone it, but cannot push or open issues or pull requests.
hedera/db/db-calc.c

388 lines
10 KiB
C

/*
* Copyright (C) 2012 - Juan Ferrer Toribio
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "db-calc.h"
#include "db-row.h"
/**
* SECTION: db-calc
* @short_description: the result of an operation made over a column or between
* columns
* @title: DbCalc
*
* A #GvnParam that holds the result of the user-given operation applied over
* a given column of a #DbModel. When any of these values is modified, #DbCalc
* updates its value according to the changes.
*
* #DbCalc has as built-in operations the sumatory and average that can be
* selected passing #DB_CALC_TYPE_SUM OR #DB_CALC_TYPE_AVG, respectively.
*
* While creating a #DbCalc it's possible to specify either a single column
* number, to calculate using only this column and no additional operations, or
* a #DbCalcFunc function, to use more than one column or do any operation
* needed with the #DbModel fields, in the latter case, custom data can also be
* passed.
**/
G_DEFINE_TYPE (DbCalc, db_calc, GVN_TYPE_PARAM);
/**
* db_calc_new:
* @model: the #DbModel in which the operations will apply
* @type: the #DbCalcOperationType
* @col: the number of a column (starting with 0)
*
* Creates a new #DbCalc using @col to select the only column to be processed
* in each row of @model.
*
* Return value: a new #DbCalc
**/
DbCalc * db_calc_new (DbModel * model, DbCalcType type, gint col)
{
g_return_val_if_fail (DB_IS_MODEL (model), NULL);
g_return_val_if_fail (0 <= col || col < db_model_get_ncols (model), NULL);
return g_object_new
(DB_TYPE_CALC, "model", model, "type", type, "col", col, NULL);
}
/**
* db_calc_new_with_func:
* @model: the #DbModel in which the operations will apply
* @type: the #DbCalcOperationType
* @func: (scope call): a #DbCalcFunc
* @data: additional data that will be passed to @func
*
* Creates a new #DbCalc using @func as the operation that will apply to each
* row of @model.
*
* Return value: a new #DbCalc
**/
DbCalc * db_calc_new_with_func (DbModel * model, DbCalcType type, DbCalcFunc func, gpointer data)
{
g_return_val_if_fail (DB_IS_MODEL (model), NULL);
g_return_val_if_fail (func, NULL);
return g_object_new
(DB_TYPE_CALC, "model", model, "type", type, "function", func, "data", data, NULL);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Private
static gdouble db_calc_value_to_double (const GValue * v)
{
switch (G_VALUE_TYPE (v))
{
case G_TYPE_UINT:
return (gdouble) g_value_get_uint (v);
case G_TYPE_UINT64:
return (gdouble) g_value_get_uint64 (v);
case G_TYPE_ULONG:
return (gdouble) g_value_get_ulong (v);
case G_TYPE_INT:
return (gdouble) g_value_get_int (v);
case G_TYPE_INT64:
return (gdouble) g_value_get_int64 (v);
case G_TYPE_LONG:
return (gdouble) g_value_get_long (v);
case G_TYPE_FLOAT:
return (gdouble) g_value_get_float (v);
case G_TYPE_DOUBLE:
return g_value_get_double (v);
case G_TYPE_STRING:
return g_strtod (g_value_get_string (v), NULL);
default:
return 0;
}
}
static void db_calc_internal_func
(DbCalc * obj, DbIter * iter, GValue * out, gpointer data)
{
const GValue * value = db_model_get_value (obj->model, iter, obj->col, NULL);
g_value_copy (value, g_value_init (out, G_VALUE_TYPE (value)));
}
static void db_calc_before_unset (DbModel * model, DbIter * iter, DbCalc * obj)
{
GValue value = G_VALUE_INIT;
obj->func (obj, iter, &value, obj->data);
if (!gvn_value_is_null (&value))
{
gdouble current, old;
current = db_calc_value_to_double (gvn_param_get_value (GVN_PARAM (obj)));
old = db_calc_value_to_double (&value);
if (obj->type == DB_CALC_TYPE_AVG)
{
if (obj->count != 1)
current = (current * (gdouble) obj->count - old) / (gdouble) (obj->count - 1);
else
current = 0;
}
else if (obj->type == DB_CALC_TYPE_SUM)
current -= old;
obj->count--;
g_value_unset (&value);
g_value_set_double (g_value_init (&value, G_TYPE_DOUBLE), current);
GVN_PARAM_GET_CLASS (obj)->put_value (GVN_PARAM (obj), &value);
}
g_value_unset (&value);
}
static void db_calc_before_delete (DbModel * model, gint path, DbCalc * obj)
{
DbIter iter;
db_model_get_iter (model, &iter, path);
db_calc_before_unset (model, &iter, obj);
}
static void db_calc_after_set (DbModel * model, DbIter * iter, DbCalc * obj)
{
GValue value = G_VALUE_INIT;
obj->func (obj, iter, &value, obj->data);
if (!gvn_value_is_null (&value))
{
gdouble current, new;
current = db_calc_value_to_double (gvn_param_get_value (GVN_PARAM (obj)));
new = db_calc_value_to_double (&value);
if (obj->type == DB_CALC_TYPE_AVG)
current = (current * (gdouble) obj->count + new) / (gdouble) (obj->count + 1);
else if (obj->type == DB_CALC_TYPE_SUM)
current += new;
obj->count++;
g_value_unset (&value);
g_value_set_double (g_value_init (&value, G_TYPE_DOUBLE), current);
GVN_PARAM_GET_CLASS (obj)->put_value (GVN_PARAM (obj), &value);
}
g_value_unset (&value);
}
static void db_calc_refresh (DbModel * model, DbModelStatus status, DbCalc * obj)
{
DbIter iter;
gdouble current = 0;
GValue val = G_VALUE_INIT;
if (status != DB_MODEL_STATUS_READY) return;
if (!db_model_get_iter_first (model, &iter)) return;
obj->count = 0;
do {
obj->func (obj, &iter, &val, obj->data);
if (!gvn_value_is_null (&val))
{
current += db_calc_value_to_double (&val);
obj->count++;
}
g_value_unset (&val);
}
while (db_model_iter_next (model, &iter));
if (obj->type == DB_CALC_TYPE_AVG)
current = (obj->count > 0) ? current / (gdouble) obj->count : 0;
g_value_set_double (g_value_init (&val, G_TYPE_DOUBLE), current);
GVN_PARAM_GET_CLASS (obj)->put_value (GVN_PARAM (obj), &val);
g_value_unset (&val);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class
enum
{
PROP_MODEL = 1
,PROP_TYPE
,PROP_FUNC
,PROP_DATA
,PROP_COL
};
static void db_calc_set_property (DbCalc * obj, guint id,
const GValue * value, GParamSpec * pspec)
{
switch (id)
{
case PROP_MODEL:
obj->model = g_value_dup_object (value);
if (obj->model)
g_object_connect (obj->model
,"signal::line-updated", db_calc_before_unset, obj
,"signal-after::line-updated", db_calc_after_set, obj
,"signal::line-deleted", db_calc_before_delete, obj
,"signal::status-changed", db_calc_refresh, obj
, NULL);
break;
case PROP_TYPE:
obj->type = g_value_get_int (value);
break;
case PROP_FUNC:
if (g_value_get_pointer (value))
obj->func = g_value_get_pointer (value);
break;
case PROP_DATA:
obj->data = g_value_get_pointer (value);
break;
case PROP_COL:
obj->col = g_value_get_int (value);
if (!obj->func)
obj->func = db_calc_internal_func;
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
}
}
static void db_calc_get_property (DbCalc * obj, guint id,
GValue * value, GParamSpec * pspec)
{
switch (id)
{
case PROP_MODEL:
g_value_set_object (value, obj->model);
break;
case PROP_TYPE:
g_value_set_int (value, obj->type);
break;
case PROP_FUNC:
g_value_set_pointer (value, obj->func);
break;
case PROP_DATA:
g_value_set_pointer (value, obj->data);
break;
case PROP_COL:
g_value_set_int (value, obj->col);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
}
}
static void db_calc_init (DbCalc * obj)
{
GvnParamSpec * spec;
obj->model = NULL;
obj->type = 0;
obj->func = NULL;
obj->data = NULL;
obj->col = 0;
obj->count = 0;
spec = gvn_param_spec_new_with_attrs (G_TYPE_DOUBLE, FALSE, TRUE, NULL);
GVN_PARAM_GET_CLASS (obj)->set_spec (GVN_PARAM (obj), spec);
}
static void db_calc_finalize (DbCalc * obj)
{
if (obj->model)
{
g_object_disconnect (obj->model
,"any_signal::line-updated", db_calc_before_unset, obj
,"any_signal::line-updated", db_calc_after_set, obj
,"any_signal::line-deleted", db_calc_before_delete, obj
,"any_signal::status-changed", db_calc_refresh, obj
,NULL
);
g_clear_object (&obj->model);
}
}
static void db_calc_class_init (DbCalcClass * k)
{
GObjectClass * klass = G_OBJECT_CLASS (k);
klass->finalize = (GObjectFinalizeFunc) db_calc_finalize;
klass->set_property = (GObjectSetPropertyFunc) db_calc_set_property;
klass->get_property = (GObjectGetPropertyFunc) db_calc_get_property;
g_object_class_install_property (klass, PROP_MODEL
,g_param_spec_object ("model"
,_("Model")
,_("The model where the operations will be applied")
,DB_TYPE_MODEL
,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)
);
g_object_class_install_property (klass, PROP_TYPE
,g_param_spec_enum ("type"
,_("Operation type")
,_("The type of the operation applied over the function")
,DB_TYPE_CALC_TYPE
,DB_CALC_TYPE_SUM
,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)
);
g_object_class_install_property (klass, PROP_FUNC
,g_param_spec_pointer ("function"
,_("Function")
,_("The function to execute")
,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)
);
g_object_class_install_property (klass, PROP_DATA
,g_param_spec_pointer ("data"
,_("Data")
,_("The user provided data for the function")
,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)
);
g_object_class_install_property (klass, PROP_COL
,g_param_spec_int ("col"
,_("Column")
,_("A column to apply the operations over it")
,0
,G_MAXINT32
,0
,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)
);
}
GType db_calc_type_get_type ()
{
static GType type = 0;
if (type == 0)
{
static const GEnumValue values[] =
{
{DB_CALC_TYPE_SUM, "DB_CALC_TYPE_SUM", "sum"}
,{DB_CALC_TYPE_AVG, "DB_CALC_TYPE_AVG", "avg"}
,{0, NULL, NULL}
};
type = g_enum_register_static
(g_intern_static_string ("DbCalcType"), values);
}
return type;
}