PageRenderTime 63ms CodeModel.GetById 13ms app.highlight 45ms RepoModel.GetById 1ms app.codeStats 0ms

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