2012-03-18 18:24:12 +02:00
|
|
|
/*
|
|
|
|
* db.c - Parts database
|
|
|
|
*
|
|
|
|
* Copyright 2012 by Werner Almesberger
|
|
|
|
*
|
|
|
|
* 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 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2012-04-29 04:50:15 +03:00
|
|
|
#include <stdlib.h>
|
2012-04-29 05:26:22 +03:00
|
|
|
#include <stdio.h>
|
2012-03-18 18:24:12 +02:00
|
|
|
#include <glib.h>
|
|
|
|
|
|
|
|
#include "util.h"
|
2012-04-29 04:50:15 +03:00
|
|
|
#include "lang.h"
|
|
|
|
#include "chr.h"
|
2012-03-18 18:24:12 +02:00
|
|
|
#include "db.h"
|
|
|
|
|
|
|
|
|
|
|
|
static GTree *tree = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
static gint comp(gconstpointer a, gconstpointer b)
|
|
|
|
{
|
|
|
|
const struct part *pa = a;
|
|
|
|
const struct part *pb = b;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Just subtracting the pointers may produce values outside the
|
|
|
|
* range of gint values.
|
|
|
|
*/
|
|
|
|
return pa->domain == pb->domain ?
|
|
|
|
pa->name < pb->name ? -1 : pa->name > pb->name ? 1 : 0 :
|
|
|
|
pa->domain < pb->domain ? -1 : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct part *part_lookup(const char *domain, const char *name)
|
|
|
|
{
|
|
|
|
struct part part = {
|
|
|
|
.domain = domain,
|
|
|
|
.name = name,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!tree)
|
|
|
|
return NULL;
|
|
|
|
return g_tree_lookup(tree, &part);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct part *part_add(const char *domain, const char *name)
|
|
|
|
{
|
|
|
|
struct part part = {
|
|
|
|
.domain = domain,
|
|
|
|
.name = name,
|
2012-04-29 04:55:34 +03:00
|
|
|
.param = NULL,
|
|
|
|
.stock = NULL,
|
2012-03-18 18:24:12 +02:00
|
|
|
};
|
|
|
|
struct part *p;
|
|
|
|
|
|
|
|
if (!tree)
|
|
|
|
tree = g_tree_new(comp);
|
|
|
|
p = g_tree_lookup(tree, &part);
|
|
|
|
if (!p) {
|
|
|
|
p = alloc_type(struct part);
|
2012-04-29 04:55:34 +03:00
|
|
|
*p = part;
|
2012-03-18 18:24:12 +02:00
|
|
|
p->next = p->prev = p;
|
|
|
|
g_tree_insert(tree, p, p);
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void part_alias(struct part *a, struct part *b)
|
|
|
|
{
|
|
|
|
struct part *tmp, *tmp2;
|
|
|
|
|
|
|
|
tmp = a->next;
|
|
|
|
a->next = b;
|
|
|
|
b->prev->next = tmp;
|
|
|
|
|
|
|
|
tmp2 = b->prev;
|
|
|
|
b->prev = a;
|
|
|
|
tmp->prev = tmp2;
|
|
|
|
}
|
2012-04-29 04:50:15 +03:00
|
|
|
|
|
|
|
|
|
|
|
static void convert_params(struct param **params,
|
|
|
|
const struct field *f, struct param ***res)
|
|
|
|
{
|
|
|
|
struct param **p, *prm;
|
|
|
|
const struct selector *sel;
|
|
|
|
const struct condition *cond;
|
|
|
|
|
|
|
|
while (f) {
|
|
|
|
for (p = params; *p; p = &(*p)->next)
|
|
|
|
if ((*p)->u.name == f->name)
|
|
|
|
break;
|
|
|
|
if (!*p) {
|
|
|
|
f = f->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove from list */
|
|
|
|
prm = *p;
|
|
|
|
*p = prm->next;
|
|
|
|
|
|
|
|
/* convert parameter */
|
|
|
|
prm->u.field = f;
|
2012-05-21 20:44:33 +03:00
|
|
|
if (!evaluate(f->fmt, prm->value.u.s, &prm->value))
|
2012-04-29 04:50:15 +03:00
|
|
|
yyerrorf("invalid value for %s", f->name);
|
|
|
|
|
|
|
|
/* add to result list */
|
|
|
|
**res = prm;
|
|
|
|
*res = &prm->next;
|
|
|
|
prm->next = NULL;
|
|
|
|
|
|
|
|
/* process selections */
|
|
|
|
for (sel = f->sel; sel; sel = sel->next) {
|
|
|
|
for (cond = sel->cond; cond; cond = cond->next)
|
|
|
|
if (compare(f->fmt, &prm->value, cond->relop,
|
|
|
|
&cond->value))
|
|
|
|
break;
|
|
|
|
if (cond) {
|
|
|
|
convert_params(params, sel->act.fields, res);
|
|
|
|
convert_params(params, sel->act.rules, res);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!sel) {
|
|
|
|
convert_params(params, f->any.fields, res);
|
|
|
|
convert_params(params, f->any.rules, res);
|
|
|
|
}
|
|
|
|
|
|
|
|
f = f->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-29 05:30:50 +03:00
|
|
|
void part_finalize(struct part *part, const struct action *act)
|
2012-04-29 04:50:15 +03:00
|
|
|
{
|
|
|
|
struct param *param = part->param;
|
|
|
|
struct param **res = &part->param;
|
|
|
|
struct param *next;
|
|
|
|
|
|
|
|
part->param = NULL;
|
2012-04-29 05:30:50 +03:00
|
|
|
convert_params(¶m, act->fields, &res);
|
|
|
|
convert_params(¶m, act->rules, &res);
|
2012-04-29 04:50:15 +03:00
|
|
|
while (param) {
|
|
|
|
yywarnf("extra parameter: %s", param->u.name);
|
|
|
|
next = param->next;
|
|
|
|
free(param);
|
|
|
|
param = next;
|
|
|
|
}
|
|
|
|
}
|
2012-04-29 05:26:22 +03:00
|
|
|
|
|
|
|
|
2012-05-24 01:29:04 +03:00
|
|
|
void part_add_stock(struct part *part, struct stock *s)
|
2012-05-01 02:03:10 +03:00
|
|
|
{
|
|
|
|
if (!part->stock) {
|
2012-05-24 01:29:04 +03:00
|
|
|
part->stock = s;
|
2012-05-01 02:03:10 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
yyerrorf("part %s %s already has stock", part->domain, part->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-06-02 11:48:22 +03:00
|
|
|
/* ----- Dumping ----------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
2012-05-01 02:40:11 +03:00
|
|
|
static void dump_stock(FILE *file, const struct stock *s)
|
|
|
|
{
|
|
|
|
const struct price *p;
|
|
|
|
|
2012-05-01 21:21:32 +03:00
|
|
|
fprintf(file, " %s %s %d %d %s %g",
|
|
|
|
s->provider->name, s->cat, s->avail, s->package, s->curr->name,
|
|
|
|
s->add);
|
2012-05-01 02:40:11 +03:00
|
|
|
for (p = s->price; p; p = p->next)
|
|
|
|
fprintf(file, " %d %g", p->qty, p->value);
|
|
|
|
fprintf(file, "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-29 05:26:22 +03:00
|
|
|
void part_dump(FILE *file, const struct part *part)
|
|
|
|
{
|
|
|
|
const struct param *p;
|
|
|
|
|
|
|
|
fprintf(file, "%s %s\n", part->domain, part->name);
|
|
|
|
if (part->param) {
|
|
|
|
fprintf(file, " ");
|
|
|
|
for (p = part->param; p; p = p->next) {
|
|
|
|
fprintf(file, " %s=", p->u.field->name);
|
2012-06-02 11:18:14 +03:00
|
|
|
dump_param(file, p->u.field->fmt, &p->value);
|
2012-04-29 05:26:22 +03:00
|
|
|
}
|
|
|
|
fprintf(file, "\n");
|
|
|
|
}
|
2012-05-01 02:40:11 +03:00
|
|
|
if (part->stock)
|
|
|
|
dump_stock(file, part->stock);
|
2012-04-29 05:26:22 +03:00
|
|
|
}
|
2012-05-01 04:49:33 +03:00
|
|
|
|
|
|
|
|
2012-06-02 11:48:22 +03:00
|
|
|
static gboolean dump_prm_traverse(gpointer key, gpointer value, gpointer data)
|
|
|
|
{
|
|
|
|
struct part *p = key;
|
|
|
|
FILE *file = data;
|
|
|
|
|
|
|
|
(void) value;
|
|
|
|
part_dump(file, p);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void parts_dump(FILE *file)
|
|
|
|
{
|
|
|
|
g_tree_foreach(tree, dump_prm_traverse, (void *) file);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-05-01 04:49:33 +03:00
|
|
|
/* ----- Currencies -------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
static struct currency *currencies = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
const struct currency *currency_lookup(const char *name)
|
|
|
|
{
|
|
|
|
const struct currency *c;
|
|
|
|
|
|
|
|
for (c = currencies; c; c = c->next)
|
|
|
|
if (c->name == name)
|
|
|
|
break;
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double currency_convert(const struct currency *from, const struct currency *to,
|
|
|
|
double value)
|
|
|
|
{
|
|
|
|
const struct exchange *ex;
|
|
|
|
|
|
|
|
for (ex = from->exchange; ex; ex = ex->next)
|
|
|
|
if (ex->dst == to)
|
|
|
|
return ex->rate*value;
|
|
|
|
fprintf(stderr, "cannot convert %s to %s\n", from->name, to->name);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct currency *currency_add(const char *name)
|
|
|
|
{
|
|
|
|
struct currency **c;
|
|
|
|
|
|
|
|
for (c = ¤cies; *c; c = &(*c)->next)
|
|
|
|
if ((*c)->name == name)
|
|
|
|
return *c;
|
|
|
|
*c = alloc_type(struct currency);
|
|
|
|
(*c)->name = name;
|
|
|
|
(*c)->exchange = NULL;
|
|
|
|
(*c)->next = NULL;
|
|
|
|
return *c;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void currency_exchange(struct currency *from, const struct currency *to,
|
|
|
|
double rate)
|
|
|
|
{
|
|
|
|
struct exchange **ex;
|
|
|
|
|
|
|
|
for (ex = &from->exchange; *ex; ex = &(*ex)->next)
|
|
|
|
if ((*ex)->dst == to)
|
|
|
|
yyerrorf("exchange %s -> %s is already defined",
|
|
|
|
from->name, to->name);
|
|
|
|
*ex = alloc_type(struct exchange);
|
|
|
|
(*ex)->dst = to;
|
|
|
|
(*ex)->rate = rate;
|
|
|
|
(*ex)->next = NULL;
|
|
|
|
}
|
2012-05-01 20:57:12 +03:00
|
|
|
|
|
|
|
|
|
|
|
/* ----- Provider database ------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
static struct provider *providers = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
struct provider *provider_lookup(const char *name)
|
|
|
|
{
|
|
|
|
struct provider *p;
|
|
|
|
|
|
|
|
for (p = providers; p; p = p->next)
|
|
|
|
if (p->name == name)
|
|
|
|
break;
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct provider *provider_add(const char *name)
|
|
|
|
{
|
|
|
|
struct provider *p;
|
|
|
|
|
|
|
|
p = provider_lookup(name);
|
|
|
|
if (p)
|
|
|
|
return p;
|
|
|
|
|
|
|
|
p = alloc_type(struct provider);
|
|
|
|
p->name = name;
|
|
|
|
p->curr = NULL;
|
|
|
|
p->shipping = 0;
|
|
|
|
p->minimum = 0;
|
|
|
|
p->next = providers;
|
|
|
|
providers = p;
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-05-22 03:19:22 +03:00
|
|
|
/* ----- Part search (by characteristics) ---------------------------------- */
|
2012-05-01 20:57:12 +03:00
|
|
|
|
2012-05-22 03:19:22 +03:00
|
|
|
|
|
|
|
struct sel_prm_data {
|
|
|
|
struct param *param;
|
|
|
|
const struct part **res;
|
|
|
|
int n_res;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static struct param *convert_vars(struct param *vars, const struct action *act)
|
|
|
|
{
|
|
|
|
struct param *res = NULL, **last = &res;
|
|
|
|
|
|
|
|
convert_params(&vars, act->fields, &last);
|
|
|
|
convert_params(&vars, act->rules, &last);
|
|
|
|
free_vars(vars);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @@@ By matching fields (and not field names), we avoid having to check for
|
|
|
|
* field compatibility. However, this also means that only fields that appear
|
|
|
|
* in the same place in the hierarchy can be matched.
|
|
|
|
*
|
|
|
|
* For example, a tolerance TOL in T=R could thus never match the tolerance TOL
|
|
|
|
* in T=C. This seems reasonable, but may need some more pondering.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @@@ We require all fields specified in the query to exist (and to match).
|
|
|
|
* Could there be cases where this doesn't make sense ?
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int params_match(const struct param *query, const struct param *part)
|
|
|
|
{
|
|
|
|
const struct param *q, *p;
|
|
|
|
|
|
|
|
for (q = query; q; q = q->next) {
|
|
|
|
for (p = part; p; p = p->next) {
|
|
|
|
if (q->u.field != p->u.field)
|
|
|
|
continue;
|
|
|
|
if (compare(q->u.field->fmt, &p->value, q->op,
|
|
|
|
&q->value))
|
|
|
|
goto next;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
next: ;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static gboolean sel_prm_traverse(gpointer key, gpointer value, gpointer data)
|
|
|
|
{
|
|
|
|
struct part *p = key;
|
|
|
|
struct sel_prm_data *d = data;
|
|
|
|
|
2012-05-24 00:48:59 +03:00
|
|
|
(void) value;
|
2012-05-22 03:19:22 +03:00
|
|
|
if (!params_match(d->param, p->param))
|
|
|
|
return FALSE;
|
|
|
|
d->res = realloc(d->res, sizeof(const struct part *)*(d->n_res+1));
|
|
|
|
if (!d->res)
|
|
|
|
abort();
|
|
|
|
d->res[d->n_res++] = key;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const struct part **select_parametric(struct param *vars,
|
|
|
|
const struct action *act)
|
|
|
|
{
|
|
|
|
struct sel_prm_data data = {
|
|
|
|
.param = convert_vars(vars, act),
|
|
|
|
.res = NULL,
|
|
|
|
.n_res = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
g_tree_foreach(tree, sel_prm_traverse, (void *) &data);
|
|
|
|
if (data.n_res)
|
|
|
|
data.res[data.n_res] = NULL;
|
|
|
|
return data.res;
|
|
|
|
}
|