Code

sysdb.h: Added helpers to compare two values.
[sysdb.git] / src / core / data.c
index 1bb5400028223c89eca2f9a10e7ee3e81cc5cb03..2381636092ceb03b7d4ef25ae8c9935ec3300583 100644 (file)
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#if HAVE_CONFIG_H
+#      include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "sysdb.h"
+
 #include "core/data.h"
+#include "utils/error.h"
+
+#include <errno.h>
 
 #include <inttypes.h>
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -60,6 +70,7 @@ sdb_data_copy(sdb_data_t *dst, const sdb_data_t *src)
                        break;
        }
 
+       sdb_data_free_datum(dst);
        *dst = tmp;
        return 0;
 } /* sdb_data_copy */
@@ -86,48 +97,222 @@ sdb_data_free_datum(sdb_data_t *datum)
 } /* sdb_data_free_datum */
 
 int
-sdb_data_format(sdb_data_t *datum, sdb_strbuf_t *buf)
+sdb_data_cmp(const sdb_data_t *d1, const sdb_data_t *d2)
+{
+#define CMP_NULL(a, b) \
+       do { \
+               if (!(a) && !(b)) return 0; \
+               if (!(a)) return -1; \
+               if (!(b)) return 1; \
+       } while (0)
+
+       CMP_NULL(d1, d2);
+
+       if (d1->type != d2->type)
+               return -1;
+
+       switch (d1->type) {
+               case SDB_TYPE_INTEGER:
+                       return SDB_CMP(d1->data.integer, d2->data.integer);
+               case SDB_TYPE_DECIMAL:
+                       return SDB_CMP(d1->data.decimal, d2->data.decimal);
+               case SDB_TYPE_STRING:
+                       CMP_NULL(d1->data.string, d2->data.string);
+                       return strcasecmp(d1->data.string, d2->data.string);
+               case SDB_TYPE_DATETIME:
+                       return SDB_CMP(d1->data.datetime, d2->data.datetime);
+               case SDB_TYPE_BINARY:
+               {
+                       int diff;
+
+                       CMP_NULL(d1->data.binary.datum, d2->data.binary.datum);
+
+                       /* on a common prefix, the shorter datum sorts less */
+                       if (d1->data.binary.length < d2->data.binary.length) {
+                               diff = memcmp(d1->data.binary.datum, d2->data.binary.datum,
+                                               d1->data.binary.length);
+                               diff = diff ? diff : -1;
+                       }
+                       else if (d1->data.binary.length > d2->data.binary.length) {
+                               diff = memcmp(d1->data.binary.datum, d2->data.binary.datum,
+                                               d2->data.binary.length);
+                               diff = diff ? diff : 1;
+                       }
+                       else
+                               diff = memcmp(d1->data.binary.datum, d2->data.binary.datum,
+                                               d1->data.binary.length);
+
+                       return diff;
+               }
+               default:
+                       return -1;
+       }
+#undef CMP_NULL
+} /* sdb_data_cmp */
+
+size_t
+sdb_data_strlen(const sdb_data_t *datum)
+{
+       if (! datum)
+               return 0;
+
+       switch (datum->type) {
+               case SDB_TYPE_INTEGER:
+                       /* log(64) */
+                       return 20;
+               case SDB_TYPE_DECIMAL:
+                       /* XXX: -0xN.NNNNNNp+NNN */
+                       return 42;
+               case SDB_TYPE_STRING:
+                       if (! datum->data.string)
+                               return 6; /* "NULL" */
+                       /* in the worst case, each character needs to be escaped */
+                       return 2 * strlen(datum->data.string) + 2;
+               case SDB_TYPE_DATETIME:
+                       /* "YYYY-MM-DD HH:MM:SS +zzzz" */
+                       return 27;
+               case SDB_TYPE_BINARY:
+                       /* "\xNN" */
+                       return 4 * datum->data.binary.length + 2;
+       }
+       return 0;
+} /* sdb_data_strlen */
+
+int
+sdb_data_format(const sdb_data_t *datum, char *buf, size_t buflen, int quoted)
 {
+       char tmp[sdb_data_strlen(datum) + 1];
+       char *data = NULL;
+       int ret = -1;
+
+       size_t i, pos;
+
        if ((! datum) || (! buf))
                return -1;
 
        switch (datum->type) {
                case SDB_TYPE_INTEGER:
-                       sdb_strbuf_append(buf, "%"PRIi64, datum->data.integer);
+                       ret = snprintf(buf, buflen, "%"PRIi64, datum->data.integer);
                        break;
                case SDB_TYPE_DECIMAL:
-                       sdb_strbuf_append(buf, "%a", datum->data.decimal);
+                       ret = snprintf(buf, buflen, "%a", datum->data.decimal);
                        break;
                case SDB_TYPE_STRING:
-                       /* TODO: escape special characters */
-                       sdb_strbuf_append(buf, "\"%s\"", datum->data.string);
+                       if (! datum->data.string)
+                               data = "NULL";
+                       else {
+                               pos = 0;
+                               for (i = 0; i < strlen(datum->data.string); ++i) {
+                                       char byte = datum->data.string[i];
+
+                                       if ((byte == '\\') || (byte == '"')) {
+                                               tmp[pos] = '\\';
+                                               ++pos;
+                                       }
+                                       tmp[pos] = byte;
+                                       ++pos;
+                               }
+                               tmp[pos] = '\0';
+                               data = tmp;
+                       }
                        break;
                case SDB_TYPE_DATETIME:
-                       {
-                               char tmp[64];
-                               if (! sdb_strftime(tmp, sizeof(tmp), "%F %T %z",
-                                                       datum->data.datetime))
-                                       return -1;
-                               tmp[sizeof(tmp) - 1] = '\0';
-                               sdb_strbuf_append(buf, "%s", tmp);
-                       }
+                       if (! sdb_strftime(tmp, sizeof(tmp), "%F %T %z",
+                                               datum->data.datetime))
+                               return -1;
+                       tmp[sizeof(tmp) - 1] = '\0';
+                       data = tmp;
                        break;
                case SDB_TYPE_BINARY:
+                       pos = 0;
+                       for (i = 0; i < datum->data.binary.length; ++i) {
+                               int byte = (int)datum->data.binary.datum[i];
+                               char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+                                       '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+                               tmp[pos] = '\\';
+                               tmp[pos + 1] = 'x';
+                               pos += 2;
+
+                               if (byte > 0xf) {
+                                       tmp[pos] = hex[byte >> 4];
+                                       ++pos;
+                               }
+                               tmp[pos] = hex[byte & 0xf];
+                               ++pos;
+                       }
+                       tmp[pos] = '\0';
+                       data = tmp;
+                       break;
+       }
+
+       if (data) {
+               if (quoted == SDB_UNQUOTED)
+                       ret = snprintf(buf, buflen, "%s", data);
+               else if (quoted == SDB_SINGLE_QUOTED)
+                       ret = snprintf(buf, buflen, "'%s'", data);
+               else
+                       ret = snprintf(buf, buflen, "\"%s\"", data);
+       }
+       buf[buflen - 1] = '\0';
+       return ret;
+} /* sdb_data_format */
+
+int
+sdb_data_parse(char *str, int type, sdb_data_t *data)
+{
+       sdb_data_t tmp;
+
+       char *endptr = NULL;
+
+       errno = 0;
+       switch (type) {
+               case SDB_TYPE_INTEGER:
+                       tmp.data.integer = strtoll(str, &endptr, 0);
+                       break;
+               case SDB_TYPE_DECIMAL:
+                       tmp.data.decimal = strtod(str, &endptr);
+                       break;
+               case SDB_TYPE_STRING:
+                       tmp.data.string = str;
+                       break;
+               case SDB_TYPE_DATETIME:
                        {
-                               size_t i;
-                               /* TODO: improve this! */
-                               sdb_strbuf_append(buf, "%s", "\"");
-                               for (i = 0; i < datum->data.binary.length; ++i)
-                                       sdb_strbuf_append(buf, "\\%x",
-                                                       (int)datum->data.binary.datum[i]);
-                               sdb_strbuf_append(buf, "%s", "\"");
+                               double datetime = strtod(str, &endptr);
+                               tmp.data.datetime = DOUBLE_TO_SDB_TIME(datetime);
                        }
                        break;
+               case SDB_TYPE_BINARY:
+                       /* we don't support any binary information containing 0-bytes */
+                       tmp.data.binary.length = strlen(str);
+                       tmp.data.binary.datum = (unsigned char *)str;
+                       break;
                default:
+                       errno = EINVAL;
+                       return -1;
+       }
+
+       if ((type == SDB_TYPE_INTEGER) || (type == SDB_TYPE_DECIMAL)
+                       || (type == SDB_TYPE_DATETIME)) {
+               if (errno || (str == endptr)) {
+                       char errbuf[1024];
+                       sdb_log(SDB_LOG_ERR, "core: Failed to parse string "
+                                       "'%s' as numeric value (type %i): %s", str, type,
+                                       sdb_strerror(errno, errbuf, sizeof(errbuf)));
                        return -1;
+               }
+               else if (endptr && (*endptr != '\0'))
+                       sdb_log(SDB_LOG_WARNING, "core: Ignoring garbage after "
+                                       "number while parsing numeric value (type %i): %s.",
+                                       type, endptr);
+       }
+
+       if (data) {
+               *data = tmp;
+               data->type = type;
        }
        return 0;
-} /* sdb_data_format */
+} /* sdb_data_parse */
 
 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */