PageRenderTime 84ms CodeModel.GetById 17ms app.highlight 60ms RepoModel.GetById 2ms app.codeStats 0ms

/src/models/m_edge.erl

https://code.google.com/p/zotonic/
Erlang | 626 lines | 484 code | 71 blank | 71 comment | 20 complexity | 31a5608a128ef16bf4789fa69c8803df MD5 | raw file
  1%% @author Marc Worrell <marc@worrell.nl>
  2%% @copyright 2009 Marc Worrell
  3%% Date: 2009-04-09
  4%%
  5%% Copyright 2009 Marc Worrell
  6%%
  7%% Licensed under the Apache License, Version 2.0 (the "License");
  8%% you may not use this file except in compliance with the License.
  9%% You may obtain a copy of the License at
 10%% 
 11%%     http://www.apache.org/licenses/LICENSE-2.0
 12%% 
 13%% Unless required by applicable law or agreed to in writing, software
 14%% distributed under the License is distributed on an "AS IS" BASIS,
 15%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 16%% See the License for the specific language governing permissions and
 17%% limitations under the License.
 18
 19-module(m_edge).
 20-author("Marc Worrell <marc@worrell.nl").
 21
 22-behaviour(gen_model).
 23
 24%% interface functions
 25-export([
 26    m_find_value/3,
 27    m_to_list/2,
 28    m_value/2,
 29
 30    get/2,
 31    get_triple/2,
 32    get_id/4,
 33    get_edges/2,
 34    insert/4,
 35    delete/2,
 36    delete/4,
 37    delete_multiple/4,
 38    replace/4,
 39    duplicate/3,
 40    update_nth/5,
 41    object/4,
 42    subject/4,
 43    objects/3,
 44    subjects/3,
 45    objects/2,
 46    subjects/2,
 47    object_edge_ids/3,
 48    subject_edge_ids/3,
 49    update_sequence/4,
 50    update_sequence_edge_ids/4,
 51    object_predicates/2,
 52    subject_predicates/2,
 53    object_predicate_ids/2,
 54    subject_predicate_ids/2
 55]).
 56
 57-include_lib("zotonic.hrl").
 58
 59
 60%% @doc Fetch all object/edge ids for a subject/predicate
 61%% @spec m_find_value(Key, Source, Context) -> term()
 62m_find_value(o, #m{value=undefined}, _Context) ->
 63    fun(Id, _IdContext) ->
 64        fun(Pred, PredContext) ->
 65            object_edge_ids(Id, Pred, PredContext)
 66        end
 67    end;
 68
 69m_find_value(s, #m{value=undefined}, _Context) ->
 70    fun(Id, _IdContext) ->
 71        fun(Pred, PredContext) ->
 72            subject_edge_ids(Id, Pred, PredContext)
 73        end
 74    end;
 75
 76m_find_value(_Key, #m{}, _Context) ->
 77    undefined.
 78
 79%% @doc Transform a m_config value to a list, used for template loops
 80%% @spec m_to_list(Source, Context) -> List
 81m_to_list(#m{}, _Context) ->
 82    [].
 83    
 84%% @doc Transform a model value so that it can be formatted or piped through filters
 85%% @spec m_value(Source, Context) -> term()
 86m_value(#m{}, _Context) ->
 87    undefined.
 88
 89
 90%% @doc Get the complete edge with the id
 91get(Id, Context) ->
 92    z_db:assoc_row("select * from edge where id = $1", [Id], Context).
 93
 94%% @doc Get the edge as a triple {subject_id, predicate, object_id}
 95get_triple(Id, Context) ->
 96    {SubjectId, Predicate, ObjectId} = z_db:q_row("
 97            select e.subject_id, r.name, e.object_id 
 98            from edge e join rsc r on e.predicate_id = r.id 
 99            where e.id = $1", [Id], Context),
100    {SubjectId, z_convert:to_atom(Predicate), ObjectId}.
101
102%% @doc Get the edge id of a subject/pred/object combination
103get_id(SubjectId, Pred, ObjectId, Context) ->
104    PredId = m_predicate:name_to_id_check(Pred, Context),
105    z_db:q1("select id from edge where subject_id = $1 and object_id = $3 and predicate_id = $2", [SubjectId, PredId, ObjectId], Context).
106
107%% @doc Return the full description of all edges from a subject, grouped by predicate
108get_edges(SubjectId, Context) ->
109    case z_depcache:get({edges, SubjectId}, Context) of
110        {ok, Edges} -> 
111            Edges;
112        undefined ->
113            Edges = z_db:assoc("
114                select e.id, e.subject_id, e.predicate_id, p.name, e.object_id, e.seq 
115                from edge e join rsc p on p.id = e.predicate_id 
116                where e.subject_id = $1 
117                order by e.predicate_id, e.seq, e.id", [SubjectId], Context),
118            Edges1 = z_utils:group_proplists(name, Edges),
119            z_depcache:set({edges, SubjectId}, Edges1, ?DAY, [SubjectId], Context),
120            Edges1
121    end.
122
123%% Insert a new edge
124insert(SubjectId, PredId, ObjectId, Context) when is_integer(PredId) ->
125    case m_predicate:is_predicate(PredId, Context) of
126        true -> insert1(SubjectId, PredId, ObjectId, Context);
127        false -> throw({error, {unknown_predicate, PredId}})
128    end;
129insert(SubjectId, Pred, ObjectId, Context) ->
130    PredId = m_predicate:name_to_id_check(Pred, Context),
131    insert1(SubjectId, PredId, ObjectId, Context).
132    
133    insert1(SubjectId, PredId, ObjectId, Context) ->
134        case z_db:q1("select id from edge where subject_id = $1 and object_id = $2 and predicate_id = $3", [SubjectId, ObjectId, PredId], Context) of
135            undefined ->
136                F = fun(Ctx) ->
137                    m_rsc:touch(SubjectId, Ctx),
138                    z_db:insert(edge, [{subject_id, SubjectId}, {object_id, ObjectId}, {predicate_id, PredId}], Ctx)
139                end,
140                
141                {ok, PredName} = m_predicate:id_to_name(PredId, Context),
142                case z_acl:is_allowed(insert, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context) of
143                    true ->
144                        {ok, EdgeId} = z_db:transaction(F, Context),
145                        z_depcache:flush(SubjectId, Context),
146                        z_depcache:flush(ObjectId, Context),
147                        z_notifier:notify({edge_insert, SubjectId, PredName, ObjectId}, Context),
148                        {ok, EdgeId};
149                    AclError ->
150                        {error, {acl, AclError}}
151                end;
152            EdgeId ->
153                % Edge exists - skip
154                {ok, EdgeId}
155        end.
156
157
158%% @doc Delete an edge by Id
159delete(Id, Context) ->
160    {SubjectId, PredName, ObjectId} = get_triple(Id, Context),
161    case z_acl:is_allowed(delete, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context) of
162        true ->
163            F = fun(Ctx) ->
164                m_rsc:touch(SubjectId, Ctx),
165                z_db:delete(edge, Id, Ctx)
166            end,
167            
168            z_db:transaction(F, Context),
169            z_depcache:flush(SubjectId, Context),
170            z_depcache:flush(ObjectId, Context),
171            z_notifier:notify({edge_delete, SubjectId, PredName, ObjectId}, Context),
172            ok;
173        AclError -> 
174            {error, {acl, AclError}}
175    end.
176
177%% @doc Delete an edge by subject, object and predicate id
178delete(SubjectId, Pred, ObjectId, Context) ->
179    PredId = m_predicate:name_to_id_check(Pred, Context),
180    {ok, PredName} = m_predicate:id_to_name(PredId, Context),
181    case z_acl:is_allowed(delete, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context) of
182        true ->
183            F = fun(Ctx) ->
184                m_rsc:touch(SubjectId, Ctx),
185                z_db:q("delete from edge where subject_id = $1 and object_id = $2 and predicate_id = $3",  [SubjectId, ObjectId, PredId], Ctx)
186            end,
187
188            z_db:transaction(F, Context),
189            z_depcache:flush(SubjectId, Context),
190            z_depcache:flush(ObjectId, Context),
191            z_notifier:notify({edge_delete, SubjectId, PredName, ObjectId}, Context),
192            ok;
193        AclError ->
194            {error, {acl, AclError}}
195    end.
196
197
198%% @doc Delete multiple edges between the subject and the object
199delete_multiple(SubjectId, Preds, ObjectId, Context) ->
200    PredIds = [ m_predicate:name_to_id_check(Pred, Context) || Pred <- Preds ],
201    PredNames = [ m_predicate:id_to_name(PredId, Context) || PredId <- PredIds ],
202    Allowed = [ z_acl:is_allowed(delete, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context)
203                || {ok, PredName} <- PredNames ],
204    case is_allowed(Allowed) of
205        true ->
206            F = fun(Ctx) ->
207                m_rsc:touch(SubjectId, Ctx),
208                z_db:q("delete from edge where subject_id = $1 and object_id = $2 and predicate_id in ("
209                        ++ string:join(lists:map(fun erlang:integer_to_list/1, PredIds), ",")
210                        ++ ")",
211                        [SubjectId, ObjectId], Ctx)
212            end,
213
214            case z_db:transaction(F, Context) of
215                0 -> 
216                    ok;
217                N when is_integer(N) -> 
218                    z_depcache:flush(SubjectId, Context),
219                    z_depcache:flush(ObjectId, Context),
220                    [
221                        z_notifier:notify({edge_delete, SubjectId, PredName, ObjectId}, Context)
222                        || PredName <- PredNames
223                    ],
224                    ok;
225                Error -> 
226                    Error
227            end;
228        AclError ->
229            {error, {acl, AclError}}
230    end.
231        
232    is_allowed([]) -> true;
233    is_allowed([true|Rest]) -> is_allowed(Rest);
234    is_allowed([Error|_]) -> Error. 
235
236
237
238%% @doc Replace the objects with the new list
239replace(SubjectId, PredId, NewObjects, Context) when is_integer(PredId) ->
240    case m_predicate:is_predicate(PredId, Context) of
241        true -> replace1(SubjectId, PredId, NewObjects, Context);
242        false -> throw({error, {unknown_predicate, PredId}})
243    end;
244replace(SubjectId, Pred, NewObjects, Context) ->
245    PredId = m_predicate:name_to_id_check(Pred, Context),
246    replace1(SubjectId, PredId, NewObjects, Context).
247
248
249    replace1(SubjectId, PredId, NewObjects, Context) ->
250        {ok, PredName} = m_predicate:id_to_name(PredId, Context),
251        case objects(SubjectId, PredName, Context) of
252            NewObjects -> 
253                ok;
254
255            CurrObjects ->
256                % Check the ACL
257                Allowed1 = [ z_acl:is_allowed(delete, 
258                                             #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId},
259                                             Context)
260                            || ObjectId <- CurrObjects ],
261                Allowed2 = [ z_acl:is_allowed(insert, 
262                                             #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId},
263                                             Context)
264                            || ObjectId <- NewObjects ],
265
266                case is_allowed(Allowed1) andalso is_allowed(Allowed2) of
267                    true ->
268                        Values = string:join([
269                            lists:flatten([
270                                $(, integer_to_list(SubjectId),
271                                $,, integer_to_list(PredId),
272                                $,, integer_to_list(Id),
273                                $,, z_convert:to_list(z_acl:user(Context)),
274                                ",now())"
275                            ]) || Id <- NewObjects
276                        ], ","),
277                        F = fun(Ctx) ->
278                            z_db:q("delete from edge where subject_id = $1 and predicate_id = $2", 
279                                    [SubjectId, PredId], Ctx),
280                            z_db:q("insert into edge (subject_id, predicate_id, object_id, creator_id, created)
281                                    values "++Values, Ctx),
282                            m_rsc:touch(SubjectId, Ctx)
283                        end,
284            
285                        % Sync all caches, notify edge delete/insert listeners
286                        z_db:transaction(F, Context),
287                        z_db:flush(SubjectId),
288                        [
289                            case lists:member(ObjId, NewObjects) of
290                                true -> nop;
291                                false -> z_notifier:notify({edge_delete, SubjectId, PredName, ObjId}, Context)
292                            end
293                            || ObjId <- CurrObjects
294                        ],
295                        [
296                            case lists:member(ObjId, CurrObjects) of
297                                true -> nop;
298                                false -> z_notifier:notify({edge_insert, SubjectId, PredName, ObjId}, Context)
299                            end
300                            || ObjId <- NewObjects
301                        ],
302                        ok;
303                    AclError ->
304                        {error, {acl, AclError}}
305                end
306        end.
307
308
309%% @doc Duplicate all edges from one id to another id.  Skip all edges that give ACL errors.
310duplicate(Id, ToId, Context) ->
311    case z_acl:rsc_editable(Id, Context) of
312        true ->
313            F = fun(Ctx) ->
314                m_rsc:touch(ToId, Ctx),
315                Edges = z_db:q("select predicate_id, object_id, seq from edge where subject_id = $1", [Id], Ctx),
316                UserId = z_acl:user(Ctx),
317                [
318                    catch z_db:insert(edge, [{subject_id, ToId}, {predicate_id, PredId}, {object_id, ObjId}, {seq, Seq}, {creator_id, UserId}], Ctx)
319                    || {PredId, ObjId, Seq} <- Edges
320                ]
321            end,
322            z_db:transaction(F, Context),
323            z_depcache:flush(ToId, Context),
324            ok;
325        false ->
326            {error, {eacces, Id}}
327    end.
328    
329
330%% @doc Update the nth edge of a subject.  Set a new object, keep the predicate.
331%% When there are not enough edges then an error is returned. The first edge is nr 1.
332%% @spec update_nth(int(), Predicate, int(), ObjectId, Context) -> {ok, EdgeId} | {error, Reason}
333update_nth(SubjectId, Predicate, Nth, ObjectId, Context) ->
334    PredId = m_predicate:name_to_id_check(Predicate, Context),
335    {ok, PredName} = m_predicate:id_to_name(PredId, Context),
336    F = fun(Ctx) ->
337        case z_db:q("select id, object_id from edge where subject_id = $1 and predicate_id = $2 order by seq,id limit 1 offset $3", 
338                    [SubjectId, PredId, Nth-1], 
339                    Ctx) of
340            [] -> 
341                {error, enoent};
342            [{EdgeId,OldObjectId}] ->
343                case z_acl:is_allowed(delete, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=OldObjectId}, Ctx) of
344                    true ->
345                        1 = z_db:q("update edge set object_id = $1, creator_id = $3, created = now() where id = $2", 
346                                   [ObjectId,EdgeId,z_acl:user(Ctx)], 
347                                   Ctx),
348                        m_rsc:touch(SubjectId, Ctx),
349                        {ok, EdgeId};
350                    false ->
351                        {error, eacces}
352                end
353        end
354    end,
355    case z_acl:is_allowed(insert, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context) of
356        true -> 
357            case z_db:transaction(F, Context) of
358                {ok,EdgeId} ->
359                    z_depcache:flush(SubjectId, Context),
360                    z_notifier:notify({edge_insert, SubjectId, PredName, ObjectId}, Context),
361                    {ok, EdgeId};
362                {error, Reason} ->
363                    {error, Reason}
364            end;
365        false ->
366            {error, eacces}
367    end.
368
369
370%% @doc Return the Nth object with a certaing predicate of a subject.
371object(Id, Pred, N, Context) ->
372    Ids = objects(Id, Pred, Context),
373    try
374        lists:nth(N, Ids)
375    catch 
376        _:_ -> undefined
377    end.
378
379%% @doc Return the Nth subject with a certaing predicate of an object.
380subject(Id, Pred, N, Context) ->
381    Ids = subjects(Id, Pred, Context),
382    try
383        lists:nth(N, Ids)
384    catch 
385        _:_ -> undefined
386    end.
387
388%% @doc Return all object ids of an id with a certain predicate.  The order of the ids is deterministic.
389%% @spec objects(Id, Pred, Context) -> List
390objects(_Id, undefined, _Context) ->
391    [];
392objects(Id, Pred, Context) when is_integer(Pred) ->
393    case z_depcache:get({objects, Pred, Id}, Context) of
394        {ok, Objects} ->
395            Objects;
396        undefined ->
397            Ids = z_db:q("select object_id from edge where subject_id = $1 and predicate_id = $2 order by seq,id", [Id, Pred], Context),
398            Objects = [ ObjId || {ObjId} <- Ids ],
399            z_depcache:set({objects, Pred, Id}, Objects, ?DAY, [Id], Context),
400            Objects
401    end;
402objects(Id, Pred, Context) ->
403    case m_predicate:name_to_id(Pred, Context) of
404        {error, _} -> [];
405        {ok, PredId} -> objects(Id, PredId, Context)
406    end.
407
408
409%% @doc Return all subject ids of an object id with a certain predicate.   The order of the ids is deterministic.
410%% @spec subjects(Id, Pred, Context) -> List
411subjects(_Id, undefined, _Context) ->
412    [];
413subjects(Id, Pred, Context) when is_integer(Pred) ->
414    case z_depcache:get({subjects, Pred, Id}, Context) of
415        {ok, Objects} ->
416            Objects;
417        undefined ->
418            Ids = z_db:q("select subject_id from edge where object_id = $1 and predicate_id = $2 order by id", [Id, Pred], Context),
419            Subjects = [ SubjId || {SubjId} <- Ids ],
420            z_depcache:set({subjects, Pred, Id}, Subjects, ?HOUR, [Id], Context),
421            Subjects
422    end;
423subjects(Id, Pred, Context) ->
424    case m_predicate:name_to_id(Pred, Context) of
425        {error, _} -> [];
426        {ok, PredId} -> subjects(Id, PredId, Context)
427    end.
428
429
430%% @doc Return all object ids of the resource
431%% @spec objects(Id, Context) -> list()
432objects(Id, Context) ->
433    F = fun() ->
434        Ids = z_db:q("select object_id from edge where subject_id = $1 order by predicate_id, seq, id", [Id], Context),
435        [ ObjId || {ObjId} <- Ids]
436    end,
437    z_depcache:memo(F, {objects, Id}, ?DAY, [Id], Context).
438
439%% @doc Return all subject ids of the resource
440%% @spec subjects(Id, Context) -> list()
441subjects(Id, Context) ->
442    F = fun() ->
443        Ids = z_db:q("select subject_id from edge where object_id = $1 order by predicate_id, id", [Id], Context),
444        [ SubjId || {SubjId} <- Ids]
445    end,
446    z_depcache:memo(F, {subjects, Id}, ?HOUR, [Id], Context).
447
448
449%% @doc Return all object ids with the edge id for a predicate/subject_id
450%% @spec object_edge_ids(Id, Predicate, Context) -> list()
451object_edge_ids(Id, Predicate, Context) ->
452    case m_predicate:name_to_id(Predicate, Context) of
453        {ok, PredId} ->
454            F = fun() ->
455                z_db:q("select object_id, id from edge where subject_id = $1 and predicate_id = $2 order by seq, id", [Id, PredId], Context)
456            end,
457            z_depcache:memo(F, {object_edge_ids, Id, PredId}, ?DAY, [Id], Context);
458        {error, _} ->
459            []
460    end.
461
462
463%% @doc Return all subject ids with the edge id for a predicate/object_id
464%% @spec subject_edge_ids(Id, Predicate, Context) -> list()
465subject_edge_ids(Id, Predicate, Context) ->
466    case m_predicate:name_to_id(Predicate, Context) of
467        {ok, PredId} ->
468            F = fun() ->
469                z_db:q("select subject_id, id from edge where object_id = $1 and predicate_id = $2 order by seq, id", [Id, PredId], Context)
470            end,
471            z_depcache:memo(F, {subject_edge_ids, Id, PredId}, ?DAY, [Id], Context);
472        {error, _} ->
473            []
474    end.
475
476
477%% @doc Reorder the edges so that the mentioned ids are in front, in the listed order.
478%% @spec update_sequence(Id, Predicate, ObjectIds, Context) -> ok | {error, Reason}
479update_sequence(Id, Pred, ObjectIds, Context) ->
480    case z_acl:rsc_editable(Id, Context) of
481        true ->
482            PredId = m_predicate:name_to_id_check(Pred, Context),
483            F = fun(Ctx) ->
484                All = z_db:q("
485                            select object_id, id 
486                            from edge 
487                            where predicate_id = $1
488                              and subject_id = $2", [PredId, Id], Ctx),
489                
490                MissingIds = lists:foldl(
491                            fun({OId, _}, Acc) ->
492                                case lists:member(OId, ObjectIds) of
493                                    true -> Acc;
494                                    false -> [OId | Acc]
495                                end
496                            end,
497                            [],
498                            All),
499
500                SortedIds = ObjectIds ++ lists:reverse(MissingIds),
501                SortedEdgeIds = [ proplists:get_value(OId, All, -1) || OId <- SortedIds ],
502                z_db:update_sequence(edge, SortedEdgeIds, Ctx),
503                m_rsc:touch(Id, Ctx),
504                ok
505            end,
506            
507            Result = z_db:transaction(F, Context),
508            z_depcache:flush(Id, Context),
509            Result;
510        false ->
511            {error, eacces}
512    end.
513
514
515
516%% @doc Update the sequence for the given edge ids.  Optionally rename the predicate on the edge.
517%% @spec update_sequence_edge_ids(Id, Predicate, EdgeIds, Context) -> ok | {error, Reason}
518update_sequence_edge_ids(Id, Pred, EdgeIds, Context) ->
519    case z_acl:rsc_editable(Id, Context) of
520        true ->
521            PredId = m_predicate:name_to_id_check(Pred, Context),
522            F = fun(Ctx) ->
523                % Figure out which edge ids need to be renamed to this predicate.
524                Current = z_db:q("
525                            select id 
526                            from edge 
527                            where predicate_id = $1
528                              and subject_id = $2", [PredId, Id], Ctx),
529                CurrentIds = [ EdgeId || {EdgeId} <- Current ],
530
531                WrongPred = lists:foldl(
532                            fun(EdgeId, Acc) ->
533                                case lists:member(EdgeId, CurrentIds) of
534                                    true -> Acc;
535                                    false -> [EdgeId | Acc]
536                                end
537                            end,
538                            [],
539                            EdgeIds),
540
541                %% Remove the edges where we don't have permission to remove the old predicate and insert the new predicate.
542                {ok, PredName} = m_predicate:id_to_name(PredId, Ctx),
543                WrongPredAllowed = lists:filter(
544                                        fun (EdgeId) -> 
545                                            {Id, EdgePredName, EdgeObjectId} = get_triple(EdgeId, Ctx),
546                                            case z_acl:is_allowed(delete, 
547                                                                  #acl_edge{subject_id=Id, predicate=EdgePredName, object_id=EdgeObjectId}, 
548                                                                  Ctx) of
549                                                true ->
550                                                    case z_acl:is_allowed(insert, 
551                                                                          #acl_edge{subject_id=Id, predicate=PredName, object_id=EdgeObjectId},
552                                                                          Ctx) of
553                                                        true -> true;
554                                                        _ -> false
555                                                    end;
556                                                _ ->
557                                                    false
558                                            end
559                                        end, WrongPred),
560                
561                % Update the predicates on the edges that don't have the correct predicate.
562                % We have to make sure that the "wrong" edges do have the correct subject_id
563                Extra = lists:foldl(
564                                fun(EdgeId, Acc) ->
565                                    case z_db:q("update edge set predicate_id = $1 where id = $2 and subject_id = $3", [PredId, EdgeId, Id], Ctx) of
566                                        1 -> [EdgeId | Acc];
567                                        0 -> Acc
568                                    end
569                                end,
570                                [],
571                                WrongPredAllowed),
572                All = CurrentIds ++ Extra,
573                
574                %% Extract all edge ids that are not in our sort list, they go to the end of the new sequence
575                AppendToEnd = lists:foldl(
576                                fun(EdgeId, Acc) ->
577                                    case lists:member(EdgeId, EdgeIds) of
578                                        true -> Acc;
579                                        false -> [ EdgeId | Acc]
580                                    end
581                                end,
582                                [],
583                                All),
584                SortedEdgeIds = EdgeIds ++ lists:reverse(AppendToEnd),
585                z_db:update_sequence(edge, SortedEdgeIds, Ctx),
586                m_rsc:touch(Id, Ctx),
587                ok
588            end,
589
590            Result = z_db:transaction(F, Context),
591            z_depcache:flush(Id, Context),
592            Result;
593        false ->
594            {error, eacces}
595    end.
596
597
598%% @doc Return the list of predicates in use by edges to objects from the id
599%% @spec object_predicates(Id, Context) -> List
600object_predicates(Id, Context) ->
601    F = fun() ->
602        Ps = z_db:q("select distinct p.name from edge e join rsc p on e.predicate_id = p.id where e.subject_id = $1 order by name", [Id], Context),
603        [ list_to_atom(binary_to_list(P)) || {P} <- Ps ]
604    end,
605    z_depcache:memo(F, {object_preds, Id}, ?DAY, [Id], Context).
606
607%% @doc Return the list of predicates is use by edges from subjects to the id
608%% @spec subject_predicates(Id, Context) -> List
609subject_predicates(Id, Context) ->
610    F = fun() ->
611        Ps = z_db:q("select distinct p.name from edge e join rsc p on e.predicate_id = p.id where e.object_id = $1 order by name", [Id], Context),
612        [ list_to_atom(binary_to_list(P)) || {P} <- Ps ]
613    end,
614    z_depcache:memo(F, {subject_preds, Id}, ?DAY, [Id], Context).
615
616%% @doc Return the list of predicate ids in use by edges to objects from the id
617%% @spec object_predicate_ids(Id, Context) -> List
618object_predicate_ids(Id, Context) ->
619    Ps = z_db:q("select distinct predicate_id from edge where subject_id = $1", [Id], Context),
620    [ P || {P} <- Ps ].
621
622%% @doc Return the list of predicates is use by edges from subjects to the id
623%% @spec subject_predicate_ids(Id, Context) -> List
624subject_predicate_ids(Id, Context) ->
625    Ps = z_db:q("select distinct predicate_id from edge where object_id = $1", [Id], Context),
626    [ P || {P} <- Ps ].