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