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