/src/install/z_install.erl

http://github.com/zotonic/zotonic · Erlang · 713 lines · 527 code · 105 blank · 81 comment · 23 complexity · 958cb84759ac41d21d8092cd9f63d1f5 MD5 · raw file

  1. %% @author Marc Worrell <marc@worrell.nl>
  2. %% @copyright 2009-2015 Marc Worrell
  3. %%
  4. %% @doc Install Zotonic, loads the datamodel into the database
  5. %% Assumes the database has already been created (which normally needs superuser permissions anyway)
  6. %%
  7. %% CREATE DATABASE zotonic WITH OWNER = zotonic ENCODING = 'UTF8';
  8. %% CREATE LANGUAGE "plpgsql";
  9. %% Copyright 2009-2015 Marc Worrell
  10. %%
  11. %% Licensed under the Apache License, Version 2.0 (the "License");
  12. %% you may not use this file except in compliance with the License.
  13. %% You may obtain a copy of the License at
  14. %%
  15. %% http://www.apache.org/licenses/LICENSE-2.0
  16. %%
  17. %% Unless required by applicable law or agreed to in writing, software
  18. %% distributed under the License is distributed on an "AS IS" BASIS,
  19. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20. %% See the License for the specific language governing permissions and
  21. %% limitations under the License.
  22. -module(z_install).
  23. -author("Marc Worrell <marc@worrell.nl").
  24. %% interface functions
  25. -export([
  26. install/1,
  27. hierarchy_table/0,
  28. hierarchy_index_1/0,
  29. hierarchy_index_2/0,
  30. medium_log_table/0,
  31. medium_update_function/0,
  32. medium_update_trigger/0,
  33. edge_log_table/0,
  34. edge_log_function/0,
  35. edge_log_trigger/0,
  36. rsc_page_path_log/0,
  37. rsc_page_path_log_fki/0
  38. ]).
  39. -include_lib("zotonic.hrl").
  40. %% @doc Install the database for the given host.
  41. %% @spec install(Host) -> ok
  42. install(Context) ->
  43. ok = z_db:transaction(
  44. fun(Context1) ->
  45. ok = install_sql_list(Context1, model_pgsql()),
  46. ok = z_install_data:install(z_context:site(Context1), Context1)
  47. end,
  48. Context),
  49. InstallData = fun() ->
  50. timer:sleep(200), %% give other processes some time to start
  51. %% install the default data for the skeleton the site is based on
  52. z_install_defaultdata:install(m_site:get(skeleton, Context), Context),
  53. %% renumber the category tree.
  54. m_category:renumber(Context)
  55. end,
  56. spawn(InstallData),
  57. ok.
  58. install_sql_list(Context, Model) ->
  59. C = z_db_pgsql:get_raw_connection(Context),
  60. [ {ok, [], []} = epgsql:squery(C, Sql) || Sql <- Model ],
  61. ok.
  62. %% @doc Return a list containing the SQL statements to build the database model
  63. model_pgsql() ->
  64. [
  65. % Table config
  66. % Holds all configuration keys
  67. "CREATE TABLE config
  68. (
  69. id serial NOT NULL,
  70. module character varying(80) NOT NULL DEFAULT 'zotonic'::character varying,
  71. key character varying(80) NOT NULL DEFAULT ''::character varying,
  72. value text NOT NULL DEFAULT ''::character varying,
  73. props bytea,
  74. created timestamp with time zone NOT NULL DEFAULT now(),
  75. modified timestamp with time zone NOT NULL DEFAULT now(),
  76. CONSTRAINT config_pkey PRIMARY KEY (id),
  77. CONSTRAINT config_module_key_key UNIQUE (module, key)
  78. )",
  79. % Table module
  80. % Holds install state of all known modules
  81. "CREATE TABLE module
  82. (
  83. id serial NOT NULL,
  84. name character varying(80) NOT NULL DEFAULT ''::character varying,
  85. uri character varying(2048) NOT NULL DEFAULT ''::character varying,
  86. is_active boolean NOT NULL DEFAULT false,
  87. created timestamp with time zone NOT NULL DEFAULT now(),
  88. modified timestamp with time zone NOT NULL DEFAULT now(),
  89. schema_version int NULL,
  90. CONSTRAINT module_pkey PRIMARY KEY (id),
  91. CONSTRAINT module_name_key UNIQUE (name)
  92. )",
  93. % Table: rsc
  94. % Holds all resources (posts, persons etc.)
  95. "CREATE TABLE rsc
  96. (
  97. id serial NOT NULL,
  98. uri character varying(2048),
  99. name character varying(80),
  100. page_path character varying(80),
  101. is_authoritative boolean NOT NULL DEFAULT true,
  102. is_published boolean NOT NULL DEFAULT false,
  103. is_featured boolean NOT NULL DEFAULT false,
  104. is_protected boolean NOT NULL DEFAULT false,
  105. is_dependent boolean NOT NULL DEFAULT false,
  106. publication_start timestamp with time zone NOT NULL DEFAULT now(),
  107. publication_end timestamp with time zone NOT NULL DEFAULT '9999-06-01 00:00:00'::timestamp with time zone,
  108. content_group_id int,
  109. creator_id int,
  110. modifier_id int,
  111. version int NOT NULL DEFAULT 1,
  112. category_id int NOT NULL,
  113. visible_for int NOT NULL DEFAULT 1, -- 0 = public, 1 = community, 2 = group
  114. slug character varying(80) NOT NULL DEFAULT ''::character varying,
  115. props bytea,
  116. created timestamp with time zone NOT NULL DEFAULT now(),
  117. modified timestamp with time zone NOT NULL DEFAULT now(),
  118. -- pivot fields for searching
  119. pivot_category_nr int,
  120. pivot_tsv tsvector, -- texts
  121. pivot_rtsv tsvector, -- related ids (cat, prop, rsc)
  122. pivot_first_name character varying(100),
  123. pivot_surname character varying(100),
  124. pivot_gender character varying(1),
  125. pivot_date_start timestamp with time zone,
  126. pivot_date_end timestamp with time zone,
  127. pivot_date_start_month_day int, -- used for birthdays
  128. pivot_date_end_month_day int, -- used for decease dates
  129. pivot_street character varying(120),
  130. pivot_city character varying(100),
  131. pivot_state character varying(50),
  132. pivot_postcode character varying(30),
  133. pivot_country character varying(80),
  134. pivot_geocode bigint,
  135. pivot_geocode_qhash bytea,
  136. pivot_title character varying(100),
  137. pivot_location_lat float,
  138. pivot_location_lng float,
  139. CONSTRAINT rsc_pkey PRIMARY KEY (id),
  140. CONSTRAINT rsc_uri_key UNIQUE (uri),
  141. CONSTRAINT rsc_name_key UNIQUE (name),
  142. CONSTRAINT rsc_page_path_key UNIQUE (page_path)
  143. )",
  144. "COMMENT ON COLUMN rsc.visible_for IS '0 = public, 1 = community, 2 = group'",
  145. "ALTER TABLE rsc ADD CONSTRAINT fk_rsc_content_group_id FOREIGN KEY (content_group_id)
  146. REFERENCES rsc (id)
  147. ON UPDATE CASCADE ON DELETE SET NULL",
  148. "ALTER TABLE rsc ADD CONSTRAINT fk_rsc_creator_id FOREIGN KEY (creator_id)
  149. REFERENCES rsc (id)
  150. ON UPDATE CASCADE ON DELETE SET NULL",
  151. "ALTER TABLE rsc ADD CONSTRAINT fk_rsc_modifier_id FOREIGN KEY (modifier_id)
  152. REFERENCES rsc (id)
  153. ON UPDATE CASCADE ON DELETE SET NULL",
  154. "CREATE INDEX fki_rsc_content_group_id ON rsc (content_group_id)",
  155. "CREATE INDEX fki_rsc_creator_id ON rsc (creator_id)",
  156. "CREATE INDEX fki_rsc_modifier_id ON rsc (modifier_id)",
  157. "CREATE INDEX fki_rsc_created ON rsc (created)",
  158. "CREATE INDEX fki_rsc_modified ON rsc (modified)",
  159. "CREATE INDEX rsc_pivot_tsv_key ON rsc USING gin(pivot_tsv)",
  160. "CREATE INDEX rsc_pivot_rtsv_key ON rsc USING gin(pivot_rtsv)",
  161. "CREATE INDEX rsc_pivot_category_nr ON rsc (pivot_category_nr)",
  162. "CREATE INDEX rsc_pivot_surname_key ON rsc (pivot_surname)",
  163. "CREATE INDEX rsc_pivot_first_name_key ON rsc (pivot_first_name)",
  164. "CREATE INDEX rsc_pivot_gender_key ON rsc (pivot_gender)",
  165. "CREATE INDEX rsc_pivot_date_start_key ON rsc (pivot_date_start)",
  166. "CREATE INDEX rsc_pivot_date_end_key ON rsc (pivot_date_end)",
  167. "CREATE INDEX rsc_pivot_date_start_month_day_key ON rsc (pivot_date_start_month_day)",
  168. "CREATE INDEX rsc_pivot_date_end_month_day_key ON rsc (pivot_date_end_month_day)",
  169. "CREATE INDEX rsc_pivot_city_street_key ON rsc (pivot_city, pivot_street)",
  170. "CREATE INDEX rsc_pivot_country_key ON rsc (pivot_country)",
  171. "CREATE INDEX rsc_pivot_postcode_key ON rsc (pivot_postcode)",
  172. "CREATE INDEX rsc_pivot_geocode_key ON rsc (pivot_geocode)",
  173. "CREATE INDEX rsc_pivot_title_key ON rsc (pivot_title)",
  174. "CREATE INDEX rsc_pivot_location_key ON rsc (pivot_location_lat, pivot_location_lng)",
  175. % Table: rsc_gone
  176. % Tracks deleted or moved resources, adding "410 gone" support
  177. % Also contains new id or new url for 301 moved permanently replies.
  178. % mod_backup is needed to recover a deleted resource's content.
  179. "CREATE TABLE rsc_gone (
  180. id bigint not null,
  181. new_id bigint,
  182. new_uri character varying(2048),
  183. version int not null,
  184. uri character varying(2048),
  185. name character varying(80),
  186. page_path character varying(80),
  187. is_authoritative boolean NOT NULL DEFAULT true,
  188. creator_id bigint,
  189. modifier_id bigint,
  190. created timestamp with time zone NOT NULL DEFAULT now(),
  191. modified timestamp with time zone NOT NULL DEFAULT now(),
  192. CONSTRAINT rsc_gone_pkey PRIMARY KEY (id)
  193. )",
  194. "CREATE INDEX rsc_gone_name ON rsc_gone(name)",
  195. "CREATE INDEX rsc_gone_page_path ON rsc_gone(page_path)",
  196. "CREATE INDEX rsc_gone_modified ON rsc_gone(modified)",
  197. % Table: protect
  198. % By making an entry in this table we protect a rsc from being deleted.
  199. % This table is maintained by the update/insert trigger.
  200. "CREATE TABLE protect (
  201. id int NOT NULL,
  202. CONSTRAINT protect_id PRIMARY KEY (id),
  203. CONSTRAINT fk_protect_id FOREIGN KEY (id)
  204. REFERENCES rsc(id)
  205. ON UPDATE CASCADE ON DELETE RESTRICT
  206. )",
  207. % Table: edge
  208. % All relations between resources, forming a directed graph
  209. "CREATE TABLE edge
  210. (
  211. id serial NOT NULL,
  212. subject_id int NOT NULL,
  213. predicate_id int NOT NULL,
  214. object_id int NOT NULL,
  215. seq int NOT NULL DEFAULT 1000000,
  216. creator_id int,
  217. created timestamp with time zone NOT NULL DEFAULT now(),
  218. CONSTRAINT edge_pkey PRIMARY KEY (id),
  219. CONSTRAINT edge_ops_key UNIQUE (object_id, predicate_id, subject_id),
  220. CONSTRAINT edge_spo_key UNIQUE (subject_id, predicate_id, object_id),
  221. CONSTRAINT fk_edge_subject_id FOREIGN KEY (subject_id)
  222. REFERENCES rsc (id)
  223. ON UPDATE CASCADE ON DELETE CASCADE,
  224. CONSTRAINT fk_edge_object_id FOREIGN KEY (object_id)
  225. REFERENCES rsc (id)
  226. ON UPDATE CASCADE ON DELETE CASCADE,
  227. CONSTRAINT fk_edge_predicate_id FOREIGN KEY (predicate_id)
  228. REFERENCES rsc (id)
  229. ON UPDATE CASCADE ON DELETE CASCADE,
  230. CONSTRAINT fk_edge_creator_id FOREIGN KEY (creator_id)
  231. REFERENCES rsc (id)
  232. ON UPDATE CASCADE ON DELETE SET NULL
  233. )",
  234. "CREATE INDEX fki_edge_subject_id ON edge (subject_id)",
  235. "CREATE INDEX fki_edge_predicate_id ON edge (predicate_id)",
  236. "CREATE INDEX fki_edge_object_id ON edge (object_id)",
  237. "CREATE INDEX fki_edge_creator_id ON edge (creator_id)",
  238. "CREATE INDEX edge_sp_seq_key ON edge (subject_id, predicate_id, seq)",
  239. % Table medium
  240. % Holds all references to media (files), used in the context of resources
  241. % Every medium is a resource.
  242. % The preview might have been generated from the original file and is always a jpeg.
  243. "CREATE TABLE medium
  244. (
  245. id int NOT NULL,
  246. filename character varying(400),
  247. rootname character varying(100),
  248. mime character varying(128) NOT NULL DEFAULT 'application/octet-stream'::character varying,
  249. width int NOT NULL DEFAULT 0,
  250. height int NOT NULL DEFAULT 0,
  251. orientation int NOT NULL DEFAULT 1,
  252. sha1 character varying(40),
  253. size int NOT NULL DEFAULT 0,
  254. preview_filename character varying(400),
  255. preview_width int NOT NULL DEFAULT 0,
  256. preview_height int NOT NULL DEFAULT 0,
  257. is_deletable_file boolean NOT NULL DEFAULT false,
  258. is_deletable_preview boolean NOT NULL DEFAULT false,
  259. props bytea,
  260. created timestamp with time zone NOT NULL DEFAULT now(),
  261. CONSTRAINT medium_pkey PRIMARY KEY (id),
  262. CONSTRAINT medium_filename_key UNIQUE (filename),
  263. CONSTRAINT fk_medium_rsc_id FOREIGN KEY (id)
  264. REFERENCES rsc (id)
  265. ON UPDATE CASCADE ON DELETE CASCADE
  266. )",
  267. "CREATE INDEX medium_rootname_key ON medium (rootname)",
  268. % Table: predicate_category
  269. % Defines which categories are valid for a predicate as subject or object
  270. "CREATE TABLE predicate_category
  271. (
  272. id serial NOT NULL,
  273. is_subject boolean NOT NULL DEFAULT true,
  274. predicate_id int NOT NULL,
  275. category_id int NOT NULL,
  276. CONSTRAINT predicate_category_pkey PRIMARY KEY (id),
  277. CONSTRAINT predicate_category_key UNIQUE (predicate_id, is_subject, category_id),
  278. CONSTRAINT fk_predicate_category_predicate_id FOREIGN KEY (predicate_id)
  279. REFERENCES rsc(id)
  280. ON UPDATE CASCADE
  281. ON DELETE CASCADE,
  282. CONSTRAINT fk_predicate_category_category_id FOREIGN KEY (category_id)
  283. REFERENCES rsc(id)
  284. ON UPDATE CASCADE
  285. ON DELETE CASCADE
  286. )",
  287. "CREATE INDEX fki_predicate_category_predicate_id ON predicate_category (predicate_id)",
  288. "CREATE INDEX fki_predicate_category_category_id ON predicate_category (category_id)",
  289. % Table persistent
  290. % Holds persistent information coupled to an user agent. Can be shopping cart, click history etc
  291. "CREATE TABLE persistent
  292. (
  293. id character varying(32) not null,
  294. props bytea,
  295. created timestamp with time zone NOT NULL DEFAULT now(),
  296. modified timestamp with time zone NOT NULL DEFAULT now(),
  297. CONSTRAINT persistent_pkey PRIMARY KEY (id)
  298. )",
  299. % Table identity
  300. % Identities of an user, used for authentication. Examples are password, openid, msn, xmpp etc.
  301. "CREATE TABLE identity
  302. (
  303. id serial NOT NULL,
  304. rsc_id int NOT NULL,
  305. type character varying(32) NOT NULL DEFAULT ''::character varying,
  306. key character varying(200) NOT NULL DEFAULT ''::character varying,
  307. is_unique boolean, -- set to true when the type/key should be unique
  308. is_verified boolean not null default false,
  309. verify_key character varying(32),
  310. propb bytea,
  311. prop1 character varying(200) NOT NULL DEFAULT ''::character varying,
  312. prop2 character varying(200) NOT NULL DEFAULT ''::character varying,
  313. prop3 character varying(200) NOT NULL DEFAULT ''::character varying,
  314. created timestamp with time zone NOT NULL DEFAULT now(),
  315. modified timestamp with time zone NOT NULL DEFAULT now(),
  316. visited timestamp with time zone,
  317. CONSTRAINT auth_pkey PRIMARY KEY (id),
  318. CONSTRAINT pk_auth_rsc_id FOREIGN KEY (rsc_id)
  319. REFERENCES rsc (id)
  320. ON UPDATE CASCADE ON DELETE CASCADE,
  321. CONSTRAINT identity_type_key_unique UNIQUE (type, key, is_unique),
  322. CONSTRAINT identity_verify_key_unique UNIQUE (verify_key)
  323. )",
  324. "CREATE INDEX fki_identity_rsc_id ON identity (rsc_id)",
  325. "CREATE INDEX identity_type_key_key ON identity (type, key)",
  326. "CREATE INDEX identity_visited_key ON identity (visited)",
  327. "CREATE INDEX identity_created_key ON identity (created)",
  328. % Email send queue and log
  329. "CREATE TABLE emailq (
  330. id serial NOT NULL,
  331. status character varying(10) not null default 'new', -- new, sent, fail
  332. retry_on timestamp with time zone default (now() + '00:10:00'::interval),
  333. retry int not null default 0,
  334. sender character varying(100),
  335. recipient character varying(100),
  336. props bytea,
  337. sent timestamp with time zone,
  338. created timestamp with time zone not null default now(),
  339. CONSTRAINT email_pkey PRIMARY KEY (id)
  340. )",
  341. "CREATE INDEX email_recipient_key ON emailq (recipient)",
  342. "CREATE INDEX email_created_key ON emailq (created)",
  343. "CREATE INDEX email_status_retry_key ON emailq (status, retry_on)",
  344. % pivot queue for rsc, all things that are updated are queued here for later full text indexing
  345. "CREATE TABLE rsc_pivot_queue
  346. (
  347. rsc_id int NOT NULL,
  348. serial int NOT NULL DEFAULT 1,
  349. due timestamp with time zone,
  350. is_update boolean NOT NULL default true,
  351. CONSTRAINT rsc_pivot_queue_pkey PRIMARY KEY (rsc_id),
  352. CONSTRAINT fk_rsc_pivot_queue_rsc_id FOREIGN KEY (rsc_id)
  353. REFERENCES rsc(id)
  354. ON UPDATE CASCADE ON DELETE CASCADE
  355. )",
  356. "CREATE INDEX fki_rsc_pivot_queue_rsc_id ON rsc_pivot_queue (rsc_id)",
  357. "CREATE INDEX fki_rsc_pivot_queue_due ON rsc_pivot_queue (is_update, due)",
  358. % queue for slow pivoting queries, for example syncing category nrs after the categories are changed.
  359. "CREATE TABLE pivot_task_queue
  360. (
  361. id serial NOT NULL,
  362. module character varying(30) NOT NULL,
  363. function character varying(30) NOT NULL,
  364. key character varying(100) NOT NULL DEFAULT ''::character varying,
  365. due timestamp with time zone ,
  366. props bytea,
  367. CONSTRAINT pivot_task_queue_pkey PRIMARY KEY (id),
  368. CONSTRAINT pivot_task_queue_module_funcion_key_key UNIQUE (module, function, key)
  369. )
  370. ",
  371. % Update/insert trigger on rsc to fill the update queue
  372. % The text indexing is delayed until the updates are stable
  373. % Also checks if the rsc is set to protected, if so makes an entry in the 'protect' table.
  374. "
  375. CREATE FUNCTION rsc_pivot_update() RETURNS trigger AS $$
  376. declare
  377. duetime timestamp;
  378. do_queue boolean;
  379. begin
  380. if (tg_op = 'INSERT') then
  381. do_queue := true;
  382. elseif (new.version <> old.version or new.modified <> old.modified) then
  383. do_queue := true;
  384. else
  385. do_queue := false;
  386. end if;
  387. if (do_queue) then
  388. <<insert_update_queue>>
  389. loop
  390. update rsc_pivot_queue
  391. set due = (case when now() < due then now() else due end),
  392. serial = serial + 1
  393. where rsc_id = new.id;
  394. exit insert_update_queue when found;
  395. begin
  396. insert into rsc_pivot_queue (rsc_id, due, is_update) values (new.id, now(), tg_op = 'UPDATE');
  397. exit insert_update_queue;
  398. exception
  399. when unique_violation then
  400. -- do nothing
  401. end;
  402. end loop insert_update_queue;
  403. end if;
  404. if (new.is_protected) then
  405. begin
  406. insert into protect (id) values (new.id);
  407. exception
  408. when unique_violation then
  409. -- do nothing
  410. end;
  411. else
  412. delete from protect where id = new.id;
  413. end if;
  414. return null;
  415. end;
  416. $$ LANGUAGE plpgsql
  417. ",
  418. "
  419. CREATE TRIGGER rsc_update_queue_trigger AFTER INSERT OR UPDATE
  420. ON rsc FOR EACH ROW EXECUTE PROCEDURE rsc_pivot_update()
  421. ",
  422. % Queue for deleted medium files, periodically checked for deleting files that are not referenced anymore
  423. "CREATE TABLE medium_deleted
  424. (
  425. id serial NOT NULL,
  426. filename character varying (400) NOT NULL,
  427. deleted timestamp with time zone NOT NULL default now(),
  428. CONSTRAINT medium_deleted_pkey PRIMARY KEY (id)
  429. )",
  430. "CREATE INDEX medium_deleted_deleted_key ON medium_deleted (deleted)",
  431. % Update/insert trigger on medium to fill the deleted files queue
  432. "
  433. CREATE FUNCTION medium_delete() RETURNS trigger AS $$
  434. begin
  435. if (tg_op = 'DELETE') then
  436. if (old.filename <> '' and old.filename is not null and old.is_deletable_file) then
  437. insert into medium_deleted (filename) values (old.filename);
  438. end if;
  439. if (old.preview_filename <> '' and old.preview_filename is not null and old.is_deletable_preview) then
  440. insert into medium_deleted (filename) values (old.preview_filename);
  441. end if;
  442. end if;
  443. return null;
  444. end;
  445. $$ LANGUAGE plpgsql
  446. ",
  447. "
  448. CREATE TRIGGER medium_deleted_trigger AFTER DELETE
  449. ON medium FOR EACH ROW EXECUTE PROCEDURE medium_delete()
  450. ",
  451. %% Table with hierarchies for menus and the category tree
  452. hierarchy_table(),
  453. hierarchy_index_1(),
  454. hierarchy_index_2(),
  455. % Table with all uploaded filenames, used to ensure unique filenames in the upload archive
  456. medium_log_table(),
  457. % Update/insert trigger on medium to fill the deleted files queue
  458. medium_update_function(),
  459. medium_update_trigger(),
  460. %% Holds administration of previous page paths
  461. rsc_page_path_log(),
  462. rsc_page_path_log_fki(),
  463. %% Track deletion/insert/changes of edges
  464. edge_log_table(),
  465. edge_log_function(),
  466. edge_log_trigger()
  467. ].
  468. hierarchy_table() ->
  469. "CREATE TABLE hierarchy (
  470. name character varying (80),
  471. id int NOT NULL,
  472. parent_id int,
  473. nr int NOT NULL DEFAULT 0,
  474. lvl int NOT NULL DEFAULT 0,
  475. lft int NOT NULL DEFAULT 0,
  476. rght int NOT NULL DEFAULT 0,
  477. CONSTRAINT hierarchy_pkey PRIMARY KEY (name, id),
  478. CONSTRAINT fk_hierarchy_id FOREIGN KEY (id)
  479. REFERENCES rsc(id)
  480. ON UPDATE CASCADE ON DELETE CASCADE
  481. DEFERRABLE INITIALLY DEFERRED
  482. )".
  483. hierarchy_index_1() ->
  484. "CREATE INDEX hierarchy_nr_key ON hierarchy (name, nr)".
  485. hierarchy_index_2() ->
  486. "CREATE INDEX fki_hierarchy_id ON hierarchy (id)".
  487. edge_log_table() ->
  488. "CREATE TABLE edge_log
  489. (
  490. id bigserial NOT NULL,
  491. op character varying(6),
  492. edge_id int not null,
  493. subject_id int not null,
  494. predicate_id int not null,
  495. predicate character varying (80),
  496. object_id int not null,
  497. seq integer not null,
  498. created timestamp with time zone NOT NULL default now(),
  499. CONSTRAINT edge_log_pkey PRIMARY KEY (id)
  500. )".
  501. edge_log_function() ->
  502. "
  503. CREATE FUNCTION edge_update() RETURNS trigger AS $$
  504. declare
  505. new_predicate character varying(80);
  506. begin
  507. if (tg_op = 'INSERT') then
  508. select into new_predicate r.name from rsc r where r.id = new.predicate_id;
  509. insert into edge_log (op, edge_id, subject_id, object_id, predicate_id, predicate, seq)
  510. values (tg_op, new.id, new.subject_id, new.object_id, new.predicate_id, new_predicate, new.seq);
  511. elseif (tg_op = 'UPDATE') then
  512. select into new_predicate r.name from rsc r where r.id = new.predicate_id;
  513. insert into edge_log (op, edge_id, subject_id, object_id, predicate_id, predicate, seq)
  514. values (tg_op, new.id, new.subject_id, new.object_id, new.predicate_id, new_predicate, new.seq);
  515. elseif (tg_op = 'DELETE') then
  516. select into new_predicate r.name from rsc r where r.id = old.predicate_id;
  517. insert into edge_log (op, edge_id, subject_id, object_id, predicate_id, predicate, seq)
  518. values (tg_op, old.id, old.subject_id, old.object_id, old.predicate_id, new_predicate, old.seq);
  519. end if;
  520. return null;
  521. end;
  522. $$ LANGUAGE plpgsql
  523. ".
  524. edge_log_trigger() ->
  525. "
  526. CREATE TRIGGER edge_update_trigger AFTER INSERT OR UPDATE OR DELETE
  527. ON edge FOR EACH ROW EXECUTE PROCEDURE edge_update()
  528. ".
  529. medium_log_table() ->
  530. "CREATE TABLE medium_log
  531. (
  532. id serial NOT NULL,
  533. usr_id int,
  534. filename character varying (400) NOT NULL,
  535. created timestamp with time zone NOT NULL default now(),
  536. CONSTRAINT medium_log_pkey PRIMARY KEY (id),
  537. CONSTRAINT medium_log_filename_key UNIQUE (filename)
  538. )".
  539. medium_update_function() ->
  540. "
  541. CREATE FUNCTION medium_update() RETURNS trigger AS $$
  542. declare
  543. user_id integer;
  544. begin
  545. select into user_id r.creator_id from rsc r where r.id = new.id;
  546. if (tg_op = 'INSERT') then
  547. if (new.filename <> '' and new.filename is not null and new.is_deletable_file) then
  548. insert into medium_log (filename, usr_id)
  549. values (new.filename, user_id);
  550. end if;
  551. if (new.preview_filename <> '' and new.preview_filename is not null and new.is_deletable_preview) then
  552. insert into medium_log (filename, usr_id)
  553. values (new.preview_filename, user_id);
  554. end if;
  555. elseif (tg_op = 'UPDATE') then
  556. if (new.filename <> '' and new.filename is not null and new.is_deletable_file and new.filename != old.filename) then
  557. insert into medium_log (filename, usr_id)
  558. values (new.filename, user_id);
  559. end if;
  560. if (new.preview_filename <> '' and new.preview_filename is not null and new.is_deletable_preview and new.preview_filename != old.preview_filename) then
  561. insert into medium_log (filename, usr_id)
  562. values (new.preview_filename, user_id);
  563. end if;
  564. -- Insert files into the medium_deleted queue table
  565. if (old.filename <> '' and old.filename is not null and old.is_deletable_file and new.filename != old.filename) then
  566. insert into medium_deleted (filename) values (old.filename);
  567. end if;
  568. if (old.preview_filename <> '' and old.preview_filename is not null and old.is_deletable_preview and new.preview_filename != old.preview_filename) then
  569. insert into medium_deleted (filename) values (old.preview_filename);
  570. end if;
  571. end if;
  572. return null;
  573. end;
  574. $$ LANGUAGE plpgsql
  575. ".
  576. medium_update_trigger() ->
  577. "
  578. CREATE TRIGGER medium_update_trigger AFTER INSERT OR UPDATE
  579. ON medium FOR EACH ROW EXECUTE PROCEDURE medium_update()
  580. ".
  581. rsc_page_path_log() ->
  582. "CREATE TABLE rsc_page_path_log (
  583. page_path character varying(80),
  584. id int not null,
  585. created timestamp with time zone NOT NULL DEFAULT now(),
  586. CONSTRAINT rsc_page_path_log_pkey PRIMARY KEY (page_path),
  587. CONSTRAINT fk_rsc_page_path_log_id FOREIGN KEY (id)
  588. REFERENCES rsc(id)
  589. ON UPDATE CASCADE ON DELETE CASCADE
  590. )".
  591. rsc_page_path_log_fki() ->
  592. "CREATE INDEX fki_rsc_page_path_log_id ON rsc_page_path_log (id)".
  593. % -- Fulltext index of products
  594. % -- TODO: Also mix in the shop product id, brand, group and properties
  595. % -- TODO: Use ispell for handling typos
  596. % CREATE INDEX shop_product_tsv ON shop_product USING gin(tsv);
  597. % CREATE FUNCTION shop_product_trigger() RETURNS trigger AS $$
  598. % begin
  599. % new.tsv :=
  600. % setweight(to_tsvector('pg_catalog.dutch', coalesce(new.title_nl,'')), 'A') ||
  601. % setweight(to_tsvector('pg_catalog.dutch', coalesce(new.desc_nl,'')), 'D') ||
  602. % setweight(to_tsvector('pg_catalog.english', coalesce(new.title_en,'')), 'A') ||
  603. % setweight(to_tsvector('pg_catalog.english', coalesce(new.desc_en,'')), 'D');
  604. % return new;
  605. % end
  606. % $$ LANGUAGE plpgsql;
  607. % CREATE TRIGGER tsvectorupdate_shop_product BEFORE INSERT OR UPDATE
  608. % ON shop_product FOR EACH ROW EXECUTE PROCEDURE shop_product_trigger();