/src/install/z_installer.erl

https://code.google.com/p/zotonic/ · Erlang · 231 lines · 171 code · 29 blank · 31 comment · 2 complexity · ddf32e82d1051c174d5008259d0e59e3 MD5 · raw file

  1. %% @author Marc Worrell <marc@worrell.nl>
  2. %% @copyright 2009 Marc Worrell
  3. %% Date: 2009-04-17
  4. %%
  5. %% @doc This server will install the database when started. It will always return ignore to the supervisor.
  6. %% This server should be started after the database pool but before any database queries will be done.
  7. %% Copyright 2009 Marc Worrell
  8. %%
  9. %% Licensed under the Apache License, Version 2.0 (the "License");
  10. %% you may not use this file except in compliance with the License.
  11. %% You may obtain a copy of the License at
  12. %%
  13. %% http://www.apache.org/licenses/LICENSE-2.0
  14. %%
  15. %% Unless required by applicable law or agreed to in writing, software
  16. %% distributed under the License is distributed on an "AS IS" BASIS,
  17. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. %% See the License for the specific language governing permissions and
  19. %% limitations under the License.
  20. -module(z_installer).
  21. -author("Marc Worrell <marc@worrell.nl").
  22. %% gen_server exports
  23. -export([start_link/1]).
  24. -include_lib("zotonic.hrl").
  25. %%====================================================================
  26. %% API
  27. %%====================================================================
  28. %% @spec start_link(SiteProps) -> {ok,Pid} | ignore | {error,Error}
  29. %% @doc Install zotonic on the databases in the PoolOpts, skips when already installed.
  30. start_link(SiteProps) when is_list(SiteProps) ->
  31. install_check(SiteProps),
  32. ignore.
  33. install_check(SiteProps) ->
  34. % Check if the config table exists, if so then assume that all is ok
  35. Name = proplists:get_value(host, SiteProps),
  36. Database = proplists:get_value(dbdatabase, SiteProps),
  37. Schema = proplists:get_value(dbschema, SiteProps, "public"),
  38. case Database of
  39. none ->
  40. ignore;
  41. _ ->
  42. z_install:pre_install(Name, SiteProps),
  43. case has_table("config", Name, Database, Schema) of
  44. false ->
  45. ?LOG("Installing database ~p@~p:~p ~p", [
  46. proplists:get_value(dbuser, SiteProps),
  47. proplists:get_value(dbhost, SiteProps),
  48. proplists:get_value(dbport, SiteProps),
  49. Database
  50. ]),
  51. z_install:install(Name);
  52. true ->
  53. ok = upgrade(Name, Database, Schema),
  54. sanity_check(Name, Database, Schema)
  55. end
  56. end.
  57. %% Check if a table exists by querying the information schema.
  58. has_table(Table, Name, Database, Schema) ->
  59. {ok, C} = pgsql_pool:get_connection(Name),
  60. {ok, HasTable} = pgsql:equery1(C, "
  61. select count(*)
  62. from information_schema.tables
  63. where table_catalog = $1
  64. and table_name = $3
  65. and table_schema = $2
  66. and table_type = 'BASE TABLE'", [Database, Schema, Table]),
  67. pgsql_pool:return_connection(Name, C),
  68. HasTable == 1.
  69. %% Check if a column in a table exists by querying the information schema.
  70. has_column(Table, Column, Name, Database, Schema) ->
  71. {ok, C} = pgsql_pool:get_connection(Name),
  72. {ok, HasColumn} = pgsql:equery1(C, "
  73. select count(*)
  74. from information_schema.columns
  75. where table_catalog = $1
  76. and table_schema = $2
  77. and table_name = $3
  78. and column_name = $4", [Database, Schema, Table, Column]),
  79. pgsql_pool:return_connection(Name, C),
  80. HasColumn == 1.
  81. %% Upgrade older Zotonic versions.
  82. upgrade(Name, Database, Schema) ->
  83. ok = install_acl(Name, Database, Schema),
  84. ok = install_identity_is_verified(Name, Database, Schema),
  85. ok = install_identity_verify_key(Name, Database, Schema),
  86. ok = install_persist(Name, Database, Schema),
  87. ok = drop_visitor(Name, Database, Schema),
  88. ok = extent_mime(Name, Database, Schema),
  89. ok.
  90. install_acl(Name, Database, Schema) ->
  91. %% Remove group, rsc_group, group_id
  92. HasRscGroup = has_table("rsc_group", Name, Database, Schema),
  93. HasGroup = has_table("group", Name, Database, Schema),
  94. case HasRscGroup andalso HasGroup of
  95. true ->
  96. {ok, C} = pgsql_pool:get_connection(Name),
  97. {ok, [], []} = pgsql:squery(C, "BEGIN"),
  98. pgsql:squery(C, "alter table rsc drop column group_id cascade"),
  99. pgsql:squery(C, "drop table rsc_group cascade"),
  100. pgsql:squery(C, "drop table \"group\" cascade"),
  101. pgsql:squery(C, "delete from module where name='mod_admin_group'"),
  102. {ok, 1} = pgsql:equery(C, "insert into module (name, is_active) values ($1, true)", ["mod_acl_adminonly"]),
  103. {ok, [], []} = pgsql:squery(C, "COMMIT"),
  104. pgsql_pool:return_connection(Name, C),
  105. ok;
  106. false ->
  107. ok
  108. end.
  109. install_persist(Name, Database, Schema) ->
  110. case has_table("persistent", Name, Database, Schema) of
  111. false ->
  112. {ok, C} = pgsql_pool:get_connection(Name),
  113. {ok,[],[]} = pgsql:squery(C, "create table persistent ( "
  114. " id character varying(32) not null,"
  115. " props bytea,"
  116. " created timestamp with time zone NOT NULL DEFAULT now(),"
  117. " modified timestamp with time zone NOT NULL DEFAULT now(),"
  118. " CONSTRAINT persistent_pkey PRIMARY KEY (id)"
  119. ")"),
  120. pgsql_pool:return_connection(Name, C),
  121. ok;
  122. true ->
  123. ok
  124. end.
  125. drop_visitor(Name, Database, Schema) ->
  126. case has_table("visitor_cookie", Name, Database, Schema) of
  127. true ->
  128. {ok, C} = pgsql_pool:get_connection(Name),
  129. {ok, [], []} = pgsql:squery(C, "BEGIN"),
  130. {ok, _N} = pgsql:squery(C,
  131. "insert into persistent (id,props) "
  132. "select c.cookie, v.props from visitor_cookie c join visitor v on c.visitor_id = v.id"),
  133. pgsql:squery(C, "drop table visitor_cookie cascade"),
  134. pgsql:squery(C, "drop table visitor cascade"),
  135. {ok, [], []} = pgsql:squery(C, "COMMIT"),
  136. pgsql_pool:return_connection(Name, C),
  137. ok;
  138. false ->
  139. ok
  140. end.
  141. extent_mime(Name, Database, Schema) ->
  142. {ok, C} = pgsql_pool:get_connection(Name),
  143. {ok, Length} = pgsql:equery1(C, "
  144. select character_maximum_length
  145. from information_schema.columns
  146. where table_catalog = $1
  147. and table_schema = $2
  148. and table_name = $3
  149. and column_name = $4", [Database, Schema, "medium", "mime"]),
  150. case Length < 128 of
  151. true ->
  152. {ok, [], []} = pgsql:squery(C, "alter table medium alter column mime type character varying(128)");
  153. false ->
  154. nop
  155. end,
  156. pgsql_pool:return_connection(Name, C),
  157. ok.
  158. install_identity_is_verified(Name, Database, Schema) ->
  159. case has_column("identity", "is_verified", Name, Database, Schema) of
  160. true ->
  161. ok;
  162. false ->
  163. {ok, C} = pgsql_pool:get_connection(Name),
  164. {ok, [], []} = pgsql:squery(C, "BEGIN"),
  165. pgsql:squery(C, "alter table identity "
  166. "add column is_verified boolean not null default false"),
  167. pgsql:squery(C, "update identity set is_verified = true where key = 'username_pw'"),
  168. {ok, [], []} = pgsql:squery(C, "COMMIT"),
  169. pgsql_pool:return_connection(Name, C),
  170. ok
  171. end.
  172. install_identity_verify_key(Name, Database, Schema) ->
  173. case has_column("identity", "verify_key", Name, Database, Schema) of
  174. true ->
  175. ok;
  176. false ->
  177. {ok, C} = pgsql_pool:get_connection(Name),
  178. {ok, [], []} = pgsql:squery(C, "BEGIN"),
  179. pgsql:squery(C, "alter table identity "
  180. "add column verify_key character varying(32), "
  181. "add constraint identity_verify_key_unique UNIQUE (verify_key)"),
  182. {ok, [], []} = pgsql:squery(C, "COMMIT"),
  183. pgsql_pool:return_connection(Name, C),
  184. ok
  185. end.
  186. % Perform some simple sanity checks
  187. sanity_check(Name, _Database, _Schema) ->
  188. {ok, C} = pgsql_pool:get_connection(Name),
  189. {ok, [], []} = pgsql:squery(C, "BEGIN"),
  190. ensure_module_active(C, "mod_authentication"),
  191. {ok, [], []} = pgsql:squery(C, "COMMIT"),
  192. pgsql_pool:return_connection(Name, C),
  193. ok.
  194. ensure_module_active(C, Module) ->
  195. case pgsql:equery(C, "select is_active from module where name = $1", [Module]) of
  196. {ok, _, [{true}]} ->
  197. ok;
  198. {ok, _, [{false}]} ->
  199. {ok, 1} = pgsql:equery(C, "update module set is_active = 1 where name = $1", [Module]);
  200. _ ->
  201. {ok, 1} = pgsql:equery(C, "insert into module (name, is_active) values ($1, true)", [Module])
  202. end.