PageRenderTime 74ms CodeModel.GetById 12ms app.highlight 55ms RepoModel.GetById 1ms app.codeStats 0ms

/src/models/m_edge.erl

http://github.com/zotonic/zotonic
Erlang | 862 lines | 687 code | 85 blank | 90 comment | 18 complexity | e7b73f20e8afda901e71c140726342a0 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    insert/5,
 36    delete/2,
 37    delete/4,
 38    delete/5,
 39    delete_multiple/4,
 40    replace/4,
 41    duplicate/3,
 42    merge/3,
 43    update_nth/5,
 44    object/4,
 45    subject/4,
 46    objects/3,
 47    subjects/3,
 48    objects/2,
 49    subjects/2,
 50    object_edge_ids/3,
 51    subject_edge_ids/3,
 52    object_edge_props/3,
 53    subject_edge_props/3,
 54    update_sequence/4,
 55    set_sequence/4,
 56    update_sequence_edge_ids/4,
 57    object_predicates/2,
 58    subject_predicates/2,
 59    object_predicate_ids/2,
 60    subject_predicate_ids/2
 61]).
 62
 63-include_lib("zotonic.hrl").
 64
 65
 66%% @doc Fetch all object/edge ids for a subject/predicate
 67%% @spec m_find_value(Key, Source, Context) -> term()
 68m_find_value(o, #m{value=undefined}, _Context) ->
 69    fun(Id, _IdContext) ->
 70        fun(Pred, PredContext) ->
 71            object_edge_ids(Id, Pred, PredContext)
 72        end
 73    end;
 74
 75m_find_value(o_props, #m{value=undefined}, _Context) ->
 76    fun(Id, _IdContext) ->
 77        fun(Pred, PredContext) ->
 78            object_edge_props(Id, Pred, PredContext)
 79        end
 80    end;
 81
 82m_find_value(s, #m{value=undefined}, _Context) ->
 83    fun(Id, _IdContext) ->
 84        fun(Pred, PredContext) ->
 85            subject_edge_ids(Id, Pred, PredContext)
 86        end
 87    end;
 88
 89m_find_value(s_props, #m{value=undefined}, _Context) ->
 90    fun(Id, _IdContext) ->
 91        fun(Pred, PredContext) ->
 92            subject_edge_props(Id, Pred, PredContext)
 93        end
 94    end;
 95
 96m_find_value(edges, #m{value=undefined}, _Context) ->
 97    fun(Id, IdContext) ->
 98        get_edges(Id, IdContext)
 99    end;
100
101%% m.edge.id[subject_id].predicatename[object_id] returns the
102%% corresponding edge id or undefined.
103m_find_value(id, #m{value=undefined}, _Context) ->
104    fun(SubjectId, _IdContext) ->
105            fun(Pred, _PredContext) ->
106                    fun(ObjectId, Context) ->
107                            z_depcache:memo(
108                              fun() ->
109                                      get_id(SubjectId, Pred, ObjectId, Context)
110                              end,
111                              {get_id, SubjectId, Pred, ObjectId}, ?DAY, [SubjectId], Context)
112                    end
113            end
114    end;
115
116m_find_value(_Key, #m{}, _Context) ->
117    undefined.
118
119%% @doc Transform a m_config value to a list, used for template loops
120%% @spec m_to_list(Source, Context) -> List
121m_to_list(#m{}, _Context) ->
122    [].
123    
124%% @doc Transform a model value so that it can be formatted or piped through filters
125%% @spec m_value(Source, Context) -> term()
126m_value(#m{}, _Context) ->
127    undefined.
128
129
130%% @doc Get the complete edge with the id
131get(Id, Context) ->
132    z_db:assoc_row("select * from edge where id = $1", [Id], Context).
133
134%% @doc Get the edge as a triple {subject_id, predicate, object_id}
135get_triple(Id, Context) ->
136    {SubjectId, Predicate, ObjectId} = z_db:q_row("
137            select e.subject_id, r.name, e.object_id 
138            from edge e join rsc r on e.predicate_id = r.id 
139            where e.id = $1", [Id], Context),
140    {SubjectId, z_convert:to_atom(Predicate), ObjectId}.
141
142%% @doc Get the edge id of a subject/pred/object combination
143get_id(SubjectId, PredId, ObjectId, Context) when is_integer(PredId) ->
144    z_db:q1("select id from edge where subject_id = $1 and object_id = $3 and predicate_id = $2", [SubjectId, PredId, ObjectId], Context);
145get_id(SubjectId, Pred, ObjectId, Context) ->
146    PredId = m_predicate:name_to_id_check(Pred, Context),
147    get_id(SubjectId, PredId, ObjectId, Context).
148
149%% @doc Return the full description of all edges from a subject, grouped by predicate
150get_edges(SubjectId, Context) ->
151    case z_depcache:get({edges, SubjectId}, Context) of
152        {ok, Edges} -> 
153            Edges;
154        undefined ->
155            Edges = z_db:assoc("
156                select e.id, e.subject_id, e.predicate_id, p.name, e.object_id,
157                       e.seq, e.created, e.creator_id
158                from edge e join rsc p on p.id = e.predicate_id 
159                where e.subject_id = $1 
160                order by e.predicate_id, e.seq, e.id", [SubjectId], Context),
161            Edges1 = z_utils:group_proplists(name, Edges),
162            z_depcache:set({edges, SubjectId}, Edges1, ?DAY, [SubjectId], Context),
163            Edges1
164    end.
165
166%% Insert a new edge
167insert(Subject, Pred, Object, Context) ->
168    insert(Subject, Pred, Object, [], Context).
169
170insert(SubjectId, PredId, ObjectId, Opts, Context) 
171  when is_integer(SubjectId), is_integer(PredId), is_integer(ObjectId) ->
172    case m_predicate:is_predicate(PredId, Context) of
173        true -> insert1(SubjectId, PredId, ObjectId,  Opts, Context);
174        false -> throw({error, {unknown_predicate, PredId}})
175    end;
176insert(SubjectId, Pred, ObjectId, Opts, Context)
177  when is_integer(SubjectId), is_integer(ObjectId) ->
178    PredId = m_predicate:name_to_id_check(Pred, Context),
179    insert1(SubjectId, PredId, ObjectId, Opts, Context);
180insert(SubjectId, Pred, Object, Opts, Context) when is_integer(SubjectId) ->
181    ObjectId = m_rsc:name_to_id_check(Object, Context),
182    insert(SubjectId, Pred, ObjectId, Opts, Context);
183insert(Subject, Pred, Object, Opts, Context) ->
184    SubjectId = m_rsc:name_to_id_check(Subject, Context),
185    insert(SubjectId, Pred, Object, Opts, Context).
186
187insert1(SubjectId, PredId, ObjectId, Opts, Context) ->
188case z_db:q1("select id 
189              from edge 
190              where subject_id = $1 
191                and object_id = $2
192                and predicate_id = $3",
193             [SubjectId, ObjectId, PredId], 
194             Context)
195of
196    undefined ->
197        F = fun(Ctx) ->
198                case z_convert:to_bool(proplists:get_value(no_touch, Opts, false)) of 
199                    true -> skip;
200                    false -> m_rsc:touch(SubjectId, Ctx)
201                end,
202                SeqOpt = case proplists:get_value(seq, Opts) of
203                            S when is_integer(S) -> [{seq, S}];
204                            undefined -> []
205                         end,
206                CreatedOpt = case proplists:get_value(created, Opts) of
207                                DT when is_tuple(DT) -> [{created,DT}];
208                                undefined -> []
209                             end,
210                EdgeProps = [
211                    {subject_id, SubjectId},
212                    {object_id, ObjectId},
213                    {predicate_id, PredId},
214                    {creator_id, case proplists:get_value(creator_id, Opts) of
215                                    undefined -> z_acl:user(Ctx);
216                                    CreatorId -> CreatorId
217                                 end}
218                    | (SeqOpt ++ CreatedOpt)
219                ],
220                z_db:insert(edge, EdgeProps, Ctx)
221            end,
222        {ok, PredName} = m_predicate:id_to_name(PredId, Context),
223        case z_acl:is_allowed(insert, 
224                              #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, 
225                              Context)
226        of
227            true ->
228                {ok, EdgeId} = z_db:transaction(F, Context),
229                z_edge_log_server:check(Context),
230                {ok, EdgeId};
231            AclError ->
232                {error, {acl, AclError}}
233        end;
234    EdgeId ->
235        % Edge exists - skip
236        {ok, EdgeId}
237end.
238
239
240%% @doc Delete an edge by Id
241delete(Id, Context) ->
242    {SubjectId, PredName, ObjectId} = get_triple(Id, Context),
243    case z_acl:is_allowed(delete, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context) of
244        true ->
245            F = fun(Ctx) ->
246                m_rsc:touch(SubjectId, Ctx),
247                z_db:delete(edge, Id, Ctx)
248            end,
249            
250            z_db:transaction(F, Context),
251            z_edge_log_server:check(Context),
252            ok;
253        AclError -> 
254            {error, {acl, AclError}}
255    end.
256
257%% @doc Delete an edge by subject, object and predicate id
258delete(SubjectId, Pred, ObjectId, Context) ->
259    delete(SubjectId, Pred, ObjectId, [], Context).
260
261delete(SubjectId, Pred, ObjectId, Options, Context) ->
262    PredId = m_predicate:name_to_id_check(Pred, Context),
263    {ok, PredName} = m_predicate:id_to_name(PredId, Context),
264    case z_acl:is_allowed(delete, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context) of
265        true ->
266            F = fun(Ctx) ->
267                case proplists:get_value(no_touch, Options) of
268                    true -> ok;
269                    _ -> m_rsc:touch(SubjectId, Ctx)
270                end,
271                z_db:q("delete from edge where subject_id = $1 and object_id = $2 and predicate_id = $3",  [SubjectId, ObjectId, PredId], Ctx)
272            end,
273
274            z_db:transaction(F, Context),
275            z_edge_log_server:check(Context),
276            ok;
277        AclError ->
278            {error, {acl, AclError}}
279    end.
280
281
282%% @doc Delete multiple edges between the subject and the object
283delete_multiple(SubjectId, Preds, ObjectId, Context) ->
284    PredIds = [ m_predicate:name_to_id_check(Pred, Context) || Pred <- Preds ],
285    PredNames = [ m_predicate:id_to_name(PredId, Context) || PredId <- PredIds ],
286    Allowed = [ z_acl:is_allowed(delete, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context)
287                || {ok, PredName} <- PredNames ],
288    case is_allowed(Allowed) of
289        true ->
290            F = fun(Ctx) ->
291                m_rsc:touch(SubjectId, Ctx),
292                z_db:q("delete 
293                        from edge 
294                        where subject_id = $1 
295                          and object_id = $2
296                          and predicate_id in (SELECT(unnest($3::int[])))",
297                       [SubjectId, ObjectId, PredIds], Ctx)
298            end,
299
300            case z_db:transaction(F, Context) of
301                0 -> 
302                    ok;
303                N when is_integer(N) -> 
304                    z_edge_log_server:check(Context),
305                    ok;
306                Error -> 
307                    Error
308            end;
309        AclError ->
310            {error, {acl, AclError}}
311    end.
312
313is_allowed([]) -> true;
314is_allowed([true|Rest]) -> is_allowed(Rest);
315is_allowed([Error|_]) -> Error. 
316
317
318
319%% @doc Replace the objects with the new list
320replace(SubjectId, PredId, NewObjects, Context) when is_integer(PredId) ->
321    case m_predicate:is_predicate(PredId, Context) of
322        true -> replace1(SubjectId, PredId, NewObjects, Context);
323        false -> throw({error, {unknown_predicate, PredId}})
324    end;
325replace(SubjectId, Pred, NewObjects, Context) ->
326    PredId = m_predicate:name_to_id_check(Pred, Context),
327    replace1(SubjectId, PredId, NewObjects, Context).
328
329
330    replace1(SubjectId, PredId, NewObjects, Context) ->
331        {ok, PredName} = m_predicate:id_to_name(PredId, Context),
332        case objects(SubjectId, PredId, Context) of
333            NewObjects -> 
334                ok;
335
336            CurrObjects ->
337                % Check the ACL
338                Allowed1 = [ z_acl:is_allowed(delete, 
339                                             #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId},
340                                             Context)
341                            || ObjectId <- CurrObjects -- NewObjects ],
342                Allowed2 = [ z_acl:is_allowed(insert, 
343                                             #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId},
344                                             Context)
345                            || ObjectId <- NewObjects -- CurrObjects ],
346
347                case is_allowed(Allowed1) andalso is_allowed(Allowed2) of
348                    true ->
349                        Result = set_sequence(SubjectId, PredId, NewObjects, Context),
350                        z_edge_log_server:check(Context),
351                        Result;
352                    false ->
353                        {error, eacces}
354                end
355        end.
356
357
358%% @doc Duplicate all edges from one id to another id. Skip all edges that give ACL errors.
359duplicate(Id, ToId, Context) ->
360    case z_acl:rsc_editable(Id, Context) andalso z_acl:rsc_editable(ToId, Context) of
361        true ->
362            F = fun(Ctx) ->
363                m_rsc:touch(ToId, Ctx),
364                FromEdges = z_db:q("
365                                select predicate_id, object_id, seq 
366                                from edge 
367                                where subject_id = $1
368                                order by seq, id",
369                                [Id],
370                                Ctx),
371                ToEdges = z_db:q("select predicate_id, object_id from edge where subject_id = $1", [ToId], Ctx),
372                FromEdges1 = lists:filter(
373                                fun({PredId, ObjectId, _Seq}) ->
374                                    Pair = {PredId, ObjectId},
375                                    not lists:member(Pair, ToEdges)
376                                end,
377                                FromEdges),
378                FromEdges2 = lists:filter(
379                                fun({PredId, ObjectId, _Seq}) ->
380                                    {ok, PredName} = m_predicate:id_to_name(PredId, Context),
381                                    z_acl:is_allowed(
382                                        insert, 
383                                        #acl_edge{subject_id=ToId, predicate=PredName, object_id=ObjectId}, 
384                                        Context)
385                                end,
386                                FromEdges1),
387                UserId = z_acl:user(Ctx),
388                lists:foreach(
389                        fun({PredId, ObjectId, Seq}) ->
390                            z_db:insert(
391                                    edge,
392                                    [{subject_id, ToId},
393                                     {predicate_id, PredId}, 
394                                     {object_id, ObjectId},
395                                     {seq, Seq},
396                                     {creator_id, UserId}],
397                                    Ctx)
398                        end,
399                        FromEdges2)
400            end,
401            z_db:transaction(F, Context),
402            z_edge_log_server:check(Context),
403            ok;
404        false ->
405            {error, {eacces, Id}}
406    end.
407
408%% @doc Move all edges from one id to another id, part of m_rsc:merge_delete/3
409merge(WinnerId, LooserId, Context) ->
410    case {z_acl:rsc_editable(WinnerId, Context), z_acl:rsc_deletable(LooserId, Context)} of
411        {true, true} ->
412            F = fun(Ctx) ->
413                %% Edges outgoing from the looser
414                LooserOutEdges = z_db:q("select predicate_id, object_id, id
415                                         from edge 
416                                         where subject_id = $1",
417                                        [LooserId],
418                                        Ctx),
419                WinnerOutEdges = z_db:q("select predicate_id, object_id 
420                                         from edge
421                                         where subject_id = $1", 
422                                        [WinnerId],
423                                        Ctx),
424                LooserOutEdges1 = lists:filter(
425                                fun({PredId, ObjectId, _EdgeId}) ->
426                                    not lists:member({PredId, ObjectId}, WinnerOutEdges)
427                                end,
428                                LooserOutEdges),
429                % TODO: discuss if we should enact these extra ACL checks
430                % LooserOutEdges2 = lists:filter(
431                %                 fun({PredId, ObjectId, _EdgeId}) ->
432                %                     {ok, PredName} = m_predicate:id_to_name(PredId, Context),
433                %                     z_acl:is_allowed(
434                %                         insert, 
435                %                         #acl_edge{subject_id=WinnerId, predicate=PredName, object_id=ObjectId}, 
436                %                         Context)
437                %                 end,
438                %                 LooserOutEdges1),
439                lists:foreach(
440                        fun({_PredId, _ObjId, EdgeId}) ->
441                            z_db:q("update edge 
442                                    set subject_id = $1 
443                                    where id = $2", 
444                                   [WinnerId, EdgeId],
445                                   Context)
446                        end,
447                        LooserOutEdges1),
448
449                %% Edges incoming to the looser
450                LooserInEdges = z_db:q("select predicate_id, subject_id, id
451                                        from edge 
452                                        where object_id = $1",
453                                       [LooserId],
454                                       Ctx),
455                WinnerInEdges = z_db:q("select predicate_id, subject_id 
456                                        from edge 
457                                        where object_id = $1",
458                                       [WinnerId],
459                                       Ctx),
460                LooserInEdges1 = lists:filter(
461                                    fun({PredId, SubjectId, _EdgeId}) ->
462                                        not lists:member({PredId, SubjectId}, WinnerInEdges)
463                                    end,
464                                    LooserInEdges),
465                lists:foreach(
466                        fun({_PredId, _SubjId, EdgeId}) ->
467                            z_db:q("update edge 
468                                    set object_id = $1 
469                                    where id = $2", 
470                                   [WinnerId, EdgeId],
471                                   Context)
472                        end,
473                        LooserInEdges1),
474
475                z_db:q("update edge set creator_id = $1 where creator_id = $2",
476                       [WinnerId, LooserId],
477                       Context)
478            end,
479            z_db:transaction(F, Context),
480            z_edge_log_server:check(Context),
481            ok;
482        {false, _} ->
483            {error, {eacces, WinnerId}};
484        {_, false} ->
485            {error, {eacces, WinnerId}}
486    end.
487
488
489%% @doc Update the nth edge of a subject.  Set a new object, keep the predicate.
490%% When there are not enough edges then an error is returned. The first edge is nr 1.
491%% @spec update_nth(int(), Predicate, int(), ObjectId, Context) -> {ok, EdgeId} | {error, Reason}
492update_nth(SubjectId, Predicate, Nth, ObjectId, Context) ->
493    PredId = m_predicate:name_to_id_check(Predicate, Context),
494    {ok, PredName} = m_predicate:id_to_name(PredId, Context),
495    F = fun(Ctx) ->
496        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", 
497                    [SubjectId, PredId, Nth-1], 
498                    Ctx) of
499            [] -> 
500                {error, enoent};
501            [{EdgeId,OldObjectId}] ->
502                case z_acl:is_allowed(delete, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=OldObjectId}, Ctx) of
503                    true ->
504                        1 = z_db:q("update edge set object_id = $1, creator_id = $3, created = now() where id = $2", 
505                                   [ObjectId,EdgeId,z_acl:user(Ctx)], 
506                                   Ctx),
507                        m_rsc:touch(SubjectId, Ctx),
508                        {ok, EdgeId};
509                    false ->
510                        {error, eacces}
511                end
512        end
513    end,
514    case z_acl:is_allowed(insert, #acl_edge{subject_id=SubjectId, predicate=PredName, object_id=ObjectId}, Context) of
515        true -> 
516            case z_db:transaction(F, Context) of
517                {ok,EdgeId} ->
518                    z_edge_log_server:check(Context),
519                    {ok, EdgeId};
520                {error, Reason} ->
521                    {error, Reason}
522            end;
523        false ->
524            {error, eacces}
525    end.
526
527
528%% @doc Return the Nth object with a certain predicate of a subject.
529object(Id, Pred, N, Context) ->
530    Ids = objects(Id, Pred, Context),
531    try
532        lists:nth(N, Ids)
533    catch 
534        _:_ -> undefined
535    end.
536
537%% @doc Return the Nth subject with a certain predicate of an object.
538subject(Id, Pred, N, Context) ->
539    Ids = subjects(Id, Pred, Context),
540    try
541        lists:nth(N, Ids)
542    catch 
543        _:_ -> undefined
544    end.
545
546%% @doc Return all object ids of an id with a certain predicate. The order of the ids is deterministic.
547%% @spec objects(Id, Pred, Context) -> List
548objects(_Id, undefined, _Context) ->
549    [];
550objects(Id, Pred, Context) when is_integer(Pred) ->
551    case z_depcache:get({objects, Pred, Id}, Context) of
552        {ok, Objects} ->
553            Objects;
554        undefined ->
555            Ids = z_db:q("select object_id from edge where subject_id = $1 and predicate_id = $2 order by seq,id", [Id, Pred], Context),
556            Objects = [ ObjId || {ObjId} <- Ids ],
557            z_depcache:set({objects, Pred, Id}, Objects, ?DAY, [Id], Context),
558            Objects
559    end;
560objects(Id, Pred, Context) ->
561    case m_predicate:name_to_id(Pred, Context) of
562        {error, _} -> [];
563        {ok, PredId} -> objects(Id, PredId, Context)
564    end.
565
566
567%% @doc Return all subject ids of an object id with a certain predicate.   The order of the ids is deterministic.
568%% @spec subjects(Id, Pred, Context) -> List
569subjects(_Id, undefined, _Context) ->
570    [];
571subjects(Id, Pred, Context) when is_integer(Pred) ->
572    case z_depcache:get({subjects, Pred, Id}, Context) of
573        {ok, Objects} ->
574            Objects;
575        undefined ->
576            Ids = z_db:q("select subject_id from edge where object_id = $1 and predicate_id = $2 order by id", [Id, Pred], Context),
577            Subjects = [ SubjId || {SubjId} <- Ids ],
578            z_depcache:set({subjects, Pred, Id}, Subjects, ?HOUR, [Id], Context),
579            Subjects
580    end;
581subjects(Id, Pred, Context) ->
582    case m_predicate:name_to_id(Pred, Context) of
583        {error, _} -> [];
584        {ok, PredId} -> subjects(Id, PredId, Context)
585    end.
586
587
588%% @doc Return all object ids of the resource
589%% @spec objects(Id, Context) -> list()
590objects(Id, Context) ->
591    F = fun() ->
592        Ids = z_db:q("select object_id from edge where subject_id = $1 order by predicate_id, seq, id", [Id], Context),
593        [ ObjId || {ObjId} <- Ids]
594    end,
595    z_depcache:memo(F, {objects, Id}, ?DAY, [Id], Context).
596
597%% @doc Return all subject ids of the resource
598%% @spec subjects(Id, Context) -> list()
599subjects(Id, Context) ->
600    F = fun() ->
601        Ids = z_db:q("select subject_id from edge where object_id = $1 order by predicate_id, id", [Id], Context),
602        [ SubjId || {SubjId} <- Ids]
603    end,
604    z_depcache:memo(F, {subjects, Id}, ?HOUR, [Id], Context).
605
606
607%% @doc Return all object ids with the edge id for a predicate/subject_id
608%% @spec object_edge_ids(Id, Predicate, Context) -> list()
609object_edge_ids(Id, Predicate, Context) ->
610    case m_predicate:name_to_id(Predicate, Context) of
611        {ok, PredId} ->
612            F = fun() ->
613                z_db:q("select object_id, id from edge where subject_id = $1 and predicate_id = $2 order by seq, id", [Id, PredId], Context)
614            end,
615            z_depcache:memo(F, {object_edge_ids, Id, PredId}, ?DAY, [Id], Context);
616        {error, _} ->
617            []
618    end.
619
620
621%% @doc Return all subject ids with the edge id for a predicate/object_id
622%% @spec subject_edge_ids(Id, Predicate, Context) -> list()
623subject_edge_ids(Id, Predicate, Context) ->
624    case m_predicate:name_to_id(Predicate, Context) of
625        {ok, PredId} ->
626            F = fun() ->
627                z_db:q("select subject_id, id from edge where object_id = $1 and predicate_id = $2 order by seq, id", [Id, PredId], Context)
628            end,
629            z_depcache:memo(F, {subject_edge_ids, Id, PredId}, ?DAY, [Id], Context);
630        {error, _} ->
631            []
632    end.
633
634
635%% @doc Return all object ids with edge properties
636-spec object_edge_props(integer(), binary()|list()|atom()|integer(), #context{}) -> list().
637object_edge_props(Id, Predicate, Context) ->
638    case m_predicate:name_to_id(Predicate, Context) of
639        {ok, PredId} ->
640            F = fun() ->
641                z_db:assoc("
642                        select *
643                        from edge
644                        where subject_id = $1
645                          and predicate_id = $2
646                        order by seq, id", 
647                       [Id, PredId], 
648                       Context)
649            end,
650            z_depcache:memo(F, {object_edge_props, Id, PredId}, ?DAY, [Id], Context);
651        {error, _} ->
652            []
653    end.
654
655%% @doc Return all subject ids with the edge properties
656-spec subject_edge_props(integer(), binary()|list()|atom()|integer(), #context{}) -> list().
657subject_edge_props(Id, Predicate, Context) ->
658    case m_predicate:name_to_id(Predicate, Context) of
659        {ok, PredId} ->
660            F = fun() ->
661                z_db:assoc("
662                        select *
663                        from edge 
664                        where object_id = $1 
665                          and predicate_id = $2 
666                        order by seq, id", 
667                       [Id, PredId], 
668                       Context)
669            end,
670            z_depcache:memo(F, {subject_edge_props, Id, PredId}, ?DAY, [Id], Context);
671        {error, _} ->
672            []
673    end.
674
675
676%% @doc Reorder the edges so that the mentioned ids are in front, in the listed order.
677%% @spec update_sequence(Id, Predicate, ObjectIds, Context) -> ok | {error, Reason}
678update_sequence(Id, Pred, ObjectIds, Context) ->
679    case z_acl:rsc_editable(Id, Context) of
680        true ->
681            PredId = m_predicate:name_to_id_check(Pred, Context),
682            F = fun(Ctx) ->
683                All = z_db:q("
684                            select object_id, id 
685                            from edge 
686                            where predicate_id = $1
687                              and subject_id = $2", [PredId, Id], Ctx),
688                
689                MissingIds = lists:foldl(
690                            fun({OId, _}, Acc) ->
691                                case lists:member(OId, ObjectIds) of
692                                    true -> Acc;
693                                    false -> [OId | Acc]
694                                end
695                            end,
696                            [],
697                            All),
698
699                SortedIds = ObjectIds ++ lists:reverse(MissingIds),
700                SortedEdgeIds = [ proplists:get_value(OId, All, -1) || OId <- SortedIds ],
701                z_db:update_sequence(edge, SortedEdgeIds, Ctx),
702                m_rsc:touch(Id, Ctx),
703                ok
704            end,
705            
706            Result = z_db:transaction(F, Context),
707            z_depcache:flush(Id, Context),
708            Result;
709        false ->
710            {error, eacces}
711    end.
712
713
714%% @doc Set edges order so that the specified object ids are in given order.
715%% Any extra edges not specified will be deleted, and any missing edges will be inserted.
716%% @spec set_sequence(Id, Predicate, ObjectIds, Context) -> ok | {error, Reason}
717set_sequence(Id, Pred, ObjectIds, Context) ->
718    case z_acl:rsc_editable(Id, Context) of
719        true ->
720            PredId = m_predicate:name_to_id_check(Pred, Context),
721            F = fun(Ctx) ->
722                        All = z_db:q("
723                            select object_id, id
724                            from edge
725                            where predicate_id = $1
726                              and subject_id = $2", [PredId, Id], Ctx),
727
728                        [ delete(EdgeId, Context) || {ObjectId, EdgeId} <- All, not lists:member(ObjectId, ObjectIds) ],
729                        NewEdges = [ begin
730                                         {ok, EdgeId} = insert(Id, Pred, ObjectId, Context),
731                                         {ObjectId, EdgeId}
732                                     end
733                                     || ObjectId <- ObjectIds,
734                                        not lists:member(ObjectId, All)
735                                   ],
736
737                        AllEdges = All ++ NewEdges,
738                        SortedEdgeIds = [ proplists:get_value(OId, AllEdges, -1) || OId <- ObjectIds ],
739                        z_db:update_sequence(edge, SortedEdgeIds, Ctx),
740                        m_rsc:touch(Id, Ctx),
741                        ok
742                end,
743
744            Result = z_db:transaction(F, Context),
745            z_depcache:flush(Id, Context),
746            Result;
747        false ->
748            {error, eacces}
749    end.
750
751
752%% @doc Update the sequence for the given edge ids.  Optionally rename the predicate on the edge.
753%% @spec update_sequence_edge_ids(Id, Predicate, EdgeIds, Context) -> ok | {error, Reason}
754update_sequence_edge_ids(Id, Pred, EdgeIds, Context) ->
755    case z_acl:rsc_editable(Id, Context) of
756        true ->
757            PredId = m_predicate:name_to_id_check(Pred, Context),
758            F = fun(Ctx) ->
759                % Figure out which edge ids need to be renamed to this predicate.
760                Current = z_db:q("
761                            select id 
762                            from edge 
763                            where predicate_id = $1
764                              and subject_id = $2", [PredId, Id], Ctx),
765                CurrentIds = [ EdgeId || {EdgeId} <- Current ],
766
767                WrongPred = lists:foldl(
768                            fun(EdgeId, Acc) ->
769                                case lists:member(EdgeId, CurrentIds) of
770                                    true -> Acc;
771                                    false -> [EdgeId | Acc]
772                                end
773                            end,
774                            [],
775                            EdgeIds),
776
777                %% Remove the edges where we don't have permission to remove the old predicate and insert the new predicate.
778                {ok, PredName} = m_predicate:id_to_name(PredId, Ctx),
779                WrongPredAllowed = lists:filter(
780                                        fun (EdgeId) -> 
781                                            {Id, EdgePredName, EdgeObjectId} = get_triple(EdgeId, Ctx),
782                                            case z_acl:is_allowed(delete, 
783                                                                  #acl_edge{subject_id=Id, predicate=EdgePredName, object_id=EdgeObjectId}, 
784                                                                  Ctx) of
785                                                true ->
786                                                    case z_acl:is_allowed(insert, 
787                                                                          #acl_edge{subject_id=Id, predicate=PredName, object_id=EdgeObjectId},
788                                                                          Ctx) of
789                                                        true -> true;
790                                                        _ -> false
791                                                    end;
792                                                _ ->
793                                                    false
794                                            end
795                                        end, WrongPred),
796                
797                % Update the predicates on the edges that don't have the correct predicate.
798                % We have to make sure that the "wrong" edges do have the correct subject_id
799                Extra = lists:foldl(
800                                fun(EdgeId, Acc) ->
801                                    case z_db:q("update edge set predicate_id = $1 where id = $2 and subject_id = $3", [PredId, EdgeId, Id], Ctx) of
802                                        1 -> [EdgeId | Acc];
803                                        0 -> Acc
804                                    end
805                                end,
806                                [],
807                                WrongPredAllowed),
808                All = CurrentIds ++ Extra,
809                
810                %% Extract all edge ids that are not in our sort list, they go to the end of the new sequence
811                AppendToEnd = lists:foldl(
812                                fun(EdgeId, Acc) ->
813                                    case lists:member(EdgeId, EdgeIds) of
814                                        true -> Acc;
815                                        false -> [ EdgeId | Acc]
816                                    end
817                                end,
818                                [],
819                                All),
820                SortedEdgeIds = EdgeIds ++ lists:reverse(AppendToEnd),
821                z_db:update_sequence(edge, SortedEdgeIds, Ctx),
822                m_rsc:touch(Id, Ctx),
823                ok
824            end,
825
826            Result = z_db:transaction(F, Context),
827            z_depcache:flush(Id, Context),
828            Result;
829        false ->
830            {error, eacces}
831    end.
832
833
834%% @doc Return the list of predicates in use by edges to objects from the id
835%% @spec object_predicates(Id, Context) -> List
836object_predicates(Id, Context) ->
837    F = fun() ->
838        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),
839        [ list_to_atom(binary_to_list(P)) || {P} <- Ps ]
840    end,
841    z_depcache:memo(F, {object_preds, Id}, ?DAY, [Id], Context).
842
843%% @doc Return the list of predicates is use by edges from subjects to the id
844%% @spec subject_predicates(Id, Context) -> List
845subject_predicates(Id, Context) ->
846    F = fun() ->
847        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),
848        [ list_to_atom(binary_to_list(P)) || {P} <- Ps ]
849    end,
850    z_depcache:memo(F, {subject_preds, Id}, ?DAY, [Id], Context).
851
852%% @doc Return the list of predicate ids in use by edges to objects from the id
853%% @spec object_predicate_ids(Id, Context) -> List
854object_predicate_ids(Id, Context) ->
855    Ps = z_db:q("select distinct predicate_id from edge where subject_id = $1", [Id], Context),
856    [ P || {P} <- Ps ].
857
858%% @doc Return the list of predicates is use by edges from subjects to the id
859%% @spec subject_predicate_ids(Id, Context) -> List
860subject_predicate_ids(Id, Context) ->
861    Ps = z_db:q("select distinct predicate_id from edge where object_id = $1", [Id], Context),
862    [ P || {P} <- Ps ].