sqlite: Implement storing objects 34/167134/11
authorPaweł Szewczyk <p.szewczyk@samsung.com>
Mon, 15 Jan 2018 15:55:53 +0000 (16:55 +0100)
committerPaweł Szewczyk <p.szewczyk@samsung.com>
Fri, 16 Feb 2018 16:05:22 +0000 (17:05 +0100)
This commit introduces the form of object signatures. They have similar
structure to json.

Note that the singature building function is re-purposed for building
other object-specific strings, including sql queries.

Change-Id: I9b268fdb46380fa311abc304116808729e5e1b06
Signed-off-by: Paweł Szewczyk <p.szewczyk@samsung.com>
src/database/sqlite.c

index 6c43c31d999a76231aa88afe929028be1c5aee40..e6f8648228cc205e6d68d05d1117f42c9f0575c2 100644 (file)
@@ -28,6 +28,7 @@
 #define META_TABLE "meta"
 #define OBJ_TABLE_PREFIX "objects"
 #define ID_KEY "id"
+#define BUFSIZE 512
 
 enum well_known_oids {
        WK_OID_INVALID = 0,
@@ -37,11 +38,15 @@ enum well_known_oids {
 
 enum prepared_stmts {
        STMT_GET_ALL_TABLES = 0,
+       STMT_FIND_TABLE,
+       STMT_INSERT_TABLE,
        STMT_NUM,
 };
 
 const char *prepared_stmts_str[STMT_NUM] = {
        [STMT_GET_ALL_TABLES] = "SELECT ('" OBJ_TABLE_PREFIX "'||id), signature FROM " META_TABLE ";",
+       [STMT_FIND_TABLE] = "SELECT ('" OBJ_TABLE_PREFIX "'||id) FROM " META_TABLE " WHERE signature = ?;",
+       [STMT_INSERT_TABLE] = "INSERT INTO " META_TABLE " (signature) VALUES (?);",
 };
 
 struct sqlite_adapter {
@@ -115,9 +120,334 @@ close_db:
        return ret;
 }
 
+static char type_id_map[] = {
+       [TYPE_OBJECT] = 'O',
+       [TYPE_STRING] = 's',
+       [TYPE_OID] = 'o',
+       [TYPE_INT] = 'i',
+       [TYPE_TIMESPEC] = 'T',
+       [TYPE_TIME_T] = 't',
+       [TYPE_UUID] = 'u',
+};
+
+static const char *type_sql_map[] = {
+       [TYPE_OBJECT] = "NULL",
+       [TYPE_STRING] = "TEXT",
+       [TYPE_OID] = "INTEGER",
+       [TYPE_INT] = "INTEGER",
+       [TYPE_TIMESPEC] = "BLOB",
+       [TYPE_TIME_T] = "INTEGER",
+       [TYPE_UUID] = "BLOB",
+};
+
+enum signature_type {
+       SIG_KEY, /**< Signature stored in database */
+       SIG_CREATE_FIELDS, /**< Fields for create query */
+       SIG_INSERT_FIELDS, /**< Fields for insert query */
+       SIG_INSERT_VALUES, /**< Values for insert query */
+};
+
+static int _get_object_signature(struct faultd_object *obj, char *buffer, size_t size, int offset, int type, const char *key_prefix)
+{
+       struct faultd_object *child;
+       int ret = 0;
+       int noff = offset;
+       char *prefix = NULL;
+
+       if (obj->key && strcmp(obj->key, ID_KEY) == 0
+                       && type != SIG_INSERT_VALUES && type != SIG_INSERT_FIELDS)
+               return 0;
+
+       if (obj->type == TYPE_OBJECT) {
+               if (type == SIG_KEY) {
+                       ret = snprintf(buffer + noff, size - noff, "%s:{", (obj->key ? obj->key : ""));
+                       if (ret < 0 || ret >= (int)size - noff)
+                               return -ENOMEM;
+
+                       noff += ret;
+               }
+
+               if (obj->key)
+                       ret = asprintf(&prefix, "%s%s.", (key_prefix ? key_prefix : ""), obj->key);
+               else
+                       prefix = strdup(key_prefix);
+               if (ret < 0 || !prefix)
+                       return -ENOMEM;
+
+               list_for_each_entry(child, &obj->val.children, node) {
+                       ret = _get_object_signature(child, buffer, size, noff, type, prefix);
+                       if (ret < 0)
+                               goto out;
+
+                       noff += ret;
+               }
+
+               free(prefix);
+               prefix = NULL;
+
+               if (type == SIG_KEY) {
+                       if (buffer[noff - 1] == ',')
+                               noff--;
+
+                       ret = snprintf(buffer + noff, size - noff, "},");
+                       if (ret < 0 || ret >= (int)size - noff)
+                               return -ENOMEM;
+
+                       noff += ret;
+               }
+
+               ret = noff - offset;
+               if (ret < 0)
+                       return 0;
+
+               return ret;
+       }
+
+       switch (type) {
+       case SIG_KEY:
+               if (obj->key && size < offset + strlen(obj->key) + 3)
+                       return -ENOMEM;
+
+               ret = snprintf(buffer + offset, size - offset,
+                               "%s:%c,", (obj->key ? obj->key : ""), type_id_map[obj->type]);
+               break;
+       case SIG_CREATE_FIELDS:
+               ret = snprintf(buffer + offset, size - offset,
+                               "`%s%s` %s,", key_prefix, obj->key, type_sql_map[obj->type]);
+               break;
+       case SIG_INSERT_FIELDS:
+               ret = snprintf(buffer + offset, size - offset,
+                               "`%s%s`,", key_prefix, obj->key);
+               break;
+       case SIG_INSERT_VALUES:
+               ret = snprintf(buffer + offset, size - offset, "?,");
+               break;
+       }
+
+       if (ret < 0 || ret >= (int)size - offset)
+               ret = -ENOMEM;
+
+out:
+       free(prefix);
+       return ret;
+}
+
+static inline int get_object_signature(struct faultd_object *obj, char *buffer, int size, int type)
+{
+       int ret;
+       int off = 0;
+
+       buffer[0] = '\0';
+       if (type == SIG_CREATE_FIELDS) {
+               ret = snprintf(buffer, size, "id INTEGER PRIMARY KEY,");
+               if (ret < 0 || ret >= size)
+                       return -ENOMEM;
+
+               off = ret;
+       }
+
+       ret = _get_object_signature(obj, buffer, size, off, type, "");
+       if (ret < 0)
+               return ret;
+
+       if (off + ret > 0)
+               buffer[off + ret - 1] = '\0';
+       return 0;
+}
+
+static int get_table_match(struct faultd_database_adapter *adapter, struct faultd_object *obj, char *signature, char **name, int *id)
+{
+       sqlite3_stmt *query;
+       int ret;
+       char *nname;
+       struct sqlite_adapter *da = to_sqlite_adapter(adapter);
+       char query_str[BUFSIZE];
+       char buf[BUFSIZE];
+       char *sqlite_err;
+       int _id;
+
+       query = da->stmts[STMT_FIND_TABLE];
+       sqlite3_reset(query);
+       sqlite3_bind_text(query, 1, signature, -1, SQLITE_TRANSIENT);
+       log_debug("query: %s", sqlite3_expanded_sql(query));
+       ret = sqlite3_step(query);
+       if (ret == SQLITE_ROW) {
+               log_debug("found matching row: %s", sqlite3_column_text(query, 0));
+               *name = strdup((const char *)sqlite3_column_text(query, 0));
+               *id = sqlite3_column_int(query, 1);
+               return 0;
+       }
+
+       log_debug("Did not found matching table. Creating new table.");
+
+       query = da->stmts[STMT_INSERT_TABLE];
+       sqlite3_reset(query);
+       sqlite3_bind_text(query, 1, signature, -1, SQLITE_TRANSIENT);
+       log_debug("query: %s", sqlite3_expanded_sql(query));
+       ret = sqlite3_step(query);
+       if (ret == SQLITE_ERROR) {
+               log_error("SQL Error: %s", sqlite3_errmsg(da->db));
+               return ret;
+       }
+
+       _id = sqlite3_last_insert_rowid(da->db);
+       ret = asprintf(&nname, OBJ_TABLE_PREFIX "%d", _id);
+       if (ret < 0)
+               return -ENOMEM;
+
+       get_object_signature(obj, buf, BUFSIZE, SIG_CREATE_FIELDS);
+       ret = snprintf(query_str, BUFSIZE, "CREATE TABLE %s (%s);", nname, buf);
+       if (ret < 0 || ret >= BUFSIZE)
+               goto free_name;
+
+       log_debug("creating table:\n%s", query_str);
+       ret = sqlite3_exec(da->db, query_str, NULL, 0, &sqlite_err);
+       if (ret != SQLITE_OK) {
+               log_error("SQL Error: %s", sqlite3_errmsg(da->db));
+               goto free_name;
+       }
+
+       ret = snprintf(query_str, BUFSIZE, "CREATE UNIQUE INDEX %s_oid_idx on %s (%s);", nname, nname, ID_KEY);
+       if (ret < 0 || ret >= BUFSIZE)
+               goto free_name;
+
+       ret = sqlite3_exec(da->db, query_str, NULL, 0, &sqlite_err);
+       if (ret != SQLITE_OK) {
+               log_error("SQL Error: %s", sqlite3_errmsg(da->db));
+               goto free_name;
+       }
+
+       *name = nname;
+       if (id)
+               *id = _id;
+
+       return 0;
+
+free_name:
+       free(nname);
+       return ret;
+}
+
+static inline uint64_t pack_oid(faultd_oid_t *oid)
+{
+       return (uint64_t)oid->sqlite.table << 32 | oid->sqlite.obj;
+}
+
+static int bind_values(struct faultd_object *obj, sqlite3_stmt *query, int n)
+{
+       struct faultd_object *child;
+       int ret;
+       int nn;
+       uint64_t packed;
+
+       switch (obj->type) {
+       case TYPE_OBJECT:
+               nn = n;
+               list_for_each_entry(child, &obj->val.children, node) {
+                       ret = bind_values(child, query, nn);
+                       if (ret < 0)
+                               return ret;
+
+                       nn += ret;
+               }
+               ret =  nn - n;
+               break;
+       case TYPE_STRING:
+               ret = sqlite3_bind_text(query, n, obj->val.s, -1, SQLITE_TRANSIENT);
+               ret = (ret == SQLITE_OK ? 1 : -ret);
+               break;
+       case TYPE_OID:
+               packed = pack_oid(&obj->val.oid);
+               ret = sqlite3_bind_int64(query, n, packed);
+               ret = (ret == SQLITE_OK ? 1 : -ret);
+               break;
+       case TYPE_INT:
+               ret = sqlite3_bind_int(query, n, obj->val.i);
+               ret = (ret == SQLITE_OK ? 1 : -ret);
+               break;
+       case TYPE_TIMESPEC:
+               ret = sqlite3_bind_blob(query, n, (const void *)&obj->val.ts, sizeof(obj->val.ts), SQLITE_TRANSIENT);
+               ret = (ret == SQLITE_OK ? 1 : -ret);
+               break;
+       case TYPE_TIME_T:
+               ret = sqlite3_bind_int(query, n, obj->val.time);
+               ret = (ret == SQLITE_OK ? 1 : -ret);
+               break;
+       case TYPE_UUID:
+               ret = sqlite3_bind_blob(query, n, (const void *)&obj->val.uuid, sizeof(obj->val.uuid), SQLITE_TRANSIENT);
+               ret = (ret == SQLITE_OK ? 1 : -ret);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
 static int sqlite_store(struct faultd_database_adapter *adapter, struct faultd_object *obj, faultd_oid_t *oid)
 {
+       sqlite3_stmt *query;
+       struct sqlite_adapter *da = to_sqlite_adapter(adapter);
+       char fields_str[BUFSIZE];
+       char values_str[BUFSIZE];
+       char query_str[BUFSIZE];
+       char *name = NULL;
+       int ret;
+       int table_id = 0;
+
+       get_object_signature(obj, fields_str, BUFSIZE, SIG_KEY);
+       ret = get_table_match(adapter, obj, fields_str, &name, &table_id);
+       if (ret < 0)
+               return ret;
+
+       log_debug("storing: %s in %s", fields_str, name);
+       ret = get_object_signature(obj, fields_str, BUFSIZE, SIG_INSERT_FIELDS);
+       if (ret < 0) {
+               log_error("Could not create query (signature: %s)", fields_str);
+               goto err;
+       }
+
+       ret = get_object_signature(obj, values_str, BUFSIZE, SIG_INSERT_VALUES);
+       if (ret < 0) {
+               log_error("Could not create query (signature: %s)", values_str);
+               goto err;
+       }
+
+       ret = snprintf(query_str, BUFSIZE, "INSERT OR REPLACE INTO %s (%s) VALUES (%s);", name, fields_str, values_str);
+       if (ret < 0 || ret >= BUFSIZE)
+               goto err;
+
+       ret = sqlite3_prepare_v2(da->db, query_str, -1, &query, NULL);
+       if (ret != SQLITE_OK) {
+               log_error("SQL Error: %s", sqlite3_errmsg(da->db));
+               goto err;
+       }
+
+       ret = bind_values(obj, query, 1);
+       if (ret < 0) {
+               log_error("Could not bind values to sql query");
+               return ret;
+       }
+
+       log_debug("query: %s", sqlite3_expanded_sql(query));
+       ret = sqlite3_step(query);
+       if (ret == SQLITE_ERROR) {
+               log_error("SQL Error: %s", sqlite3_errmsg(da->db));
+               ret = -1;
+               goto err;
+       }
+
+       if (oid) {
+               oid->sqlite.table = table_id;
+               oid->sqlite.obj = sqlite3_last_insert_rowid(da->db);
+       }
+
        return 0;
+err:
+
+       free(name);
+       return ret;
 }
 
 static int sqlite_get_by_oid(struct faultd_database_adapter *adapter, faultd_oid_t *oid, struct faultd_object *result)