eccodes/src/grib_db.cc

934 lines
26 KiB
C++

/*
* (C) Copyright 2005- ECMWF.
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
*
* In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
* virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
*/
/*
*
* Description: grib database routines
*
*/
#include "grib_api_internal.h"
#define GRIB_START_ARRAY_SIZE 5000
#define GRIB_ARRAY_INCREMENT 1000
#define SWAP(a, b) \
temp = (a); \
(a) = (b); \
(b) = temp;
#define GRIB_ORDER_BY_ASC 1
#define GRIB_ORDER_BY_DESC -1
/* Note: A fast cut-down version of strcmp which does NOT return -1 */
/* 0 means input strings are equal and 1 means not equal */
GRIB_INLINE static int grib_inline_strcmp(const char* a, const char* b)
{
if (*a != *b)
return 1;
while ((*a != 0 && *b != 0) && *(a) == *(b)) {
a++;
b++;
}
return (*a == 0 && *b == 0) ? 0 : 1;
}
static int grib_db_new_column(grib_db* db, int id, char* key, int type);
static void grib_db_delete_columns(grib_db* db);
static int grib_db_columns_resize(grib_db* db, size_t newsize);
static int grib_db_column_copy_from_handle(grib_handle* h, grib_db* db, int i);
static grib_db* grib_db_create_from_keys(grib_context* c, char** keys, int nkeys, int* err);
static void grib_fieldset* grib_db_fieldset_create(grib_db* db, int* err);
static void grib_db_sort(grib_set* set, int beg, int theEnd);
static grib_order_by* grib_db_new_order_by(grib_context* c, char* obstr);
static void grib_db_delete_order_by(grib_context* c, grib_order_by* order_by);
static int grib_db_resize(grib_db* db, size_t newsize);
static int grib_db_resize_fields(grib_db* db, size_t newsize);
/* --------------- grib_column functions ------------------*/
static int grib_db_new_column(grib_db* db, int id, char* key, int type)
{
grib_column* column = 0;
grib_context* c;
int err = 0;
if (!db)
return GRIB_INVALID_ARGUMENT;
c = db->context;
db->columns[id].errors = (int*)grib_context_malloc(c,
sizeof(int) * GRIB_START_ARRAY_SIZE);
switch (type) {
case GRIB_TYPE_LONG:
db->columns[id].long_values = (long*)grib_context_malloc(c,
sizeof(long) * GRIB_START_ARRAY_SIZE);
if (!db->columns[id].long_values) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_new_column : Cannot malloc %d bytes",
sizeof(long) * GRIB_START_ARRAY_SIZE);
err = GRIB_OUT_OF_MEMORY;
return err;
}
break;
case GRIB_TYPE_DOUBLE:
db->columns[id].double_values = (double*)grib_context_malloc(c,
sizeof(double) * GRIB_START_ARRAY_SIZE);
if (!db->columns[id].double_values) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_new_column : Cannot malloc %d bytes",
sizeof(double) * GRIB_START_ARRAY_SIZE);
err = GRIB_OUT_OF_MEMORY;
return err;
}
break;
case GRIB_TYPE_STRING:
db->columns[id].string_values = (char**)grib_context_malloc(c,
sizeof(char*) * GRIB_START_ARRAY_SIZE);
if (!db->columns[id].string_values) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_new_column : Cannot malloc %d bytes",
sizeof(char*) * GRIB_START_ARRAY_SIZE);
err = GRIB_OUT_OF_MEMORY;
return err;
}
break;
default:
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_new_column : unknown column type %d", type);
grib_context_free(c, column);
return err;
}
db->columns[id].context = c;
db->columns[id].name = strdup(key);
db->columns[id].type = type;
db->columns[id].values_array_size = GRIB_START_ARRAY_SIZE;
db->columns[id].size = 0;
return err;
}
static void grib_db_delete_columns(grib_db* db)
{
int i = 0;
grib_context* c;
if (!set)
return;
c = db->context;
for (i = 0; i < db->columns_size; i++) {
switch (db->columns[i].type) {
case GRIB_TYPE_LONG:
grib_context_free(c, db->columns[i].long_values);
break;
case GRIB_TYPE_DOUBLE:
grib_context_free(c, db->columns[i].double_values);
break;
case GRIB_TYPE_STRING:
for (i = 0; i < db->columns[i].size; i++)
grib_context_free(c, db->columns[i].string_values[i]);
grib_context_free(c, db->columns[i].string_values);
break;
default:
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_new_column : unknown column type %d", db->columns[i].type);
}
grib_context_free(c, db->columns[i].errors);
free(db->columns[i].name);
}
grib_context_free(c, db->columns);
}
static int grib_db_columns_resize(grib_db* db, size_t newsize)
{
double* newdoubles;
long* newlongs;
char** newstrings;
int* newerrors;
int i = 0;
grib_context* c;
if (!db || !db->columns)
return GRIB_INVALID_ARGUMENT;
c = db->context;
if (newsize <= db->columns[0].values_array_size)
return 0;
for (i = 0; i < db->columns_size; i++) {
switch (db->columns[i].type) {
case GRIB_TYPE_LONG:
newlongs = (long*)grib_context_realloc(c, db->columns[i].long_values,
newsize * sizeof(long));
if (!newlongs) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_columns_resize : Cannot malloc %d bytes", newsize - db->columns[i].values_array_size);
return GRIB_OUT_OF_MEMORY;
}
else
db->columns[i].long_values = newlongs;
break;
case GRIB_TYPE_DOUBLE:
newdoubles = (double*)grib_context_realloc(c, db->columns[i].double_values,
newsize * sizeof(double));
if (!newdoubles) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_columns_resize : Cannot malloc %d bytes", newsize - db->columns[i].values_array_size);
return GRIB_OUT_OF_MEMORY;
}
else
db->columns[i].double_values = newdoubles;
break;
case GRIB_TYPE_STRING:
newstrings = (char**)grib_context_realloc(c, db->columns[i].string_values,
newsize * sizeof(char*));
if (!newstrings) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_columns_resize : Cannot malloc %d bytes", newsize - db->columns[i].values_array_size);
return GRIB_OUT_OF_MEMORY;
}
else
db->columns[i].string_values = newstrings;
break;
}
newerrors = (int*)grib_context_realloc(c, db->columns[i].errors, newsize * sizeof(int));
if (!newerrors) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_columns_resize : Cannot malloc %d bytes",
db->columns[i].errors, newsize * sizeof(int));
return GRIB_OUT_OF_MEMORY;
}
else
db->columns[i].errors = newerrors;
db->columns[i].values_array_size = newsize;
}
return GRIB_SUCCESS;
}
static int grib_db_column_copy_from_handle(grib_handle* h, grib_db* db, int i)
{
int err = 0;
long lval = 0;
double dval = 0;
char sval[1024];
size_t slen = 1024;
if (!db || !h || db->columns[i].type == 0)
return GRIB_INVALID_ARGUMENT;
if (db->columns[i].size >= db->columns[i].values_array_size)
grib_db_columns_resize(db, db->columns[i].values_array_size + GRIB_ARRAY_INCREMENT);
switch (db->columns[i].type) {
case GRIB_TYPE_LONG:
err = grib_get_long(h, db->columns[i].name, &lval);
db->columns[i].long_values[db->columns[i].size] = lval;
break;
case GRIB_TYPE_DOUBLE:
err = grib_get_double(h, db->columns[i].name, &dval);
db->columns[i].double_values[db->columns[i].size] = dval;
break;
case GRIB_TYPE_STRING:
err = grib_get_string(h, db->columns[i].name, sval, &slen);
db->columns[i].string_values[db->columns[i].size] = strdup(sval);
break;
}
db->columns[i].errors[db->columns[i].size] = err;
db->columns[i].size++;
return err;
}
// grib_db* grib_db_new_from_files(grib_context* c, char* filenames[],
// int nfiles, char** keys, int nkeys, int* err)
// {
// int i = 0;
// int ret = GRIB_SUCCESS;
// grib_db* db = 0;
// if (!c) c = grib_context_get_default();
// if (((!keys || nkeys == 0)) || !filenames) {
// *err = GRIB_INVALID_ARGUMENT;
// return NULL;
// }
// db = grib_db_new_from_file(c, filenames[0], keys, nkeys, err);
// if (!db || *err != GRIB_SUCCESS)
// return db;
// *err = GRIB_SUCCESS;
// for (i = 1; i < nfiles; i++) {
// ret = grib_db_load(db, filenames[i]);
// if (ret != GRIB_SUCCESS)
// *err = ret;
// }
// return db;
// }
// grib_db* grib_db_new_from_file(grib_context* c, char* filename,
// char** keys, int nkeys, int* err)
// {
// int i = 0;
// int ret = GRIB_SUCCESS;
// grib_db* db = 0;
// if (!c)
// c = grib_context_get_default();
// if (((!keys || nkeys == 0)) || !filename) {
// *err = GRIB_INVALID_ARGUMENT;
// return NULL;
// }
// db = grib_db_create_from_keys(c, keys, nkeys, err);
// *err = GRIB_SUCCESS;
// ret = grib_db_load(db, filename);
// if (ret != GRIB_SUCCESS)
// *err = ret;
// return db;
// }
static grib_db* grib_db_create_from_keys(grib_context* c, char** keys, int nkeys, int* err)
{
grib_db* db = 0;
size_t msize = 0, size = 0;
int i = 0;
int type = 0;
int default_type = GRIB_TYPE_DOUBLE;
if (!c)
c = grib_context_get_default();
size = GRIB_START_ARRAY_SIZE;
msize = sizeof(grib_db);
db = (grib_db*)grib_context_malloc(c, msize);
if (!db) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_create : Cannot malloc %d bytes", msize);
return NULL;
}
db->context = c;
db->size = 0;
db->fields_array_size = size;
db->fields = 0;
db->columns = 0;
db->fields = grib_db_create_fields(db->context, size);
db->columns = (grib_column*)grib_context_malloc(c, sizeof(grib_column) * nkeys);
if (!set->columns) {
grib_context_log(c, GRIB_LOG_ERROR, "grib_db_new_query: memory allocation error");
*err = GRIB_OUT_OF_MEMORY;
return NULL;
}
for (i = 0; i < nkeys; i++) {
char* key = strdup(keys[i]);
char* p = key;
while (*p != ':' && *p != '\0')
p++;
if (*p == ':') {
type = grib_type_to_int(*(p + 1));
*p = '\0';
}
else {
type = default_type;
}
*err = grib_db_new_column(db, i, key, type);
free(key);
}
db->columns_size = nkeys;
return db;
}
void grib_db_delete(grib_db* db)
{
grib_context* c = 0;
if (!db)
return;
c = set->context;
grib_db_delete_columns(db);
grib_db_delete_fields(db);
grib_context_free(c, db);
}
grib_query* grib_db_new_query(grib_context* c, const char* where_string,
const char* order_by_string)
{
grib_query* q = 0;
if (!c)
c = grib_context_get_default();
q = (grib_query*)grib_context_malloc(c, sizeof(grib_query));
q->where_string = 0;
q->order_by = 0;
if (where_string)
q->where_string = strdup(where_string);
if (order_by_string) {
q->order_by = grib_db_new_order_by(set->context, (char*)order_by_string);
if ((err = grib_db_set_order_by(set, ob)) != GRIB_SUCCESS)
return q;
}
return q;
}
grib_fieldset* grib_db_execute(grib_db* db, grib_query* query, int* err)
{
grib_fieldset* set = NULL;
if (!db) {
*err = GRIB_INVALID_ARGUMENT;
return NULL;
}
set = grib_db_fieldset_create(db, err);
if (*err != GRIB_SUCCESS)
return set;
set->query = query;
*err = grib_db_apply_where(set, query->where_string);
if (*err != GRIB_SUCCESS)
return set;
*err = grib_db_apply_order_by(set, query->order_by);
if (*err != GRIB_SUCCESS)
return set;
return set;
}
static void grib_fieldset* grib_db_fieldset_create(grib_db* db, int* err)
{
grib_fieldset* set = (grib_fieldset*)grib_context_malloc(db->context,
sizeof(grib_fieldset));
if (!set) {
*err = GRIB_OUT_OF_MEMORY;
return NULL;
}
set->db = db;
set->context = db->context;
set->grib_query = NULL;
set->size = 0;
set->filter = NULL;
set->order = NULL;
return set;
}
int grib_db_apply_where(grib_fieldset* set)
{
int err = GRIB_SUCCESS;
grib_math* m = 0;
if (!set)
return GRIB_INVALID_ARGUMENT;
// m=grib_math_new(set->context,where_string,&err);
// print_math(m);
// printf("\n");
if (set->filter)
grib_db_delete_int_array(set->filter);
set->filter = grib_db_create_int_array(set->context, db->size);
if (set->order)
grib_db_delete_int_array(set->order);
set->order = grib_db_create_int_array(set->context, db->size);
for (i = 0; i < db->size; i++)
set->filter[i] = i;
return err;
}
int grib_db_apply_order_by(grib_fieldset* set)
{
int err = 0;
grib_order_by* ob = NULL;
if (!set || !set->query)
return GRIB_INVALID_ARGUMENT;
if (set->query->order_by)
grib_db_sort(set, 0, set->size - 1);
grib_db_rewind(set);
return err;
}
// static int grib_fieldset_compare(grib_fieldset* set, int* i, int* j)
// {
// int ret = 0;
// double d = 0;
// int idkey = 0;
// grib_order_by* ob = 0;
// int ii = 0, jj = 0;
// int *order = 0, *filter = 0;
// if (!set || !set->order_by)
// return GRIB_INVALID_ARGUMENT;
// ob = set->query->order_by;
// order = set->order->el;
// filter = set->filter->el;
// ii = *(set->filter->el + *(order + *i));
// jj = *(set->filter->el + *(order + *j));
// while (ob) {
// idkey = ob->idkey;
// switch (set->db->columns[idkey].type) {
// case GRIB_TYPE_STRING:
// ret = grib_inline_strcmp(set->db->columns[idkey].string_values[ii],
// set->db->columns[idkey].string_values[jj]);
// break;
// case GRIB_TYPE_DOUBLE:
// d = set->db->columns[idkey].double_values[ii] -
// set->db->columns[idkey].double_values[jj];
// if (d > 0)
// ret = 1;
// else if (d == 0)
// ret = 0;
// else
// ret = -1;
// break;
// case GRIB_TYPE_LONG:
// ret = set->db->columns[idkey].long_values[ii] -
// set->db->columns[idkey].long_values[jj];
// break;
// default:
// return GRIB_INVALID_TYPE;
// }
// if (ret != 0) {
// ret *= ob->mode;
// break;
// }
// ob = ob->next;
// }
// return ret;
// }
static void grib_db_sort(grib_set* set, int beg, int theEnd)
{
double temp;
int l = 0, r = 0;
if (theEnd > beg) {
l = beg + 1;
r = theEnd;
while (l < r) {
if (grib_db_compare(set, &l, &beg) <= 0) {
l++;
}
else if (grib_db_compare(set, &r, &beg) >= 0) {
r--;
}
else {
SWAP(set->order->el[l], set->order->el[r])
}
}
if (grib_db_compare(set, &l, &beg) < 0) {
SWAP(set->order->el[l], set->order->el[beg])
l--;
}
else {
l--;
SWAP(set->order->el[l], set->order->el[beg])
}
grib_db_sort(set, beg, l);
grib_db_sort(set, r, theEnd);
}
}
static void grib_db_delete_order_by(grib_context* c, grib_order_by* order_by)
{
grib_order_by* ob = order_by;
if (!c)
c = grib_context_get_default();
while (order_by) {
if (order_by->key)
free(order_by->key);
ob = order_by;
order_by = order_by->next;
grib_context_free(c, ob);
}
return;
}
static grib_order_by* grib_db_new_order_by(grib_context* c, char* obstr)
{
char *t1 = 0, *t2 = 0, *p = 0;
int id = 0;
char *z = 0, *zs = 0;
char* lasts = NULL;
int mode, mode_default = GRIB_ORDER_BY_ASC;
grib_order_by *ob, *sob;
if (!obstr)
return NULL;
z = strdup(obstr);
zs = z;
grib_trim(&z);
if (strlen(z) == 0) {
return 0;
}
ob = (grib_order_by*)grib_context_malloc(c, sizeof(grib_order_by));
sob = ob;
ob->key = 0;
ob->idkey = 0;
ob->mode = 0;
ob->next = 0;
t1 = strtok_r(z, ",", &lasts);
while (t1) {
grib_trim(&t1);
t2 = strdup(t1);
p = t2;
while (*p != ' ' && *p != '\0')
p++;
mode = mode_default;
if (p != t2) {
while (*p == ' ')
p++;
if (*p != '\0') {
*(p - 1) = '\0';
if (!grib_inline_strcmp(p, "asc"))
mode = GRIB_ORDER_BY_ASC;
if (!grib_inline_strcmp(p, "desc"))
mode = GRIB_ORDER_BY_DESC;
}
grib_trim(&p);
}
grib_trim(&t2);
id = -1;
t1 = strtok_r(NULL, ",", &lasts);
if (ob->key) {
ob->next = (grib_order_by*)grib_context_malloc(c, sizeof(grib_order_by));
ob = ob->next;
ob->key = 0;
ob->next = 0;
}
ob->mode = mode;
ob->key = t2;
ob->idkey = id;
}
free(zs);
return sob;
}
int grib_db_load(grib_db* db, char* filename)
{
int ret = GRIB_SUCCESS;
int err = 0;
int i = 0;
grib_handle* h = 0;
int nkeys;
grib_file* file;
double offset = 0;
long length = 0;
grib_context* c = 0;
if (!db || !filename)
return GRIB_INVALID_ARGUMENT;
c = db->context;
nkeys = db->columns_size;
file = grib_file_open(filename, "r", &err);
if (!file || !file->handle)
return err;
while ((h = grib_handle_new_from_file(c, file->handle, &err)) != NULL || ret != GRIB_SUCCESS) {
if (!h)
return ret;
err = GRIB_SUCCESS;
for (i = 0; i < db->columns_size; i++) {
err = grib_db_column_copy_from_handle(h, db, i);
if (err != GRIB_SUCCESS)
ret = err;
}
if (err == GRIB_SUCCESS || err == GRIB_NOT_FOUND) {
if (db->fields_array_size < db->columns[0].values_array_size) {
ret = grib_db_resize(db, db->columns[0].values_array_size);
if (ret != GRIB_SUCCESS)
return ret;
}
offset = 0;
ret = grib_get_double(h, "offset", &offset);
db->fields[db->size] = (grib_field*)grib_context_malloc(c, sizeof(grib_field));
db->fields[db->size]->file = file;
file->refcount++;
db->fields[db->size]->offset = (off_t)offset;
ret = grib_get_long(h, "totalLength", &length);
db->fields[db->size]->length = length;
db->size = set->columns[0].size;
}
grib_handle_delete(h);
}
if (h)
grib_handle_delete(h);
grib_file_close(file->name, 0, &err);
return ret;
}
static int grib_db_resize(grib_db* db, size_t newsize)
{
int err = 0;
err = grib_db_resize_fields(db, newsize);
if (err != 0)
return err;
set->fields_array_size = newsize;
return GRIB_SUCCESS;
}
void grib_db_rewind(grib_fieldset* set)
{
if (set)
set->current = 0;
}
grib_handle* grib_db_next_handle(grib_fieldset* set, int* err)
{
grib_handle* h;
*err = GRIB_SUCCESS;
h = grib_db_retrieve(set, set->current, err);
if (*err == GRIB_SUCCESS) {
set->current++;
}
return h;
}
int grib_db_count(grib_fieldset* set)
{
return set->size;
}
grib_handle* grib_db_retrieve(grib_fieldset* set, int i, int* err)
{
grib_handle* h = 0;
grib_field* field = 0;
*err = GRIB_SUCCESS;
if (!set) {
*err = GRIB_INVALID_ARGUMENT;
return NULL;
}
if (i >= set->size)
return NULL;
field = set->db->fields[set->filter->el[set->order->el[i]]];
grib_file_open(field->file->name, "r", err);
if (*err != GRIB_SUCCESS)
return NULL;
fseeko(field->file->handle, field->offset, SEEK_SET);
h = grib_handle_new_from_file(set->context, field->file->handle, err);
if (*err != GRIB_SUCCESS)
return NULL;
grib_file_close(field->file->name, 0, err);
return h;
}
static grib_int_array* grib_db_create_int_array(grib_context* c, size_t size)
{
grib_int_array* a;
int i = 0;
if (!c)
c = grib_context_get_default();
a = (grib_int_array*)grib_context_malloc(c, sizeof(grib_int_array));
if (!a) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_create_int_array : Cannot malloc %d bytes",
sizeof(grib_int_array));
return NULL;
}
a->el = (int*)grib_context_malloc(c, sizeof(int) * size);
if (!a->el) {
grib_context_log(c, GRIB_LOG_ERROR,
"grib_db_create_int_array : Cannot malloc %d bytes",
sizeof(int) * size);
return NULL;
}
a->size = size;
a->context = c;
for (i = 0; i < size; i++)
a->el[i] = i;
return a;
}
static int grib_db_resize_int_array(grib_int_array* a, size_t newsize)
{
int* el;
int err = 0;
if (!a)
return GRIB_INVALID_ARGUMENT;
newsize = newsize * sizeof(int);
el = (int*)grib_context_realloc(a->context, a->el, newsize);
if (!el) {
grib_context_log(a->context, GRIB_LOG_ERROR,
"grib_db_resize_int_array : Cannot malloc %d bytes",
newsize);
return GRIB_OUT_OF_MEMORY;
}
else
a->el = el;
a->size = newsize;
return err;
}
static void grib_db_delete_int_array(grib_int_array* f)
{
grib_context* c = NULL;
if (!f)
return;
c = f->context;
grib_context_free(c, f->el);
grib_context_free(c, f);
}
static grib_field** grib_db_create_fields(grib_context* c, size_t size)
{
int i = 0;
grib_field** fields = (grib_field**)grib_context_malloc(c, size * sizeof(grib_field*));
if (!fields)
return NULL;
for (i = 0; i < size; i++)
fields[i] = 0;
return fields;
}
static int grib_db_resize_fields(grib_db* db, size_t newsize)
{
int err = 0;
int i;
grib_field** fields;
if (!db)
return GRIB_INVALID_ARGUMENT;
fields = (grib_field**)grib_context_realloc(set->context, set->fields, newsize * sizeof(grib_field*));
if (!fields) {
grib_context_log(set->context, GRIB_LOG_ERROR,
"grib_db_resize_fields : Cannot malloc %d bytes",
newsize * sizeof(grib_field*));
return GRIB_OUT_OF_MEMORY;
}
else
db->fields = fields;
for (i = set->fields_array_size; i < newsize; i++)
db->fields[i] = 0;
db->fields_array_size = newsize;
return err;
}
static void grib_db_delete_fields(grib_fieldset* set)
{
int i;
for (i = 0; i < set->size; i++) {
if (!set->fields[i])
continue;
set->fields[i]->file->refcount--;
grib_context_free(set->context, set->fields[i]);
}
grib_context_free(set->context, set->fields);
}
static void grib_trim(char** x)
{
char* p = 0;
while (**x == ' ')
(*x)++;
if (**x == '\0')
return;
p = (*x) + strlen(*x) - 1;
while (*p == ' ') {
*p = '\0';
p--;
}
if (*p == ' ')
*p = '\0';
}
static int grib_db_set_order_by(grib_fieldset* set, grib_order_by* ob)
{
grib_order_by* next = ob;
int i = 0;
while (next) {
next->idkey = -1;
for (i = 0; i < set->columns_size; i++) {
if (!grib_inline_strcmp(next->key, set->columns[i].name)) {
next->idkey = i;
break;
}
}
if (next->idkey == -1) {
grib_context_log(set->context, GRIB_LOG_ERROR,
"Unable to apply the order by. Key missing from the fieldset.\n");
return GRIB_MISSING_KEY;
}
next = next->next;
}
set->order_by = ob;
return GRIB_SUCCESS;
}