Bug fixing
This commit is contained in:
236
src/database.cpp
236
src/database.cpp
@@ -183,25 +183,46 @@ bool Database::remove_by_hash(const std::string& hash) {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database::get(const std::string& hash, dbEntry& entry) {
|
bool Database::get(const std::string& key, dbEntry& entry) {
|
||||||
std::string sql = "SELECT labels, tags, metadata FROM objects WHERE hash = ?;";
|
std::string sql;
|
||||||
sqlite3_stmt* stmt;
|
if (key.find(':') != std::string::npos) {
|
||||||
|
// Query by label:tag
|
||||||
|
sql = "SELECT hash, labels, tags, metadata FROM objects WHERE labels LIKE ? AND tags LIKE ?;";
|
||||||
|
} else {
|
||||||
|
// Query by hash
|
||||||
|
sql = "SELECT hash, labels, tags, metadata FROM objects WHERE hash = ?;";
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
|
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_bind_text(stmt, 1, hash.c_str(), -1, SQLITE_STATIC);
|
if (key.find(':') != std::string::npos) {
|
||||||
|
// Split label:tag
|
||||||
|
size_t pos = key.find(':');
|
||||||
|
std::string label = key.substr(0, pos);
|
||||||
|
std::string tag = key.substr(pos + 1);
|
||||||
|
|
||||||
|
// Create JSON array patterns for LIKE query
|
||||||
|
std::string label_pattern = "%\"" + label + "\"%";
|
||||||
|
std::string tag_pattern = "%\"" + tag + "\"%";
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, label_pattern.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 2, tag_pattern.c_str(), -1, SQLITE_STATIC);
|
||||||
|
} else {
|
||||||
|
sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_STATIC);
|
||||||
|
}
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_ROW) {
|
if (sqlite3_step(stmt) != SQLITE_ROW) {
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.hash = hash;
|
entry.hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
|
||||||
std::string labels_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
|
std::string labels_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
|
||||||
std::string tags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
|
std::string tags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
|
||||||
std::string metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
|
std::string metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
|
||||||
|
|
||||||
entry.labels = nlohmann::json::parse(labels_str).get<std::vector<std::string>>();
|
entry.labels = nlohmann::json::parse(labels_str).get<std::vector<std::string>>();
|
||||||
entry.tags = nlohmann::json::parse(tags_str).get<std::vector<std::string>>();
|
entry.tags = nlohmann::json::parse(tags_str).get<std::vector<std::string>>();
|
||||||
@@ -237,69 +258,168 @@ bool Database::list(std::vector<dbEntry>& entries) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Database::merge_existing_entry(const dbEntry& existing, const dbEntry& new_entry, dbEntry& merged) {
|
||||||
|
// Merge labels and tags
|
||||||
|
std::set<std::string> merged_labels(existing.labels.begin(), existing.labels.end());
|
||||||
|
merged_labels.insert(new_entry.labels.begin(), new_entry.labels.end());
|
||||||
|
std::set<std::string> merged_tags(existing.tags.begin(), existing.tags.end());
|
||||||
|
merged_tags.insert(new_entry.tags.begin(), new_entry.tags.end());
|
||||||
|
|
||||||
|
// Create merged entry
|
||||||
|
merged = new_entry; // Start with new entry's data
|
||||||
|
merged.labels = std::vector<std::string>(merged_labels.begin(), merged_labels.end());
|
||||||
|
merged.tags = std::vector<std::string>(merged_tags.begin(), merged_tags.end());
|
||||||
|
|
||||||
|
// Update metadata - preserve fields from existing entry that aren't in new entry
|
||||||
|
merged.metadata = existing.metadata; // Start with existing metadata
|
||||||
|
for (const auto& [key, value] : new_entry.metadata.items()) {
|
||||||
|
merged.metadata[key] = value; // Override with new values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure required fields are set correctly
|
||||||
|
merged.metadata["labels"] = merged.labels;
|
||||||
|
merged.metadata["tags"] = merged.tags;
|
||||||
|
merged.metadata["hash"] = merged.hash;
|
||||||
|
|
||||||
|
// Update database
|
||||||
|
std::string sql = "UPDATE objects SET labels = ?, tags = ?, metadata = ? WHERE hash = ?;";
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
|
||||||
|
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string labels_str = nlohmann::json(merged.labels).dump();
|
||||||
|
std::string tags_str = nlohmann::json(merged.tags).dump();
|
||||||
|
std::string metadata_str = merged.metadata.dump();
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, labels_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 2, tags_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 3, metadata_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 4, merged.hash.c_str(), -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
bool success = sqlite3_step(stmt) == SQLITE_DONE;
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::insert_new_entry(const dbEntry& entry) {
|
||||||
|
std::string sql = "INSERT INTO objects (hash, labels, tags, metadata) VALUES (?, ?, ?, ?);";
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
|
||||||
|
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update metadata to include labels, tags, and hash
|
||||||
|
nlohmann::json metadata = entry.metadata;
|
||||||
|
metadata["labels"] = entry.labels;
|
||||||
|
metadata["tags"] = entry.tags;
|
||||||
|
metadata["hash"] = entry.hash;
|
||||||
|
|
||||||
|
std::string labels_str = nlohmann::json(entry.labels).dump();
|
||||||
|
std::string tags_str = nlohmann::json(entry.tags).dump();
|
||||||
|
std::string metadata_str = metadata.dump();
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, entry.hash.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 2, labels_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 3, tags_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 4, metadata_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
bool success = sqlite3_step(stmt) == SQLITE_DONE;
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::handle_tag_conflicts(const dbEntry& entry) {
|
||||||
|
for (const auto& label : entry.labels) {
|
||||||
|
// Find all entries with this label
|
||||||
|
std::string find_sql = "SELECT hash, labels, tags, metadata FROM objects WHERE labels LIKE ?;";
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
if (sqlite3_prepare_v2(db_, find_sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label_pattern = "%\"" + label + "\"%";
|
||||||
|
sqlite3_bind_text(stmt, 1, label_pattern.c_str(), -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
|
std::string other_hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
|
||||||
|
if (other_hash == entry.hash) continue; // Skip our own entry
|
||||||
|
|
||||||
|
std::string other_labels_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
|
||||||
|
std::string other_tags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
|
||||||
|
std::string other_metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
|
||||||
|
|
||||||
|
// Parse the other entry
|
||||||
|
dbEntry other;
|
||||||
|
other.hash = other_hash;
|
||||||
|
other.labels = nlohmann::json::parse(other_labels_str).get<std::vector<std::string>>();
|
||||||
|
other.tags = nlohmann::json::parse(other_tags_str).get<std::vector<std::string>>();
|
||||||
|
other.metadata = nlohmann::json::parse(other_metadata_str);
|
||||||
|
|
||||||
|
// Remove any tags that are in our entry
|
||||||
|
std::vector<std::string> new_tags;
|
||||||
|
for (const auto& tag : other.tags) {
|
||||||
|
if (std::find(entry.tags.begin(), entry.tags.end(), tag) == entry.tags.end()) {
|
||||||
|
new_tags.push_back(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the other entry if it had any tags removed
|
||||||
|
if (new_tags.size() != other.tags.size()) {
|
||||||
|
other.tags = new_tags;
|
||||||
|
other.metadata["tags"] = new_tags; // Update metadata to match
|
||||||
|
|
||||||
|
std::string update_sql = "UPDATE objects SET tags = ?, metadata = ? WHERE hash = ?;";
|
||||||
|
sqlite3_stmt* update_stmt;
|
||||||
|
if (sqlite3_prepare_v2(db_, update_sql.c_str(), -1, &update_stmt, nullptr) != SQLITE_OK) {
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string new_tags_str = nlohmann::json(new_tags).dump();
|
||||||
|
std::string new_metadata_str = other.metadata.dump();
|
||||||
|
|
||||||
|
sqlite3_bind_text(update_stmt, 1, new_tags_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(update_stmt, 2, new_metadata_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(update_stmt, 3, other.hash.c_str(), -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
bool update_success = sqlite3_step(update_stmt) == SQLITE_DONE;
|
||||||
|
sqlite3_finalize(update_stmt);
|
||||||
|
if (!update_success) {
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Database::update_or_insert(const dbEntry& entry) {
|
bool Database::update_or_insert(const dbEntry& entry) {
|
||||||
// First try to get existing entry
|
// First try to get existing entry by hash
|
||||||
dbEntry existing;
|
dbEntry existing;
|
||||||
bool exists = get(entry.hash, existing);
|
bool exists = get(entry.hash, existing);
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
// Merge labels and tags
|
// Merge with existing entry
|
||||||
std::set<std::string> merged_labels(existing.labels.begin(), existing.labels.end());
|
dbEntry merged;
|
||||||
merged_labels.insert(entry.labels.begin(), entry.labels.end());
|
if (!merge_existing_entry(existing, entry, merged)) {
|
||||||
std::set<std::string> merged_tags(existing.tags.begin(), existing.tags.end());
|
|
||||||
merged_tags.insert(entry.tags.begin(), entry.tags.end());
|
|
||||||
|
|
||||||
// Create new entry with merged data
|
|
||||||
dbEntry merged = entry;
|
|
||||||
merged.labels = std::vector<std::string>(merged_labels.begin(), merged_labels.end());
|
|
||||||
merged.tags = std::vector<std::string>(merged_tags.begin(), merged_tags.end());
|
|
||||||
|
|
||||||
// Merge metadata
|
|
||||||
for (const auto& [key, value] : entry.metadata.items()) {
|
|
||||||
merged.metadata[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update database
|
|
||||||
std::string sql = "UPDATE objects SET labels = ?, tags = ?, metadata = ? WHERE hash = ?;";
|
|
||||||
sqlite3_stmt* stmt;
|
|
||||||
|
|
||||||
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string labels_str = nlohmann::json(merged.labels).dump();
|
|
||||||
std::string tags_str = nlohmann::json(merged.tags).dump();
|
|
||||||
std::string metadata_str = merged.metadata.dump();
|
|
||||||
|
|
||||||
sqlite3_bind_text(stmt, 1, labels_str.c_str(), -1, SQLITE_STATIC);
|
// Handle tag conflicts
|
||||||
sqlite3_bind_text(stmt, 2, tags_str.c_str(), -1, SQLITE_STATIC);
|
return handle_tag_conflicts(merged);
|
||||||
sqlite3_bind_text(stmt, 3, metadata_str.c_str(), -1, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_text(stmt, 4, merged.hash.c_str(), -1, SQLITE_STATIC);
|
|
||||||
|
|
||||||
bool success = sqlite3_step(stmt) == SQLITE_DONE;
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
return success;
|
|
||||||
} else {
|
} else {
|
||||||
// Insert new entry
|
// Insert new entry
|
||||||
std::string sql = "INSERT INTO objects (hash, labels, tags, metadata) VALUES (?, ?, ?, ?);";
|
if (!insert_new_entry(entry)) {
|
||||||
sqlite3_stmt* stmt;
|
|
||||||
|
|
||||||
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string labels_str = nlohmann::json(entry.labels).dump();
|
|
||||||
std::string tags_str = nlohmann::json(entry.tags).dump();
|
|
||||||
std::string metadata_str = entry.metadata.dump();
|
|
||||||
|
|
||||||
sqlite3_bind_text(stmt, 1, entry.hash.c_str(), -1, SQLITE_STATIC);
|
// Handle tag conflicts
|
||||||
sqlite3_bind_text(stmt, 2, labels_str.c_str(), -1, SQLITE_STATIC);
|
return handle_tag_conflicts(entry);
|
||||||
sqlite3_bind_text(stmt, 3, tags_str.c_str(), -1, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_text(stmt, 4, metadata_str.c_str(), -1, SQLITE_STATIC);
|
|
||||||
|
|
||||||
bool success = sqlite3_step(stmt) == SQLITE_DONE;
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,7 +19,7 @@ class dbEntry {
|
|||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
public:
|
public:
|
||||||
static const int CURRENT_VERSION = 2;
|
static const int CURRENT_VERSION = 3;
|
||||||
|
|
||||||
Database(const std::filesystem::path& path);
|
Database(const std::filesystem::path& path);
|
||||||
~Database();
|
~Database();
|
||||||
@@ -37,6 +37,11 @@ class Database {
|
|||||||
bool setVersion(int version);
|
bool setVersion(int version);
|
||||||
bool migrate(int from_version, int to_version);
|
bool migrate(int from_version, int to_version);
|
||||||
bool createObjectsTable();
|
bool createObjectsTable();
|
||||||
|
|
||||||
|
// New utility functions
|
||||||
|
bool merge_existing_entry(const dbEntry& existing, const dbEntry& new_entry, dbEntry& merged);
|
||||||
|
bool insert_new_entry(const dbEntry& entry);
|
||||||
|
bool handle_tag_conflicts(const dbEntry& entry);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace simple_object_storage
|
} // namespace simple_object_storage
|
||||||
|
4
test.sh
4
test.sh
@@ -83,7 +83,9 @@ echo "upload response: ${UPLOAD_RESPONSE}"
|
|||||||
OBJECT_HASH=$(echo ${UPLOAD_RESPONSE} | jq -r '.hash')
|
OBJECT_HASH=$(echo ${UPLOAD_RESPONSE} | jq -r '.hash')
|
||||||
|
|
||||||
# check the hash matches.
|
# check the hash matches.
|
||||||
CHECK_HASH=$(curl -s "${BASE_URL}/hash/${BASE_TAG}:test1" | jq -r '.hash')
|
CMD="${BASE_URL}/hash/${BASE_TAG}:test1"
|
||||||
|
echo "checking hash via ${CMD}"
|
||||||
|
CHECK_HASH=$(curl -s "${CMD}" | jq -r '.hash')
|
||||||
[ "${OBJECT_HASH}" != "${CHECK_HASH}" ] && die "hash does not match: ${OBJECT_HASH} != ${CHECK_HASH}"
|
[ "${OBJECT_HASH}" != "${CHECK_HASH}" ] && die "hash does not match: ${OBJECT_HASH} != ${CHECK_HASH}"
|
||||||
|
|
||||||
# get md5sum of this file
|
# get md5sum of this file
|
||||||
|
Reference in New Issue
Block a user