Skip to content
This repository has been archived by the owner on Jul 5, 2023. It is now read-only.

Commit

Permalink
Attachments are now held in script object scope
Browse files Browse the repository at this point in the history
* this matches CouchDB behaviour
* still needs units and HEAD/DELETE
  • Loading branch information
craigminihan committed Nov 24, 2015
1 parent 9f197da commit a263113
Show file tree
Hide file tree
Showing 29 changed files with 647 additions and 231 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@
[submodule "externals/gperftools"]
path = externals/gperftools
url = https://github.com/gperftools/gperftools.git
[submodule "externals/ConstTimeEncoding"]
path = externals/ConstTimeEncoding
url = https://github.com/RipcordSoftware/ConstTimeEncoding.git
1 change: 1 addition & 0 deletions externals/ConstTimeEncoding
Submodule ConstTimeEncoding added at faeff4
2 changes: 1 addition & 1 deletion externals/libscriptobject
55 changes: 55 additions & 0 deletions src/avancedb/base64_helper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* AvanceDB - an in-memory database similar to Apache CouchDB
* Copyright (C) 2015 Ripcord Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "base64_helper.h"

#include <algorithm>

#include "../../externals/ConstTimeEncoding/base64.h"

std::string Base64Helper::Encode(const std::vector<unsigned char>& data) {
auto encodedSize = data.size() * 4;
encodedSize = (encodedSize + 3) / 3;
encodedSize += 4 - (encodedSize % 4);

std::string text;
text.resize(encodedSize);

base64Encode(&text[0], data.data(), data.size());

return text;
}

std::vector<unsigned char> Base64Helper::Decode(const char* text, std::size_t size) {
std::vector<unsigned char> data;

if (size > 0) {
auto decodedSize = size;
while (text[decodedSize - 1] == '=') {
--decodedSize;
}

decodedSize *= 3;
decodedSize /= 4;
data.resize(decodedSize);

base64Decode(data.data(), text, size);
}

return data;
}
37 changes: 37 additions & 0 deletions src/avancedb/base64_helper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* AvanceDB - an in-memory database similar to Apache CouchDB
* Copyright (C) 2015 Ripcord Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef BASE64_HELPER_H
#define BASE64_HELPER_H

#include <string>
#include <vector>

class Base64Helper final {
public:
Base64Helper() = delete;

static std::string Encode(const std::vector<unsigned char>& data);
static std::vector<unsigned char> Decode(const char* text, std::size_t size);

private:

};

#endif /* BASE64_HELPER_H */

8 changes: 8 additions & 0 deletions src/avancedb/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ document_ptr Database::SetDocument(const char* id, script_object_ptr obj) {
return docs_->SetDocument(id, obj);
}

document_ptr Database::SetDocumentAttachment(const char* id, const char* rev, const char* name, const char* contentType, const std::vector<unsigned char>& attachment) {
return docs_->SetDocumentAttachment(id, rev, name, contentType, attachment);
}

document_attachment_ptr Database::GetDocumentAttachment(const char* id, const char* attName) {
return docs_->GetDocumentAttachment(id, attName);
}

document_ptr Database::GetDesignDocument(const char* id, bool throwOnFail) {
return docs_->GetDesignDocument(id, throwOnFail);
}
Expand Down
5 changes: 5 additions & 0 deletions src/avancedb/database.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#ifndef DATABASE_H
#define DATABASE_H

#include <vector>

#include <boost/noncopyable.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
Expand Down Expand Up @@ -49,6 +51,9 @@ class Database final : public boost::enable_shared_from_this<Database>, private
document_ptr DeleteDocument(const char* id, const char* rev);
document_ptr SetDocument(const char* id, script_object_ptr);

document_ptr SetDocumentAttachment(const char* id, const char* rev, const char* name, const char* contentType, const std::vector<unsigned char>& attachment);
document_attachment_ptr GetDocumentAttachment(const char* id, const char* attName);

document_ptr GetDesignDocument(const char* id, bool throwOnFail = true);
document_ptr DeleteDesignDocument(const char* id, const char* rev);
document_ptr SetDesignDocument(const char* id, script_object_ptr);
Expand Down
14 changes: 0 additions & 14 deletions src/avancedb/document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,4 @@ const script_object_ptr Document::getObject() const {

bool Document::ValidateHashField(const char* name) {
return name != nullptr && std::strcmp(name, "_id") != 0 && std::strcmp(name, "_rev") != 0;
}

document_attachment_ptr Document::getAttachment(const char* name) const {
auto iter = attachments_.find(name);
return iter != attachments_.cend() ? iter->second : nullptr;
}

Document::attachment_collection Document::getAttachments() const {
return attachments_;
}

void Document::putAttachment(const char* name, const char* contentType, const document_attachment_ptr::element_type::value_type* data, document_attachment_ptr::element_type::size_type size) {
auto attachment = document_attachment_ptr::element_type::Create(name, contentType, data, size);
attachments_[name] = attachment;
}
6 changes: 1 addition & 5 deletions src/avancedb/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,7 @@ class Document final : public boost::enable_shared_from_this<Document>, private
const char* getRev() const;
sequence_type getUpdateSequence() const;

const script_object_ptr getObject() const;

document_attachment_ptr getAttachment(const char*) const;
void putAttachment(const char*, const char*, const document_attachment_ptr::element_type::value_type*, document_attachment_ptr::element_type::size_type);
attachment_collection getAttachments() const;
const script_object_ptr getObject() const;

private:

Expand Down
22 changes: 8 additions & 14 deletions src/avancedb/document_attachment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

#include "document_attachment.h"

#include "md5.h"
#include <utility>

DocumentAttachment::DocumentAttachment(const char* name, const char* contentType, std::vector<value_type>&& data, const char* digest) :
name_(name), contentType_(contentType), data_(std::move(data)), digest_(digest) {

DocumentAttachment::DocumentAttachment(const char* name, const char* contentType, const value_type* data, size_type size) :
name_(name), contentType_(contentType), data_(data, data + size), hash_(GetHash(data, size)) {
}

document_attachment_ptr DocumentAttachment::Create(const char* name, const char* contentType, const value_type* data, size_type size) {
return boost::make_shared<document_attachment_ptr::element_type>(name, contentType, data, size);
document_attachment_ptr DocumentAttachment::Create(const char* name, const char* contentType, std::vector<value_type>&& data, const char* digest) {
return boost::make_shared<document_attachment_ptr::element_type>(name, contentType, std::forward<decltype(data)>(data), digest);
}

const std::string& DocumentAttachment::Name() const {
Expand All @@ -44,13 +45,6 @@ const DocumentAttachment::value_type* DocumentAttachment::Data() const {
return data_.data();
}

const std::string& DocumentAttachment::Hash() const {
return hash_;
}

std::string DocumentAttachment::GetHash(const value_type* data, size_type size) {
MD5 hash;
hash.update(data, size);
hash.finalize();
return hash.hexdigest();
const std::string& DocumentAttachment::Digest() const {
return digest_;
}
12 changes: 5 additions & 7 deletions src/avancedb/document_attachment.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,23 @@ class DocumentAttachment final : public boost::enable_shared_from_this<DocumentA
using value_type = unsigned char;
using size_type = std::size_t;

static document_attachment_ptr Create(const char* name, const char* contentType, const value_type* data, size_type size);
static document_attachment_ptr Create(const char* name, const char* contentType, std::vector<value_type>&& data, const char* digest);

const std::string& Name() const;
const std::string& ContentType() const;
size_type Size() const;
const value_type* Data() const;
const std::string& Hash() const;
const std::string& Digest() const;

private:
friend document_attachment_ptr boost::make_shared<document_attachment_ptr::element_type>(const char*&, const char*&, const value_type*&, size_type&);
friend document_attachment_ptr boost::make_shared<document_attachment_ptr::element_type>(const char*&, const char*&, std::vector<value_type>&&, const char*&);

DocumentAttachment(const char* name, const char* contentType, const value_type* data, size_type size);

static std::string GetHash(const value_type* data, size_type size);
DocumentAttachment(const char* name, const char* contentType, std::vector<value_type>&& data, const char* digest);

const std::string name_;
const std::string contentType_;
const std::vector<value_type> data_;
const std::string hash_;
const std::string digest_;
};

#endif /* DOCUMENT_ATTACHMENT_H */
9 changes: 9 additions & 0 deletions src/avancedb/document_revision.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,13 @@ unsigned char DocumentRevision::GetCharNumericValue(char ch) {
} else {
return (ch | 0x20) - 0x61 + 10;
}
}

DocumentRevision::version_type DocumentRevision::getVersion() const {
return version_;
}

void DocumentRevision::getDigest(Digest& digest) const {
static_assert(digestLength_ == sizeof(digest), "Mismatch in digest size");
std::memcpy(digest, digest_, digestLength_);
}
3 changes: 3 additions & 0 deletions src/avancedb/document_revision.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class DocumentRevision {
DocumentRevision& FormatRevision(RevString& rev);
static void FormatRevision(version_type version, const Digest& digest, RevString& rev);

version_type getVersion() const;
void getDigest(Digest&) const;

private:

DocumentRevision(uint64_t version, const Digest& digest);
Expand Down
102 changes: 101 additions & 1 deletion src/avancedb/documents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
#include "config.h"
#include "uuid_helper.h"
#include "map_reduce_result.h"
#include "base64_helper.h"
#include "document_attachment.h"

#include "script_object_vector_source.h"

#include "md5.h"

Documents::Documents(database_ptr db) : db_(db), docCount_(0),
dataSize_(0), updateSeq_(0), localUpdateSeq_(0),
Expand Down Expand Up @@ -109,7 +115,7 @@ document_ptr Documents::DeleteDocument(const char* id, const char* rev) {
document_ptr Documents::SetDocument(const char* id, script_object_ptr obj) {
auto coll = GetDocumentCollectionIndex(id);

boost::lock_guard<DocumentCollection> guard{*docs_[coll]};
boost::unique_lock<DocumentCollection> lock{*docs_[coll]};

Document::Compare compare{id};
auto oldDoc = docs_[coll]->find_fn(compare);
Expand All @@ -130,6 +136,8 @@ document_ptr Documents::SetDocument(const char* id, script_object_ptr obj) {

docs_[coll]->insert(newDoc);

lock.unlock();

if (!oldDoc) {
docCount_.fetch_add(1, boost::memory_order_relaxed);
} else {
Expand All @@ -141,6 +149,98 @@ document_ptr Documents::SetDocument(const char* id, script_object_ptr obj) {
return newDoc;
}

document_ptr Documents::SetDocumentAttachment(const char* id, const char* rev, const char* name, const char* contentType, const std::vector<unsigned char>& attachment) {
auto coll = GetDocumentCollectionIndex(id);

boost::unique_lock<DocumentCollection> lock{*docs_[coll]};

Document::Compare compare{id};
auto oldDoc = docs_[coll]->find_fn(compare);
if (!oldDoc) {
throw DocumentMissing{};
}

if (std::strcmp(rev, oldDoc->getRev()) != 0) {
throw DocumentConflict{};
}

auto oldRev = DocumentRevision::Parse(rev);

auto encodedAttachment = Base64Helper::Encode(attachment);

MD5 md5;
md5.update(attachment.data(), attachment.size());
md5.finalize();
std::vector<unsigned char> digest(16);
md5.bindigest(digest.data());
auto encodedDigest = std::string("md5-") + Base64Helper::Encode(digest).data();

rs::scriptobject::utils::ScriptObjectVectorSource newAttachmentSource({
std::make_pair("content_type", contentType),
std::make_pair("revpos", oldRev.getVersion() + 1),
std::make_pair("digest", encodedDigest.data()),
std::make_pair("data", encodedAttachment.c_str()),
std::make_pair("length", attachment.size())
});

auto newAttachmentObj = rs::scriptobject::ScriptObjectFactory::CreateObject(newAttachmentSource, true);

rs::scriptobject::utils::ScriptObjectVectorSource newNamedAttachmentSource{
{ std::make_pair(name, newAttachmentObj) }
};

auto newNamedAttachmentObj = rs::scriptobject::ScriptObjectFactory::CreateObject(newNamedAttachmentSource, false);

auto attachmentsObj = oldDoc->getObject()->getObject("_attachments", false);
if (!!attachmentsObj) {
attachmentsObj = rs::scriptobject::ScriptObject::Merge(attachmentsObj, newNamedAttachmentObj, rs::scriptobject::ScriptObject::MergeStrategy::Back);
} else {
attachmentsObj = newNamedAttachmentObj;
}

rs::scriptobject::utils::ScriptObjectVectorSource newAttachmentsObjSource{
{ std::make_pair("_attachments", attachmentsObj) }
};

auto newAttachmentsObj = rs::scriptobject::ScriptObjectFactory::CreateObject(newAttachmentsObjSource, true);

newAttachmentsObj = rs::scriptobject::ScriptObject::Merge(oldDoc->getObject(), newAttachmentsObj, rs::scriptobject::ScriptObject::MergeStrategy::Back);

auto newDoc = Document::Create(id, newAttachmentsObj, ++updateSeq_);

docs_[coll]->insert(newDoc);

lock.unlock();

dataSize_.fetch_sub(oldDoc->getObject()->getSize(true), boost::memory_order_relaxed);
dataSize_.fetch_add(newDoc->getObject()->getSize(true), boost::memory_order_relaxed);

return newDoc;
}

document_attachment_ptr Documents::GetDocumentAttachment(const char* id, const char* attName) {
auto doc = GetDocument(id, true);

auto attachments = doc->getObject()->getObject("_attachments", false);
if (!attachments) {
throw DocumentAttachmentMissing{};
}

auto attachment = attachments->getObject(attName, false);
if (!attachment) {
throw DocumentAttachmentMissing{};
}

auto contentType = attachment->getString("content_type");
auto digest = attachment->getString("digest");

auto encodedData = attachment->getString("data");
auto encodedDataSize = attachment->getStringFieldLength("data");
auto data = Base64Helper::Decode(encodedData, encodedDataSize > 0 ? encodedDataSize - 1 : 0);

return DocumentAttachment::Create(attName, contentType, std::move(data), digest);
}

document_ptr Documents::GetDesignDocument(const char* id, bool throwOnFail) {
std::string designId = "_design/";
designId += id;
Expand Down
Loading

0 comments on commit a263113

Please sign in to comment.