LCOV - code coverage report
Current view: top level - extra/webserver/webserver/resource - SPWebResource.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 693 837 82.8 %
Date: 2024-05-12 00:16:13 Functions: 114 114 100.0 %

          Line data    Source code
       1             : /**
       2             :  Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
       3             : 
       4             :  Permission is hereby granted, free of charge, to any person obtaining a copy
       5             :  of this software and associated documentation files (the "Software"), to deal
       6             :  in the Software without restriction, including without limitation the rights
       7             :  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       8             :  copies of the Software, and to permit persons to whom the Software is
       9             :  furnished to do so, subject to the following conditions:
      10             : 
      11             :  The above copyright notice and this permission notice shall be included in
      12             :  all copies or substantial portions of the Software.
      13             : 
      14             :  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      15             :  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      16             :  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      17             :  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      18             :  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      19             :  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      20             :  THE SOFTWARE.
      21             :  **/
      22             : 
      23             : #include "SPWebResource.h"
      24             : #include "SPWebRoot.h"
      25             : #include "SPWebHost.h"
      26             : 
      27             : namespace STAPPLER_VERSIONIZED stappler::web {
      28             : 
      29        2425 : Resource::Resource(ResourceType t, const Transaction &a, QueryList &&list)
      30        2425 : : _type(t), _transaction(a), _queries(move(list)) {
      31        2425 :         if (_queries.isDeltaApplicable()) {
      32        1025 :                 _delta = Time::microseconds(_transaction.getDeltaValue(*_queries.getScheme()));
      33             :         }
      34        2425 : }
      35             : 
      36             : SP_COVERAGE_TRIVIAL
      37             : Resource::~Resource() { }
      38             : 
      39        1425 : ResourceType Resource::getType() const {
      40        1425 :         return _type;
      41             : }
      42             : 
      43        2725 : const db::Scheme &Resource::getScheme() const { return *_queries.getScheme(); }
      44             : 
      45          25 : Status Resource::getStatus() const { return _status; }
      46             : 
      47         100 : bool Resource::isDeltaApplicable() const {
      48         100 :         return _queries.isDeltaApplicable();
      49             : }
      50             : 
      51          50 : bool Resource::hasDelta() const {
      52          50 :         if (isDeltaApplicable()) {
      53          50 :                 if (_queries.isView()) {
      54          25 :                         return static_cast<const db::FieldView *>(_queries.getItems().front().field->getSlot())->delta;
      55             :                 } else {
      56          25 :                         return _queries.getScheme()->hasDelta();
      57             :                 }
      58             :         }
      59           0 :         return false;
      60             : }
      61             : 
      62          50 : void Resource::setQueryDelta(Time d) {
      63          50 :         _queries.setDelta(d);
      64          50 : }
      65             : 
      66          50 : Time Resource::getQueryDelta() const {
      67          50 :         return _queries.getDelta();
      68             : }
      69             : 
      70        3400 : Time Resource::getSourceDelta() const { return _delta; }
      71             : 
      72         600 : void Resource::addExtraResolveField(StringView str) {
      73         600 :         if (emplace_ordered(_extraResolves, str.pdup())) {
      74         575 :                 _isResolvesUpdated = true;
      75             :         }
      76         600 : }
      77             : 
      78        2425 : void Resource::setUser(User *u) {
      79        2425 :         _user = u;
      80        2425 : }
      81             : 
      82        2100 : void Resource::setFilterData(const Value &val) {
      83        2100 :         _filterData = val;
      84        2100 : }
      85             : 
      86         725 : void Resource::applyQuery(const Value &query) {
      87         725 :         _queries.apply(query);
      88         725 : }
      89             : 
      90        2350 : void Resource::prepare(QueryList::Flags flags) {
      91        2350 :         if (_isResolvesUpdated) {
      92        2350 :                 _queries.resolve(_extraResolves);
      93        2350 :                 _isResolvesUpdated = false;
      94             :         }
      95        2350 :         _queries.addFlag(flags);
      96        2350 : }
      97             : 
      98        1850 : const db::QueryList &Resource::getQueries() const {
      99        1850 :         return _queries;
     100             : }
     101             : 
     102             : SP_COVERAGE_TRIVIAL
     103             : bool Resource::prepareUpdate() { return false; }
     104             : 
     105             : SP_COVERAGE_TRIVIAL
     106             : bool Resource::prepareCreate() { return false; }
     107             : 
     108             : SP_COVERAGE_TRIVIAL
     109             : bool Resource::prepareAppend() { return false; }
     110             : 
     111             : SP_COVERAGE_TRIVIAL
     112             : bool Resource::removeObject() { return false; }
     113             : 
     114             : SP_COVERAGE_TRIVIAL
     115             : Value Resource::updateObject(Value &, Vector<db::InputFile> &) { return Value(); }
     116             : 
     117             : SP_COVERAGE_TRIVIAL
     118             : Value Resource::createObject(Value &, Vector<db::InputFile> &) { return Value(); }
     119             : 
     120             : SP_COVERAGE_TRIVIAL
     121             : Value Resource::appendObject(Value &) { return Value(); }
     122             : 
     123             : SP_COVERAGE_TRIVIAL
     124             : Value Resource::getResultObject() { return Value(); }
     125             : 
     126             : SP_COVERAGE_TRIVIAL
     127             : void Resource::resolve(const Scheme &, Value &) { }
     128             : 
     129         450 : db::InputConfig::Require Resource::getInputFlags() const {
     130         450 :         return db::InputConfig::Require::Data | db::InputConfig::Require::Files;
     131             : }
     132             : 
     133         525 : size_t Resource::getMaxRequestSize() const {
     134         525 :         return getRequestScheme().getMaxRequestSize();
     135             : }
     136         525 : size_t Resource::getMaxVarSize() const {
     137         525 :         return getRequestScheme().getMaxVarSize();
     138             : }
     139         525 : size_t Resource::getMaxFileSize() const {
     140         525 :         return getRequestScheme().getMaxFileSize();
     141             : }
     142             : 
     143         250 : void Resource::encodeFiles(Value &data, Vector<db::InputFile> &files) {
     144         325 :         for (auto &it : files) {
     145          75 :                 const db::Field * f = getRequestScheme().getField(it.name);
     146          75 :                 if (f && f->isFile()) {
     147          25 :                         if (!data.hasValue(it.name)) {
     148          25 :                                 data.setInteger(it.negativeId(), it.name);
     149             :                         }
     150          50 :                 } else if (f && f->getType() == db::Type::Extra) {
     151          50 :                         auto fobj = f->getSlot<db::FieldExtra>();
     152          50 :                         auto cIt = fobj->fields.find("content");
     153          50 :                         auto tIt = fobj->fields.find("type");
     154          50 :                         auto mIt = fobj->fields.find("mtime");
     155          50 :                         if (cIt != fobj->fields.end() && tIt != fobj->fields.end() && tIt->second.getType() == db::Type::Text) {
     156          50 :                                 if (cIt->second.getType() == db::Type::Text && !it.isBinary) {
     157             :                                         auto patch = Value({
     158          50 :                                                 pair("content", Value(it.readText())),
     159          50 :                                                 pair("type", Value(it.type)),
     160         125 :                                         });
     161             : 
     162          25 :                                         if (mIt != fobj->fields.end()) {
     163          25 :                                                 patch.setInteger(Time::now().toMicros(), "mtime");
     164             :                                         }
     165          25 :                                         data.setValue(move(patch), f->getName().str<Interface>());
     166          50 :                                 } else if (cIt->second.getType() == db::Type::Bytes) {
     167             :                                         auto patch = Value({
     168          50 :                                                 pair("content", Value(it.readBytes())),
     169          50 :                                                 pair("type", Value(it.type)),
     170         125 :                                         });
     171             : 
     172          25 :                                         if (mIt != fobj->fields.end()) {
     173          25 :                                                 patch.setInteger(Time::now().toMicros(), "mtime");
     174             :                                         }
     175          25 :                                         data.setValue(move(patch), f->getName().str<Interface>());
     176          25 :                                 }
     177             :                         }
     178             :                 }
     179             :         }
     180         250 : }
     181             : 
     182         300 : static bool Resource_isIdRequest(const db::QueryFieldResolver &next, ResolveOptions opts, ResolveOptions target) {
     183         300 :         if (next.getResolves().empty()) {
     184           0 :                 if (auto vec = next.getIncludeVec()) {
     185           0 :                         if (vec->size() == 1 && vec->front().name == "$id") {
     186           0 :                                 return true;
     187             :                         }
     188             :                 }
     189             :         }
     190             : 
     191         300 :         if ((opts & ResolveOptions::Ids) != ResolveOptions::None) {
     192           0 :                 if ((opts & target) == ResolveOptions::None) {
     193           0 :                         return true;
     194             :                 }
     195             :         }
     196         300 :         return false;
     197             : }
     198             : 
     199          50 : void Resource::resolveSet(const QueryFieldResolver &res, int64_t id, const db::Field &field, Value &fobj) {
     200          50 :         QueryFieldResolver next(res.next(field.getName()));
     201          50 :         if (next) {
     202          50 :                 auto &fields = next.getResolves();
     203          50 :                 bool idOnly = Resource_isIdRequest(next, ResolveOptions::None, ResolveOptions::Sets);
     204             : 
     205             :                 auto objs = idOnly
     206          50 :                                 ? Worker(*res.getScheme(), _transaction).getField(fobj, field, Set<const Field *>{(const Field *)nullptr})
     207         100 :                                 : Worker(*res.getScheme(), _transaction).getField(fobj, field, fields);
     208          50 :                 if (objs.isArray()) {
     209          50 :                         Value arr;
     210          50 :                         for (auto &sit : objs.asArray()) {
     211           0 :                                 if (sit.isDictionary()) {
     212           0 :                                         auto id = sit.getInteger("__oid");
     213           0 :                                         if (idOnly) {
     214           0 :                                                 arr.addInteger(id);
     215             :                                         } else {
     216           0 :                                                 if (_resolveObjects.insert(id).second == false) {
     217           0 :                                                         sit.setInteger(id);
     218             :                                                 }
     219           0 :                                                 arr.addValue(std::move(sit));
     220             :                                         }
     221             :                                 }
     222             :                         }
     223          50 :                         fobj = std::move(arr);
     224          50 :                         return;
     225          50 :                 }
     226          50 :         }
     227           0 :         fobj = Value();
     228             : }
     229             : 
     230          75 : void Resource::resolveObject(const QueryFieldResolver &res, int64_t id, const db::Field &field, Value &fobj) {
     231          75 :         QueryFieldResolver next(res.next(field.getName()));
     232          75 :         if (next && _resolveObjects.find(fobj.asInteger()) == _resolveObjects.end()) {
     233          75 :                 auto &fields = next.getResolves();
     234          75 :                 if (!Resource_isIdRequest(next, _resolve, ResolveOptions::Objects)) {
     235          75 :                         Value obj = Worker(*res.getScheme(), _transaction).getField(fobj, field, fields);
     236          75 :                         if (obj.isDictionary()) {
     237          75 :                                 auto id = obj.getInteger("__oid");
     238          75 :                                 if (_resolveObjects.insert(id).second == false) {
     239           0 :                                         fobj.setInteger(id);
     240             :                                 } else {
     241          75 :                                         fobj.setValue(move(obj));
     242             :                                 }
     243          75 :                                 return;
     244             :                         }
     245          75 :                 } else {
     246           0 :                         return;
     247             :                 }
     248           0 :                 fobj.setNull();
     249             :         }
     250             : }
     251             : 
     252          25 : void Resource::resolveArray(const QueryFieldResolver &res, int64_t id, const db::Field &field, Value &fobj) {
     253          25 :         fobj.setValue(Worker(*res.getScheme(), _transaction).getField(fobj, field));
     254          25 : }
     255             : 
     256         175 : void Resource::resolveFile(const QueryFieldResolver &res, int64_t id, const db::Field &field, Value &fobj) {
     257         175 :         QueryFieldResolver next(res.next(field.getName()));
     258         175 :         if (next) {
     259         175 :                 auto fields = next.getResolves();
     260         175 :                 if (!Resource_isIdRequest(next, _resolve, ResolveOptions::Files)) {
     261         175 :                         Value obj = Worker(*res.getScheme(), _transaction).getField(fobj, field, fields);
     262         175 :                         if (obj.isDictionary()) {
     263         175 :                                 fobj.setValue(move(obj));
     264         175 :                                 return;
     265             :                         }
     266         175 :                 } else {
     267           0 :                         return;
     268             :                 }
     269         175 :         }
     270           0 :         fobj.setNull();
     271             : }
     272             : 
     273        1100 : static void Resource_resolveExtra(const db::QueryFieldResolver &res, Value &obj) {
     274        1100 :         auto &fields = res.getResolves();
     275        1100 :         auto &fieldData = res.getResolvesData();
     276        1100 :         auto &dict = obj.asDict();
     277        1100 :         auto it = dict.begin();
     278        4175 :         while (it != dict.end()) {
     279        3075 :                 auto f = res.getField(it->first);
     280        3075 :                 if (fieldData.find(it->first) != fieldData.end()) {
     281          25 :                         db::QueryFieldResolver next(res.next(it->first));
     282          25 :                         if (next) {
     283           0 :                                 Resource_resolveExtra(next, it->second);
     284             :                         }
     285          25 :                         it ++;
     286        3050 :                 } else if (!f || f->isProtected() || (fields.find(f) == fields.end())) {
     287          25 :                         it = dict.erase(it);
     288        3025 :                 } else if (f->getType() == db::Type::Extra && it->second.isDictionary()) {
     289           0 :                         db::QueryFieldResolver next(res.next(it->first));
     290           0 :                         if (next) {
     291           0 :                                 Resource_resolveExtra(next, it->second);
     292             :                         }
     293           0 :                         it ++;
     294             :                 } else {
     295        3025 :                         it ++;
     296             :                 }
     297             :         }
     298        1100 : }
     299             : 
     300        5900 : int64_t Resource::processResolveResult(const QueryFieldResolver &res, const Set<const Field *> &fields, Value &obj) {
     301        5900 :         int64_t id = 0;
     302        5900 :         auto &dict = obj.asDict();
     303        5900 :         auto it = dict.begin();
     304             : 
     305       46475 :         while (it != dict.end()) {
     306       40575 :                 if (it->first == "__oid") {
     307        5900 :                         id = it->second.asInteger();
     308        5900 :                         ++ it;
     309        6000 :                         continue;
     310       34675 :                 } else if (it->first == "__delta") {
     311          50 :                         if (res.getMeta() == QueryFieldResolver::Meta::None) {
     312          50 :                                 if (it->second.getString("action") == "delete") {
     313           0 :                                         it->second.setString("delete");
     314             :                                 } else {
     315          50 :                                         it = dict.erase(it);
     316          50 :                                         continue;
     317             :                                 }
     318             :                         } else {
     319           0 :                                 auto meta = res.getMeta();
     320           0 :                                 if ((meta & QueryFieldResolver::Meta::Action) == QueryFieldResolver::Meta::None) {
     321           0 :                                         it->second.erase("action");
     322           0 :                                 } else if ((meta & QueryFieldResolver::Meta::Time) == QueryFieldResolver::Meta::None) {
     323           0 :                                         it->second.erase("time");
     324             :                                 }
     325             :                         }
     326           0 :                         ++ it;
     327           0 :                         continue;
     328       34625 :                 } else if (it->first == "__views") {
     329          50 :                         auto meta = res.getMeta();
     330          50 :                         if ((meta & QueryFieldResolver::Meta::View) == QueryFieldResolver::Meta::None) {
     331          50 :                                 it = dict.erase(it);
     332          50 :                                 continue;
     333             :                         } else {
     334           0 :                                 ++ it;
     335           0 :                                 continue;
     336             :                         }
     337       34575 :                 } else if (it->first == "__ts_rank") {
     338           0 :                         ++ it;
     339           0 :                         continue;
     340             :                 }
     341             : 
     342       34575 :                 auto f = res.getField(it->first);
     343       34575 :                 if (!f || f->isProtected() || (fields.find(f) == fields.end())) {
     344        1800 :                         it = dict.erase(it);
     345       32775 :                 } else if ((f->getType() == db::Type::Extra || f->getType() == db::Type::Data || f->getType() == db::Type::Virtual) && it->second.isDictionary()) {
     346        2225 :                         QueryFieldResolver next(res.next(it->first));
     347        2225 :                         if (next) {
     348        1100 :                                 Resource_resolveExtra(next, it->second);
     349             :                         }
     350        2225 :                         it ++;
     351             :                 } else {
     352       30550 :                         it ++;
     353             :                 }
     354             :         }
     355        5900 :         return id;
     356             : }
     357             : 
     358        5900 : void Resource::resolveResult(const QueryFieldResolver &res, Value &obj, uint16_t depth, uint16_t max) {
     359        5900 :         auto &searchField = res.getResolves();
     360             : 
     361        5900 :         int64_t id = processResolveResult(res, searchField, obj);
     362             : 
     363        5900 :         if (res && depth <= max) {
     364        5900 :                 auto & fields = *res.getFields();
     365       80325 :                 for (auto &it : fields) {
     366       74425 :                         const Field &f = it.second;
     367       74425 :                         auto type = f.getType();
     368             : 
     369       74425 :                         if (f.isSimpleLayout() || searchField.find(&f) == searchField.end()) {
     370       74050 :                                 if (type == db::Type::Bytes && f.getTransform() == db::Transform::Uuid) {
     371           0 :                                         auto &fobj = obj.getValue(it.first);
     372           0 :                                         if (fobj.isBytes()) {
     373           0 :                                                 fobj.setString(memory::uuid(fobj.getBytes()).str());
     374             :                                         }
     375             :                                 }
     376       74050 :                                 continue;
     377       74050 :                         }
     378             : 
     379         375 :                         if (!obj.hasValue(it.first) && (type == db::Type::Set || type == db::Type::Array || type == db::Type::View)) {
     380          75 :                                 obj.setInteger(id, it.first);
     381             :                         }
     382             : 
     383         375 :                         auto &fobj = obj.getValue(it.first);
     384         375 :                         if (type == db::Type::Object && fobj.isInteger()) {
     385          75 :                                 resolveObject(res, id, f, fobj);
     386         300 :                         } else if ((type == db::Type::Set || type == db::Type::View) && fobj.isInteger()) {
     387          50 :                                 resolveSet(res, id, f, fobj);
     388         250 :                         } else if (type == db::Type::Array && fobj.isInteger()) {
     389          25 :                                 resolveArray(res, id, f, fobj);
     390         225 :                         } else if ((type == db::Type::File || type == db::Type::Image) && fobj.isInteger()) {
     391         175 :                                 resolveFile(res, id, f, fobj);
     392             :                         }
     393             :                 }
     394             : 
     395       80325 :                 for (auto &it : fields) {
     396       74425 :                         auto &f = it.second;
     397       74425 :                         auto type = f.getType();
     398             : 
     399         500 :                         if ((type == db::Type::Object && obj.isDictionary(it.first))
     400       74925 :                                         || ((type == db::Type::Set || type == db::Type::View) && obj.isArray(it.first))) {
     401          75 :                                 QueryFieldResolver next(res.next(it.first));
     402          75 :                                 if (next) {
     403          75 :                                         if (type == db::Type::Set || type == db::Type::View) {
     404           0 :                                                 auto &fobj = obj.getValue(it.first);
     405           0 :                                                 for (auto &sit : fobj.asArray()) {
     406           0 :                                                         if (sit.isDictionary()) {
     407           0 :                                                                 resolveResult(next, sit, depth + 1, max);
     408             :                                                         }
     409             :                                                 }
     410           0 :                                         } else {
     411          75 :                                                 auto &fobj = obj.getValue(it.first);
     412          75 :                                                 if (fobj.isDictionary()) {
     413          75 :                                                         resolveResult(next, fobj, depth + 1, max);
     414             :                                                 }
     415             :                                         }
     416             :                                 }
     417       74350 :                         } else if (f.isFile() && obj.isDictionary()) {
     418        1500 :                                 auto &dict = obj.asDict();
     419        1500 :                                 auto f_it = dict.find(it.first);
     420        1500 :                                 if (f_it != dict.end() && f_it->second.isNull()) {
     421           0 :                                         dict.erase(f_it);
     422             :                                 }
     423             :                         }
     424             :                 }
     425           0 :         } else if (obj.isDictionary()) {
     426           0 :                 auto &dict = obj.asDict();
     427           0 :                 auto it = dict.begin();
     428           0 :                 while (it != dict.end()) {
     429           0 :                         auto f = res.getField(it->first);
     430           0 :                         if (f && f->isFile()) {
     431           0 :                                 it = dict.erase(it);
     432             :                         } else {
     433           0 :                                 ++ it;
     434             :                         }
     435             :                 }
     436             :         }
     437        5900 : }
     438             : 
     439        5825 : void Resource::resolveResult(const QueryList &l, Value &obj) {
     440        5825 :         if (_isResolvesUpdated) {
     441         175 :                 _queries.resolve(_extraResolves);
     442         175 :                 _isResolvesUpdated = false;
     443             :         }
     444        5825 :         resolveResult(l.getFields(), obj, 0, l.getResolveDepth());
     445        5825 : }
     446             : 
     447        1650 : const db::Scheme &Resource::getRequestScheme() const {
     448        1650 :         return getScheme();
     449             : }
     450             : 
     451        2075 : ResourceObject::ResourceObject(const Transaction &a, QueryList &&q)
     452        2075 : : Resource(ResourceType::Object, a, move(q)) { }
     453             : 
     454          50 : bool ResourceObject::prepareUpdate() {
     455          50 :         return true;
     456             : }
     457             : 
     458             : SP_COVERAGE_TRIVIAL
     459             : bool ResourceObject::prepareCreate() {
     460             :         _status = HTTP_NOT_IMPLEMENTED;
     461             :         return false;
     462             : }
     463             : 
     464             : SP_COVERAGE_TRIVIAL
     465             : bool ResourceObject::prepareAppend() {
     466             :         _status = HTTP_NOT_IMPLEMENTED;
     467             :         return false;
     468             : }
     469             : 
     470          75 : bool ResourceObject::removeObject() {
     471          75 :         auto objs = getDatabaseId(_queries);
     472          75 :         if (objs.empty()) {
     473           0 :                 return false;
     474             :         }
     475             : 
     476          75 :         bool ret = (objs.size() == 1);
     477         150 :         for (auto &it : objs) {
     478          75 :                 _transaction.perform([&, this] {
     479          75 :                         if (ret) {
     480          75 :                                 ret = Worker(getScheme(), _transaction).remove(it);
     481             :                         } else {
     482           0 :                                 Worker(getScheme(), _transaction).remove(it);
     483             :                         }
     484          75 :                         return true;
     485             :                 });
     486             :         }
     487          75 :         return (objs.size() == 1)?ret:true;
     488          75 : }
     489             : 
     490          75 : Value ResourceObject::performUpdate(const Vector<int64_t> &objs, Value &data, Vector<db::InputFile> &files) {
     491          75 :         Value ret;
     492          75 :         encodeFiles(data, files);
     493             : 
     494         175 :         for (auto &it : data.asDict()) {
     495         100 :                 addExtraResolveField(it.first);
     496             :         }
     497             : 
     498         150 :         for (auto &it : objs) {
     499          75 :                 _transaction.perform([&, this] {
     500          75 :                         ret.addValue(Worker(getScheme(), _transaction).update(it, data));
     501          75 :                         return true;
     502             :                 });
     503             :         }
     504             : 
     505         150 :         return processResultList(_queries, ret);
     506          75 : }
     507             : 
     508          50 : Value ResourceObject::updateObject(Value &data, Vector<db::InputFile> &files) {
     509          50 :         Value ret;
     510          50 :         if (files.empty() && (!data.isDictionary() || data.empty())) {
     511           0 :                 return Value();
     512             :         }
     513             : 
     514             :         // single-object or mass update
     515          50 :         auto objs = getDatabaseId(_queries);
     516          50 :         if (objs.empty()) {
     517           0 :                 return Value();
     518             :         }
     519             : 
     520          50 :         return performUpdate(objs, data, files);
     521          50 : }
     522             : 
     523        1225 : Value ResourceObject::getResultObject() {
     524        1225 :         auto ret = getDatabaseObject();
     525        1225 :         if (!ret.isArray()) {
     526           0 :                 return Value();
     527             :         }
     528             : 
     529        1225 :         return processResultList(_queries, ret);
     530        1225 : }
     531             : 
     532          25 : int64_t ResourceObject::getObjectMtime() {
     533          25 :         auto tmpQuery = _queries;
     534          25 :         tmpQuery.clearFlags();
     535          25 :         tmpQuery.addFlag(QueryList::SimpleGet);
     536          25 :         auto str = tmpQuery.setQueryAsMtime();
     537          25 :         if (!str.empty()) {
     538          25 :                 if (auto ret = _transaction.performQueryList(tmpQuery)) {
     539          25 :                         if (ret.isArray()) {
     540          25 :                                 return ret.getValue(0).getInteger(str);
     541             :                         } else {
     542           0 :                                 return ret.getInteger(str);
     543             :                         }
     544          25 :                 }
     545             :         }
     546             : 
     547           0 :         return 0;
     548          25 : }
     549             : 
     550        1700 : Value ResourceObject::processResultList(const QueryList &s, Value &ret) {
     551        1700 :         if (ret.isArray()) {
     552        1700 :                 auto &arr = ret.asArray();
     553        1700 :                 auto it = arr.begin();
     554        7450 :                 while (it != arr.end()) {
     555        5750 :                         if (it->isInteger()) {
     556         450 :                                 if (auto val = Worker(getScheme(), _transaction).get(it->getInteger())) {
     557         225 :                                         *it = move(val);
     558         225 :                                 }
     559             :                         }
     560             : 
     561        5750 :                         if (!processResultObject(s, *it)) {
     562           0 :                                 it = arr.erase(it);
     563             :                         } else {
     564        5750 :                                 it ++;
     565             :                         }
     566             :                 }
     567        1700 :                 return std::move(ret);
     568             :         }
     569           0 :         return Value();
     570             : }
     571             : 
     572        5825 : bool ResourceObject::processResultObject(const QueryList &s, Value &obj) {
     573        5825 :         if (obj.isDictionary()) {
     574        5825 :                 resolveResult(s, obj);
     575        5825 :                 return true;
     576             :         }
     577           0 :         return false;
     578             : }
     579             : 
     580        1225 : Value ResourceObject::getDatabaseObject() {
     581        1225 :         return _transaction.performQueryList(_queries);
     582             : }
     583             : 
     584         125 : Vector<int64_t> ResourceObject::getDatabaseId(const QueryList &q, size_t count) {
     585         125 :         const Vector<QueryList::Item> &items = q.getItems();
     586         125 :         count = min(items.size(), count);
     587             : 
     588         125 :         return _transaction.performQueryListForIds(q, count);
     589             : }
     590             : 
     591        1325 : ResourceReslist::ResourceReslist(const Transaction &a, QueryList &&q)
     592        1325 : : ResourceObject(a, move(q)) {
     593        1325 :         _type = ResourceType::ResourceList;
     594        1325 : }
     595             : 
     596         100 : bool ResourceReslist::prepareCreate() {
     597         100 :         return true;
     598             : }
     599             : 
     600         125 : Value ResourceReslist::doCreateObject(Value &data, Vector<db::InputFile> &files, const Value &extra) {
     601         125 :         if (extra.isDictionary()) {
     602         100 :                 for (auto & it : extra.asDict()) {
     603          50 :                         data.setValue(it.second, it.first);
     604             :                 }
     605             :         }
     606             : 
     607         125 :         if (!files.empty()) {
     608          25 :                 encodeFiles(data, files);
     609             :         }
     610             : 
     611         625 :         for (auto &it : data.asDict()) {
     612         500 :                 addExtraResolveField(it.first);
     613             :         }
     614             : 
     615         250 :         return Worker(getScheme(), _transaction).create(data);
     616             : }
     617             : 
     618         100 : Value ResourceReslist::performCreateObject(Value &data, Vector<db::InputFile> &files, const Value &extra) {
     619             :         // single object
     620         100 :         if (data.isDictionary() || data.empty()) {
     621          75 :                 Value ret = doCreateObject(data, files, extra);
     622          75 :                 if (processResultObject(_queries, ret)) {
     623          75 :                         return ret;
     624             :                 }
     625         100 :         } else if (data.isArray()) {
     626          25 :                 Vector<db::InputFile> empty;
     627          25 :                 Value ret;
     628          50 :                 for (auto &obj : data.asArray()) {
     629          25 :                         Value n = doCreateObject(obj, empty, extra);
     630          25 :                         if (n) {
     631          25 :                                 ret.addValue(std::move(n));
     632             :                         }
     633          25 :                 }
     634          25 :                 return processResultList(_queries, ret);
     635          25 :         }
     636             : 
     637           0 :         return Value();
     638             : }
     639             : 
     640          75 : Value ResourceReslist::createObject(Value &data, Vector<db::InputFile> &file) {
     641         150 :         return performCreateObject(data, file, Value());
     642             : }
     643             : 
     644         600 : ResourceSet::ResourceSet(const Transaction &a, QueryList &&q)
     645         600 : : ResourceReslist(a, move(q)) {
     646         600 :         _type = ResourceType::Set;
     647         600 : }
     648             : 
     649          25 : bool ResourceSet::prepareAppend() {
     650          25 :         return true;
     651             : }
     652             : 
     653          25 : Value ResourceSet::createObject(Value &data, Vector<db::InputFile> &file) {
     654             :         // write object patch
     655          25 :         Value extra;
     656          25 :         auto &items = _queries.getItems();
     657          25 :         auto &item = items.back();
     658          25 :         if (items.size() > 1 && item.ref) {
     659             :                 // has subqueries, try to calculate origin
     660          25 :                 if (auto id = items.at(items.size() - 2).query.getSingleSelectId()) {
     661          25 :                         extra.setInteger(id, item.ref->getName().str<Interface>());
     662             :                 } else {
     663           0 :                         auto ids = getDatabaseId(_queries, _queries.size() - 1);
     664           0 :                         if (ids.size() == 1) {
     665           0 :                                 extra.setInteger(ids.front(), item.ref->getName().str<Interface>());
     666             :                         }
     667           0 :                 }
     668             :         }
     669          25 :         if (!item.query.getSelectList().empty()) {
     670             :                 // has select query, try to extract extra data
     671           0 :                 for (auto &it : item.query.getSelectList()) {
     672           0 :                         if (it.compare == db::Comparation::Equal) {
     673           0 :                                 extra.setValue(it.value1, it.field);
     674             :                         }
     675             :                 }
     676             :         }
     677          50 :         return performCreateObject(data, file, extra);
     678          25 : }
     679             : 
     680          25 : Value ResourceSet::appendObject(Value &data) {
     681             :         // write object patch
     682          25 :         Value extra;
     683          25 :         auto &items = _queries.getItems();
     684          25 :         auto &item = items.back();
     685          25 :         if (items.size() > 1 && item.ref) {
     686             :                 // has subqueries, try to calculate origin
     687          25 :                 if (auto id = items.at(items.size() - 2).query.getSingleSelectId()) {
     688          25 :                         extra.setInteger(id, item.ref->getName().str<Interface>());
     689             :                 } else {
     690           0 :                         auto ids = getDatabaseId(_queries);
     691           0 :                         if (ids.size() == 1 && ids.front()) {
     692           0 :                                 extra.setInteger(ids.front(), item.ref->getName().str<Interface>());
     693             :                         }
     694           0 :                 }
     695             :         }
     696             : 
     697          25 :         if (extra.empty()) {
     698           0 :                 return Value();
     699             :         }
     700             : 
     701             :         // collect object ids from input data
     702          25 :         Value val;
     703          25 :         if (data.isDictionary() && data.hasValue(item.ref->getName())) {
     704           0 :                 val = std::move(data.getValue(item.ref->getName()));
     705             :         } else {
     706          25 :                 val = std::move(data);
     707             :         }
     708          25 :         Vector<int64_t> ids;
     709          25 :         Vector<db::InputFile> empty;
     710          25 :         if (val.isArray()) {
     711          50 :                 for (auto &it : val.asArray()) {
     712          25 :                         if (it.isDictionary()) {
     713          25 :                                 it = doCreateObject(it, empty, extra);
     714          25 :                                 if (it.isInteger("__oid")) {
     715          25 :                                         it = Value(it.getInteger("__oid"));
     716             :                                 }
     717             :                         }
     718          25 :                         auto i = it.asInteger();
     719          25 :                         if (i) {
     720          25 :                                 ids.push_back(i);
     721             :                         }
     722             :                 }
     723             :         } else {
     724           0 :                 if (val.isDictionary()) {
     725           0 :                         val = doCreateObject(val, empty, extra);
     726           0 :                         if (val.isInteger("__oid")) {
     727           0 :                                 val = Value(val.getInteger("__oid"));
     728             :                         }
     729             :                 }
     730           0 :                 auto i = val.asInteger();
     731           0 :                 if (i) {
     732           0 :                         ids.push_back(i);
     733             :                 }
     734             :         }
     735             : 
     736          25 :         Vector<db::InputFile> files;
     737          25 :         return performUpdate(ids, extra, files);
     738          25 : }
     739             : 
     740             : 
     741         175 : ResourceRefSet::ResourceRefSet(const Transaction &a, QueryList &&q)
     742         175 : : ResourceSet(a, move(q)), _sourceScheme(_queries.getSourceScheme()), _field(_queries.getField()) {
     743         175 :         _type = ResourceType::ReferenceSet;
     744         175 : }
     745             : 
     746          25 : bool ResourceRefSet::prepareUpdate() {
     747          25 :         return true;
     748             : }
     749          75 : bool ResourceRefSet::prepareCreate() {
     750          75 :         return true;
     751             : }
     752          25 : bool ResourceRefSet::prepareAppend() {
     753          25 :         return true;
     754             : }
     755          25 : bool ResourceRefSet::removeObject() {
     756          25 :         auto id = getObjectId();
     757          25 :         if (id == 0) {
     758           0 :                 return false;
     759             :         }
     760             : 
     761          50 :         return _transaction.perform([&, this] () -> bool {
     762          25 :                 Vector<int64_t> objs;
     763          25 :                 if (!isEmptyRequest()) {
     764           0 :                         objs = getDatabaseId(_queries);
     765           0 :                         if (objs.empty()) {
     766           0 :                                 return false;
     767             :                         }
     768             :                 }
     769          25 :                 return doCleanup(id, objs);
     770          50 :         });
     771             : }
     772          25 : Value ResourceRefSet::updateObject(Value &value, Vector<db::InputFile> &files) {
     773          25 :         if (value.isDictionary() && value.hasValue(_field->getName()) && (value.isBasicType(_field->getName()) || value.isArray(_field->getName()))) {
     774           0 :                 value = value.getValue(_field->getName());
     775             :         }
     776          25 :         if (value.isBasicType() && !value.isNull()) {
     777           0 :                 return doAppendObject(value, true);
     778          25 :         } else if (value.isArray()) {
     779          25 :                 return doAppendObjects(value, true);
     780             :         } else {
     781           0 :                 return ResourceSet::updateObject(value, files);
     782             :         }
     783             : }
     784          75 : Value ResourceRefSet::createObject(Value &value, Vector<db::InputFile> &files) {
     785          75 :         encodeFiles(value, files);
     786          75 :         return appendObject(value);
     787             : }
     788         100 : Value ResourceRefSet::appendObject(Value &value) {
     789         100 :         if (value.isBasicType()) {
     790          25 :                 return doAppendObject(value, false);
     791          75 :         } else if (value.isArray()) {
     792          75 :                 return doAppendObjects(value, false);
     793           0 :         } else if (value.isDictionary()) {
     794           0 :                 return doAppendObject(value, false);
     795             :         }
     796           0 :         return Value();
     797             : }
     798             : 
     799         150 : int64_t ResourceRefSet::getObjectId() {
     800         150 :         if (!_objectId) {
     801         150 :                 auto ids = _transaction.performQueryListForIds(_queries, _queries.getItems().size() - 1);
     802         150 :                 if (!ids.empty()) {
     803         150 :                         _objectId = ids.front();
     804             :                 }
     805         150 :         }
     806         150 :         return _objectId;
     807             : }
     808             : 
     809          25 : bool ResourceRefSet::isEmptyRequest() {
     810          25 :         if (_queries.getItems().back().query.empty()) {
     811          25 :                 return true;
     812             :         }
     813           0 :         return false;
     814             : }
     815             : 
     816         125 : Vector<int64_t> ResourceRefSet::prepareAppendList(int64_t id, const Value &patch, bool cleanup) {
     817         125 :         Vector<int64_t> ids;
     818         125 :         if (patch.isArray() && patch.size() > 0) {
     819         350 :                 for (auto &it : patch.asArray()) {
     820         225 :                         Value obj;
     821         225 :                         if (it.isNull() || (it.isDictionary() && !it.hasValue("__oid"))) {
     822         150 :                                 obj = Worker(getScheme(), _transaction).create(it);
     823             :                         } else {
     824          75 :                                 obj = Worker(getScheme(), _transaction).get(it);
     825             :                         }
     826         225 :                         if (obj) {
     827         225 :                                 if (auto pushId = obj.getInteger("__oid")) {
     828         225 :                                         ids.push_back(pushId);
     829             :                                 }
     830             :                         }
     831         225 :                 }
     832             :         }
     833             : 
     834         125 :         return ids;
     835           0 : }
     836             : 
     837          25 : bool ResourceRefSet::doCleanup(int64_t id, const Vector<int64_t> &objs) {
     838          25 :         if (objs.empty()) {
     839          25 :                 Worker(*_sourceScheme, _transaction).clearField(id, *_field);
     840             :         } else {
     841           0 :                 Value objsData;
     842           0 :                 for (auto &it : objs) {
     843           0 :                         objsData.addInteger(it);
     844             :                 }
     845           0 :                 Worker(*_sourceScheme, _transaction).clearField(id, *_field, move(objsData));
     846           0 :         }
     847          25 :         return true;
     848             : }
     849             : 
     850          25 : Value ResourceRefSet::doAppendObject(const Value &val, bool cleanup) {
     851          25 :         Value arr;
     852          25 :         arr.addValue(val);
     853          50 :         return doAppendObjects(arr, cleanup);
     854          25 : }
     855             : 
     856         125 : Value ResourceRefSet::doAppendObjects(const Value &val, bool cleanup) {
     857         125 :         Value ret;
     858         125 :         _transaction.perform([&, this] { // all or nothing
     859         125 :                 return doAppendObjectsTransaction(ret, val, cleanup);
     860             :         });
     861             : 
     862         125 :         if (!_queries.getFields().getFields()->empty()) {
     863         125 :                 return processResultList(_queries, ret);
     864             :         }
     865             : 
     866           0 :         return ret;
     867         125 : }
     868             : 
     869         125 : bool ResourceRefSet::doAppendObjectsTransaction(Value &ret, const Value &val, bool cleanup) {
     870         125 :         auto id = getObjectId();
     871         125 :         if (id == 0) {
     872           0 :                 return false;
     873             :         }
     874             : 
     875         125 :         Vector<int64_t> ids = prepareAppendList(id, val, cleanup);
     876         125 :         if (ids.empty()) {
     877           0 :                 Root::getCurrent()->error("ResourceRefSet", "Empty changeset id list in update/append action", Value({
     878           0 :                         pair("sourceScheme", Value(_sourceScheme->getName())),
     879           0 :                         pair("targetScheme", Value(getScheme().getName())),
     880           0 :                 }));
     881           0 :                 return false;
     882             :         }
     883             : 
     884         125 :         Value patch;
     885         350 :         for (auto &it : ids) {
     886         225 :                 patch.addInteger(it);
     887             :         }
     888             : 
     889         125 :         if (cleanup) {
     890          25 :                 ret = Worker(*_sourceScheme, _transaction).setField(id, *_field, move(patch));
     891             :         } else {
     892         100 :                 ret = Worker(*_sourceScheme, _transaction).appendField(id, *_field, move(patch));
     893             :         }
     894             : 
     895         125 :         return !ret.empty();
     896         125 : }
     897             : 
     898         350 : ResourceProperty::ResourceProperty(const Transaction &a, QueryList &&q, const Field *prop)
     899         350 : : Resource(ResourceType::File, a, move(q)), _field(prop) {
     900         350 :         _queries.setProperty(prop);
     901         350 : }
     902             : 
     903          50 : bool ResourceProperty::removeObject() {
     904             :         // perform one-line remove
     905         100 :         return _transaction.perform([&, this] () -> bool {
     906          50 :                 if (auto id = getObjectId()) {
     907         100 :                         if (Worker(getScheme(), _transaction).update(id, Value({ pair(_field->getName().str<Interface>(), Value()) }))) {
     908          50 :                                 return true;
     909             :                         }
     910             :                 }
     911           0 :                 _status = HTTP_CONFLICT;
     912           0 :                 return false;
     913         100 :         });
     914             : }
     915             : 
     916         275 : uint64_t ResourceProperty::getObjectId() {
     917         275 :         auto ids = _transaction.performQueryListForIds(_queries);
     918         550 :         return ids.empty() ? 0 : ids.front();
     919         275 : }
     920             : 
     921         150 : ResourceFile::ResourceFile(const Transaction &a, QueryList &&q, const Field *prop)
     922         150 : : ResourceProperty(a, move(q), prop) {
     923         150 :         _type = ResourceType::File;
     924         150 : }
     925             : 
     926          50 : db::InputConfig::Require ResourceFile::getInputFlags() const {
     927          50 :         return db::InputConfig::Require::Files;
     928             : }
     929             : 
     930          25 : bool ResourceFile::prepareUpdate() {
     931          25 :         return true;
     932             : }
     933          75 : bool ResourceFile::prepareCreate() {
     934          75 :         return true;
     935             : }
     936          75 : Value ResourceFile::updateObject(Value &, Vector<db::InputFile> &f) {
     937          75 :         if (f.empty()) {
     938           0 :                 _status = HTTP_BAD_REQUEST;
     939           0 :                 return Value();
     940             :         }
     941             : 
     942          75 :         db::InputFile *file = nullptr;
     943          75 :         for (auto &it : f) {
     944          75 :                 if (it.name == _field->getName() || it.name == "content") {
     945           0 :                         file = &it;
     946           0 :                         break;
     947          75 :                 } else if (it.name.empty()) {
     948          75 :                         it.name = _field->getName().str<Interface>();
     949          75 :                         file = &it;
     950          75 :                         break;
     951             :                 }
     952             :         }
     953             : 
     954         150 :         for (auto &it : f) {
     955          75 :                 if (it.name != _field->getName() && &it != file) {
     956           0 :                         it.close();
     957             :                 }
     958             :         }
     959             : 
     960          75 :         if (!file) {
     961           0 :                 _status = HTTP_BAD_REQUEST;
     962           0 :                 return Value();
     963             :         }
     964             : 
     965          75 :         if (file->name != _field->getName()) {
     966           0 :                 file->name = _field->getName().str<Interface>();
     967             :         }
     968             : 
     969          75 :         Value patch;
     970          75 :         patch.setInteger(file->negativeId(), _field->getName().str<Interface>());
     971             : 
     972             :         // perform one-line update
     973          75 :         if (auto id = getObjectId()) {
     974          75 :                 auto ret = Worker(getScheme(), _transaction).update(id, patch);
     975          75 :                 ret = getFileForObject(ret);
     976          75 :                 return ret;
     977          75 :         }
     978           0 :         return Value();
     979          75 : }
     980          50 : Value ResourceFile::createObject(Value &val, Vector<db::InputFile> &f) {
     981             :         // same as update
     982          50 :         return updateObject(val, f);
     983             : }
     984             : 
     985          25 : Value ResourceFile::getResultObject() {
     986          25 :         if (_field->hasFlag(db::Flags::Protected)) {
     987           0 :                 _status = HTTP_NOT_FOUND;
     988           0 :                 return Value();
     989             :         }
     990          25 :         return getDatabaseObject();
     991             : }
     992             : 
     993          75 : Value ResourceFile::getFileForObject(Value &object) {
     994          75 :         if (object.isDictionary()) {
     995          75 :                 auto id = object.getInteger(_field->getName());
     996          75 :                 if (id) {
     997          75 :                         auto fileScheme = Host::getCurrent().getFileScheme();
     998          75 :                         Value ret(Worker(*fileScheme, _transaction).get(id));
     999          75 :                         return ret;
    1000          75 :                 }
    1001             :         }
    1002           0 :         return Value();
    1003             : }
    1004             : 
    1005          25 : Value ResourceFile::getDatabaseObject() {
    1006          25 :         return _transaction.performQueryListField(_queries, *_field);
    1007             : }
    1008             : 
    1009         200 : ResourceArray::ResourceArray(const Transaction &a, QueryList &&q, const Field *prop)
    1010         200 : : ResourceProperty(a, move(q), prop) {
    1011         200 :         _type = ResourceType::Array;
    1012         200 : }
    1013             : 
    1014          50 : bool ResourceArray::prepareUpdate() {
    1015          50 :         return true;
    1016             : }
    1017          75 : bool ResourceArray::prepareCreate() {
    1018          75 :         return true;
    1019             : }
    1020          25 : bool ResourceArray::prepareAppend() {
    1021          25 :         return true;
    1022             : }
    1023             : 
    1024         150 : static Value ResourceArray_extract(Value &data, StringView fieldName) {
    1025         150 :         Value arr;
    1026         150 :         if (data.isArray()) {
    1027          50 :                 arr = std::move(data);
    1028             :         } else {
    1029         100 :                 arr.addValue(move(data));
    1030             :         }
    1031         150 :         return arr;
    1032           0 : }
    1033             : 
    1034          50 : Value ResourceArray::updateObject(Value &data, Vector<db::InputFile> &) {
    1035          50 :         Value arr = ResourceArray_extract(data, _field->getName());
    1036             : 
    1037          50 :         if (!arr.isArray()) {
    1038           0 :                 _status = HTTP_BAD_REQUEST;
    1039           0 :                 return Value();
    1040             :         }
    1041             : 
    1042             :         // perform one-line update
    1043          50 :         if (auto id = getObjectId()) {
    1044         100 :                 return Worker(getScheme(), _transaction).setField(id, *_field, std::move(arr));
    1045             :         }
    1046           0 :         return Value();
    1047          50 : }
    1048         100 : Value ResourceArray::createObject(Value &data, Vector<db::InputFile> &) {
    1049         100 :         Value arr = ResourceArray_extract(data, _field->getName());
    1050             : 
    1051         100 :         if (!arr.isArray()) {
    1052           0 :                 _status = HTTP_BAD_REQUEST;
    1053           0 :                 return Value(false);
    1054             :         }
    1055             : 
    1056             :         // perform one-line update
    1057         100 :         if (auto id = getObjectId()) {
    1058         200 :                 return Worker(getScheme(), _transaction).appendField(id, *_field, move(arr));
    1059             :         }
    1060           0 :         return Value();
    1061         100 : }
    1062             : 
    1063          25 : Value ResourceArray::appendObject(Value &data) {
    1064          25 :         Vector<db::InputFile> empty;
    1065          50 :         return createObject(data, empty);
    1066          25 : }
    1067             : 
    1068          25 : Value ResourceArray::getResultObject() {
    1069          25 :         if (_field->hasFlag(db::Flags::Protected)) {
    1070           0 :                 _status = HTTP_NOT_FOUND;
    1071           0 :                 return Value();
    1072             :         }
    1073          25 :         return getDatabaseObject();
    1074             : }
    1075             : 
    1076          25 : Value ResourceArray::getDatabaseObject() {
    1077          25 :         return _transaction.performQueryListField(_queries, *_field);
    1078             : }
    1079             : 
    1080         150 : ResourceFieldObject::ResourceFieldObject(const Transaction &a, QueryList &&q)
    1081         150 : : ResourceObject(a, move(q)), _sourceScheme(_queries.getSourceScheme()), _field(_queries.getField()) {
    1082         150 :         _type = ResourceType::ObjectField;
    1083         150 : }
    1084             : 
    1085          25 : bool ResourceFieldObject::prepareUpdate() {
    1086          25 :         return true;
    1087             : }
    1088             : 
    1089          50 : bool ResourceFieldObject::prepareCreate() {
    1090          50 :         return true;
    1091             : }
    1092             : 
    1093             : SP_COVERAGE_TRIVIAL
    1094             : bool ResourceFieldObject::prepareAppend() {
    1095             :         return false;
    1096             : }
    1097             : 
    1098          25 : bool ResourceFieldObject::removeObject() {
    1099          25 :         auto id = getObjectId();
    1100          25 :         if (id == 0) {
    1101           0 :                 return false;
    1102             :         }
    1103             : 
    1104          50 :         return _transaction.perform([&, this] () -> bool {
    1105          25 :                 return doRemoveObject();
    1106          25 :         });
    1107             : }
    1108             : 
    1109          25 : Value ResourceFieldObject::updateObject(Value &val, Vector<db::InputFile> &files) {
    1110             :         // create or update object
    1111          25 :         Value ret;
    1112          25 :         _transaction.perform([&, this] () -> bool {
    1113          25 :                 if (getObjectId()) {
    1114          25 :                         ret = doUpdateObject(val, files);
    1115             :                 } else {
    1116           0 :                         ret = doCreateObject(val, files);
    1117             :                 }
    1118          25 :                 if (ret) {
    1119          25 :                         return true;
    1120             :                 }
    1121           0 :                 return false;
    1122             :         });
    1123          25 :         return ret;
    1124           0 : }
    1125             : 
    1126          50 : Value ResourceFieldObject::createObject(Value &val, Vector<db::InputFile> &files) {
    1127             :         // remove then recreate object
    1128          50 :         Value ret;
    1129          50 :         _transaction.perform([&, this] () -> bool {
    1130          50 :                 if (getObjectId()) {
    1131          25 :                         if (!doRemoveObject()) {
    1132           0 :                                 return false;
    1133             :                         }
    1134             :                 }
    1135          50 :                 ret = doCreateObject(val, files);
    1136          50 :                 if (ret) {
    1137          50 :                         return true;
    1138             :                 }
    1139           0 :                 return false;
    1140             :         });
    1141          50 :         return ret;
    1142           0 : }
    1143             : 
    1144             : SP_COVERAGE_TRIVIAL
    1145             : Value ResourceFieldObject::appendObject(Value &) {
    1146             :         return Value();
    1147             : }
    1148             : 
    1149         200 : int64_t ResourceFieldObject::getRootId() {
    1150         200 :         if (!_rootId) {
    1151         100 :                 auto ids = _transaction.performQueryListForIds(_queries, _queries.getItems().size() - 1);
    1152         100 :                 if (!ids.empty()) {
    1153         100 :                         _rootId = ids.front();
    1154             :                 }
    1155         100 :         }
    1156         200 :         return _rootId;
    1157             : }
    1158             : 
    1159         125 : int64_t ResourceFieldObject::getObjectId() {
    1160         125 :         if (!_objectId) {
    1161         100 :                 if (auto id = getRootId()) {
    1162         100 :                         if (auto obj = Worker(*_field->getSlot()->owner, _transaction).get(id, {_field->getName()})) {
    1163         100 :                                 _objectId = obj.getInteger(_field->getName());
    1164         100 :                         }
    1165             :                 }
    1166             :         }
    1167         125 :         return _objectId;
    1168             : }
    1169             : 
    1170          50 : bool ResourceFieldObject::doRemoveObject() {
    1171          50 :         return Worker(*_sourceScheme, _transaction).clearField(getRootId(), *_field);
    1172             : }
    1173             : 
    1174          25 : Value ResourceFieldObject::doUpdateObject(Value &val, Vector<db::InputFile> &files) {
    1175          25 :         encodeFiles(val, files);
    1176          50 :         return Worker(getScheme(), _transaction).update(getObjectId(), val);
    1177             : }
    1178             : 
    1179          50 : Value ResourceFieldObject::doCreateObject(Value &val, Vector<db::InputFile> &files) {
    1180          50 :         encodeFiles(val, files);
    1181         100 :         if (auto ret = Worker(getScheme(), _transaction).create(val)) {
    1182          50 :                 if (auto id = ret.getInteger("__oid")) {
    1183         150 :                         auto updObj = Worker(*_sourceScheme, _transaction).update(getRootId(), Value({
    1184         100 :                                 pair(_field->getName().str<Interface>(), Value(id))
    1185         150 :                         }));
    1186          50 :                         if (updObj) {
    1187          50 :                                 return ret;
    1188             :                         }
    1189          50 :                 }
    1190          50 :         }
    1191           0 :         return Value();
    1192             : }
    1193             : 
    1194             : 
    1195          75 : ResourceView::ResourceView(const Transaction &h, QueryList &&q)
    1196          75 : : ResourceSet(h, move(q)), _field(_queries.getField()) {
    1197          75 :         if (_queries.isDeltaApplicable()) {
    1198          50 :                 auto tag = _queries.getItems().front().query.getSingleSelectId();
    1199          50 :                 _delta = Time::microseconds(_transaction.getDeltaValue(*_queries.getSourceScheme(), *static_cast<const db::FieldView *>(_field->getSlot()), tag));
    1200             :         }
    1201          75 : }
    1202             : 
    1203             : SP_COVERAGE_TRIVIAL
    1204             : bool ResourceView::prepareUpdate() { return false; }
    1205             : 
    1206             : SP_COVERAGE_TRIVIAL
    1207             : bool ResourceView::prepareCreate() { return false; }
    1208             : 
    1209             : SP_COVERAGE_TRIVIAL
    1210             : bool ResourceView::prepareAppend() { return false; }
    1211             : 
    1212             : SP_COVERAGE_TRIVIAL
    1213             : bool ResourceView::removeObject() { return false; }
    1214             : 
    1215             : SP_COVERAGE_TRIVIAL
    1216             : Value ResourceView::updateObject(Value &data, Vector<db::InputFile> &) { return Value(); }
    1217             : 
    1218             : SP_COVERAGE_TRIVIAL
    1219             : Value ResourceView::createObject(Value &data, Vector<db::InputFile> &) { return Value(); }
    1220             : 
    1221          75 : Value ResourceView::getResultObject() {
    1222          75 :         auto ret = _transaction.performQueryListField(_queries, *_field);
    1223          75 :         if (!ret.isArray()) {
    1224          50 :                 return Value();
    1225             :         }
    1226             : 
    1227          25 :         return processResultList(_queries, ret);
    1228          75 : }
    1229             : 
    1230         225 : ResourceSearch::ResourceSearch(const Transaction &a, QueryList &&q, const Field *prop)
    1231         225 : : ResourceObject(a, move(q)), _field(prop) {
    1232         225 :         _type = ResourceType::Search;
    1233         225 : }
    1234             : 
    1235         225 : Value ResourceSearch::getResultObject() {
    1236         225 :         auto slot = _field->getSlot<db::FieldFullTextView>();
    1237         225 :         if (auto &searchData = _queries.getExtraData().getValue("search")) {
    1238         225 :                 auto q = slot->parseQuery(searchData);
    1239             : 
    1240         225 :                 if (!q.empty()) {
    1241         225 :                         _queries.setFullTextQuery(_field, db::FullTextQuery(q));
    1242         225 :                         auto ret = _transaction.performQueryList(_queries);
    1243         225 :                         if (!ret.isArray()) {
    1244           0 :                                 return Value();
    1245             :                         }
    1246             : 
    1247         225 :                         auto res = processResultList(_queries, ret);
    1248         225 :                         if (!res.empty()) {
    1249         225 :                                 if (auto &headlines = _queries.getExtraData().getValue("headlines")) {
    1250         175 :                                         auto ql = slot->searchConfiguration->stemQuery(q);
    1251         350 :                                         for (auto &it : res.asArray()) {
    1252         175 :                                                 makeHeadlines(it, headlines, ql);
    1253             :                                         }
    1254         175 :                                 }
    1255             :                         }
    1256         225 :                         return res;
    1257         225 :                 }
    1258         225 :         }
    1259           0 :         return Value();
    1260             : }
    1261             : 
    1262         175 : void ResourceSearch::makeHeadlines(Value &obj, const Value &headlineInfo, const Vector<String> &ql) {
    1263         175 :         auto &h = obj.emplace("__headlines");
    1264         350 :         for (auto &it : headlineInfo.asDict()) {
    1265         175 :                 auto d = getObjectLine(obj, it.first);
    1266         175 :                 if (d && d->isString()) {
    1267         175 :                         auto headStr = makeHeadline(d->getString(), it.second, ql);
    1268         175 :                         if (!headStr.empty()) {
    1269         175 :                                 h.setString(headStr, it.first);
    1270             :                         }
    1271         175 :                 }
    1272             :         }
    1273         175 : }
    1274             : 
    1275         175 : String ResourceSearch::makeHeadline(const StringView &value, const Value &headlineInfo, const Vector<String> &ql) {
    1276         175 :         auto slot = _field->getSlot<db::FieldFullTextView>();
    1277         175 :         stappler::search::Configuration::HeadlineConfig cfg;
    1278         175 :         if (headlineInfo.isString()) {
    1279          75 :                 if (headlineInfo.getString() == "plain") {
    1280          50 :                         cfg.startToken = StringView("<b>"); cfg.stopToken = StringView("</b>");
    1281          50 :                         return slot->searchConfiguration->makeHeadline(cfg, value, ql);
    1282          25 :                 } else if (headlineInfo.getString() == "html") {
    1283          25 :                         cfg.startToken = StringView("<b>"); cfg.stopToken = StringView("</b>");
    1284          25 :                         cfg.startFragment = StringView("<p>"); cfg.stopFragment = StringView("</p>");
    1285          25 :                         return slot->searchConfiguration->makeHtmlHeadlines(cfg, value, ql);
    1286             :                 }
    1287         100 :         } else if (headlineInfo.isDictionary()) {
    1288         100 :                 auto type = headlineInfo.getString("type");
    1289         100 :                 auto start = headlineInfo.getString("start");
    1290         100 :                 auto end = headlineInfo.getString("end");
    1291             : 
    1292         100 :                 if (type == "html") {
    1293          50 :                         if (!start.empty() && start.size() < 24 && !end.empty() && end.size() < 24) {
    1294          25 :                                 cfg.startToken = start; cfg.stopToken = end;
    1295             :                         } else {
    1296          25 :                                 cfg.startToken = StringView("<b>"); cfg.stopToken = StringView("</b>");
    1297             :                         }
    1298             : 
    1299          50 :                         auto fragStart = headlineInfo.getString("fragStart");
    1300          50 :                         auto fragEnd = headlineInfo.getString("fragStop");
    1301          50 :                         if (!fragStart.empty() && fragStart.size() < 24 && !fragEnd.empty() && fragEnd.size() < 24) {
    1302          25 :                                 cfg.startFragment = fragStart; cfg.stopFragment = fragEnd;
    1303             :                         } else {
    1304          25 :                                 cfg.startFragment = StringView("<p>"); cfg.stopFragment = StringView("</p>");
    1305             :                         }
    1306             : 
    1307          50 :                         cfg.maxWords = headlineInfo.getInteger("maxWords", search::Configuration::HeadlineConfig::DefaultMaxWords);
    1308          50 :                         cfg.minWords = headlineInfo.getInteger("minWords", search::Configuration::HeadlineConfig::DefaultMinWords);
    1309          50 :                         cfg.shortWord = headlineInfo.getInteger("shortWord", search::Configuration::HeadlineConfig::DefaultShortWord);
    1310             : 
    1311          50 :                         size_t frags = 1;
    1312          50 :                         auto fragments = headlineInfo.getValue("fragments");
    1313          50 :                         if (fragments.isString() && fragments.getString() == "max") {
    1314           0 :                                 frags = maxOf<size_t>();
    1315          50 :                         } else if (auto f = fragments.asInteger()) {
    1316          25 :                                 frags = f;
    1317             :                         }
    1318             : 
    1319          50 :                         return slot->searchConfiguration->makeHtmlHeadlines(cfg, value, ql, frags);
    1320          50 :                 } else {
    1321          50 :                         if (!start.empty() && start.size() < 24 && !end.empty() && end.size() < 24) {
    1322          25 :                                 cfg.startToken = start; cfg.stopToken = end;
    1323             :                         } else {
    1324          25 :                                 cfg.startToken = StringView("<b>"); cfg.stopToken = StringView("</b>");
    1325             :                         }
    1326             : 
    1327          50 :                         return slot->searchConfiguration->makeHeadline(cfg, value, ql);
    1328             :                 }
    1329         100 :         }
    1330           0 :         return String();
    1331         175 : }
    1332             : 
    1333         175 : const Value *ResourceSearch::getObjectLine(const Value &obj, const StringView &key) {
    1334         175 :         const Value *ptr = &obj;
    1335         175 :         key.split<StringView::Chars<'.'>>([&] (const StringView &k) {
    1336         350 :                 if (ptr && ptr->isDictionary()) {
    1337         350 :                         if (auto &v = ptr->getValue(k)) {
    1338         350 :                                 ptr = &v;
    1339             :                         }
    1340             :                 } else {
    1341           0 :                         ptr = nullptr;
    1342             :                 }
    1343         350 :         });
    1344         175 :         return ptr;
    1345             : }
    1346             : 
    1347             : }

Generated by: LCOV version 1.14