Source file: mdx/lib/Manitou/Schema.pm
# Copyright (C) 2004-2012 Daniel Verite
# This file is part of Manitou-Mail (see http://www.manitou-mail.org)
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
package Manitou::Schema;
use strict;
use vars qw(@ISA @EXPORT_OK);
use Carp;
require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw(current_version supported_versions upgrade_schema_statements
create_data_statements create_function_statements
create_table_statements create_trigger_statements);
sub current_version {
return "1.3.1";
}
sub supported_versions {
return ("0.9.12", "1.0.0", "1.0.1", "1.0.2", "1.1.0", "1.2.0", "1.3.0", "1.3.1");
}
my $create_script=<<EOF;
CREATE TABLE identities (
identity_id INT primary key default nextval('seq_identity_id'),
email_addr TEXT NOT NULL,
username TEXT,
xface TEXT,
signature TEXT
);
CREATE TABLE mail (
mail_id int,
sender text,
recipients text,
sender_fullname text,
subject text,
msg_date timestamptz default now(),
sender_date timestamptz,
identity_id INT REFERENCES identities(identity_id),
status INT,
mod_user_id INT,
thread_id INT,
message_id text,
in_reply_to INT,
date_processed timestamptz,
priority INT default 0,
flags int default 0,
raw_size int
);
CREATE UNIQUE INDEX pk_mail_idx ON mail(mail_id);
CREATE INDEX mail_in_replyto_idx ON mail(in_reply_to);
CREATE INDEX mail_message_id_idx ON mail(message_id);
CREATE INDEX mail_date_idx ON mail(msg_date);
CREATE INDEX mail_thread_idx ON mail(thread_id);
CREATE TABLE notes (
mail_id int REFERENCES mail(mail_id),
note text,
last_changed timestamptz default now()
);
CREATE UNIQUE INDEX notes_idx ON notes(mail_id);
CREATE TABLE mail_status (
mail_id int,
status int
);
CREATE UNIQUE INDEX pk_mail_status_idx ON mail_status(mail_id);
CREATE TABLE header (
mail_id INT REFERENCES mail(mail_id),
lines TEXT
);
create unique index pk_header_idx on header(mail_id);
CREATE TABLE body (
mail_id INT REFERENCES mail(mail_id),
bodytext TEXT,
bodyhtml TEXT
);
create unique index pk_body_idx on body(mail_id);
CREATE TABLE attachments (
attachment_id INT primary key,
mail_id INT REFERENCES mail(mail_id),
content_type VARCHAR(300),
content_size INT,
filename VARCHAR(300),
charset VARCHAR(30),
mime_content_id TEXT
);
CREATE INDEX idx_attachments_mail_id ON Attachments(mail_id);
CREATE TABLE attachment_contents (
attachment_id INT REFERENCES attachments(attachment_id),
content OID,
fingerprint TEXT
);
CREATE UNIQUE INDEX attch_ct_idx ON attachment_contents(attachment_id);
CREATE INDEX attach_ct_fp_idx ON attachment_contents(fingerprint);
CREATE TABLE users (
user_id INT PRIMARY KEY CHECK (user_id>0),
fullname VARCHAR(300),
login VARCHAR(80),
email TEXT,
custom_field1 TEXT,
custom_field2 TEXT,
custom_field3 TEXT
);
CREATE UNIQUE INDEX users_login_idx ON users(login);
CREATE TABLE tags (
tag_id INT,
name VARCHAR(300),
parent_id INT
);
CREATE UNIQUE INDEX tag_id_pk ON tags(tag_id);
ALTER TABLE tags ADD CONSTRAINT parent_tag_fk
FOREIGN KEY (parent_id) REFERENCES tags(tag_id);
CREATE TABLE mail_tags (
mail_id INT REFERENCES mail(mail_id),
tag INT REFERENCES tags(tag_id),
agent INT,
date_insert timestamptz default now()
);
CREATE UNIQUE INDEX mail_tags_idx ON mail_tags(mail_id,tag);
CREATE TABLE config (
conf_key VARCHAR(100) not null,
value text,
conf_name VARCHAR(100),
date_update timestamptz
);
CREATE TABLE files (
mail_id INT,
filename VARCHAR(300)
);
CREATE TABLE addresses (
addr_id INT PRIMARY KEY,
email_addr VARCHAR(300) UNIQUE,
name VARCHAR(300),
nickname varchar(300),
last_sent_to timestamptz,
last_recv_from timestamptz,
notes text,
invalid int default 0,
recv_pri int default 0,
nb_sent_to int,
nb_recv_from int
);
CREATE TABLE mail_addresses (
mail_id INT REFERENCES mail(mail_id),
addr_id INT REFERENCES addresses(addr_id),
addr_type SMALLINT,
addr_pos SMALLINT
);
CREATE INDEX mail_addresses_addrid_idx ON mail_addresses(addr_id);
CREATE INDEX mail_addresses_mailid_idx ON mail_addresses(mail_id);
CREATE TABLE programs (
program_name varchar(256),
content_type varchar(256),
conf_name varchar(100)
);
CREATE TABLE mime_types (
suffix varchar(20) NOT NULL,
mime_type varchar(100) NOT NULL
);
CREATE TABLE runtime_info (
rt_key varchar(100) not null,
rt_value text
);
CREATE UNIQUE INDEX runtime_info_pk ON runtime_info(rt_key);
CREATE TABLE words (
word_id int PRIMARY KEY,
wordtext varchar(50)
);
CREATE UNIQUE INDEX wordtext_idx ON words(wordtext);
CREATE TABLE non_indexable_words (
wordtext varchar(50)
);
CREATE TABLE filter_expr (
expr_id int PRIMARY KEY,
name varchar(100),
user_lastmod int,
last_update timestamptz default now(),
expression text,
direction char(1) default 'I',
apply_order real UNIQUE,
last_hit timestamptz
);
CREATE UNIQUE INDEX expr_idx ON filter_expr(name);
CREATE TABLE filter_action (
expr_id int REFERENCES filter_expr(expr_id),
action_order smallint,
action_arg text,
action_type varchar(100)
);
CREATE UNIQUE INDEX filter_action_idx ON filter_action(expr_id,action_order);
CREATE TABLE filter_log (
expr_id int, -- No reference to filter_expr, we don't want any constraint here
mail_id int, -- No reference to mail to be able to delete mail without touching this table
hit_date timestamptz default now()
);
CREATE TABLE user_queries (
title text NOT NULL,
sql_stmt text
);
CREATE UNIQUE INDEX user_queries_idx ON user_queries(title);
CREATE TABLE tags_words (
tag_id int REFERENCES tags(tag_id),
word_id int REFERENCES words(word_id),
counter int
) WITHOUT OIDS;
CREATE INDEX tags_words_idx ON tags_words(word_id);
CREATE TABLE forward_addresses (
to_email_addr varchar(300),
forward_to text
);
CREATE UNIQUE INDEX fwa_idx ON forward_addresses(to_email_addr);
CREATE TABLE raw_mail (
mail_id int REFERENCES mail(mail_id),
mail_text oid
);
CREATE UNIQUE INDEX idx_raw_mail ON raw_mail(mail_id);
CREATE TABLE inverted_word_index (
word_id int REFERENCES words(word_id),
part_no int,
mailvec bytea,
nz_offset int default 0
);
CREATE UNIQUE INDEX iwi_idx ON inverted_word_index(word_id,part_no);
CREATE TABLE jobs_queue (
job_id serial,
mail_id int,
job_type varchar(100),
job_args text,
status smallint
);
CREATE UNIQUE INDEX jobs_pk_idx ON jobs_queue(job_id);
CREATE TABLE global_notepad (
contents text,
last_modified timestamptz
);
EOF
my %tables=("mailing_definition"=> <<'EOT'
CREATE TABLE mailing_definition (
mailing_id serial PRIMARY KEY,
title text,
sender_email text,
creation_date timestamptz default now(),
end_date timestamptz,
text_template text,
html_template text,
header_template text,
csv_columns text
)
EOT
,"mailing_run" => <<'EOT'
CREATE TABLE mailing_run (
mailing_id int REFERENCES mailing_definition(mailing_id),
status smallint,
throughput float,
nb_total int,
nb_sent int,
last_sent timestamptz
)
EOT
,"mailing_data" => <<'EOT'
CREATE TABLE mailing_data (
mailing_data_id serial primary key,
mailing_id int REFERENCES mailing_definition(mailing_id),
recipient_email text,
csv_data text,
sent character
)
EOT
,"mail_template" => <<'EOT'
CREATE TABLE mail_template (
template_id serial PRIMARY KEY,
title text,
body_text text,
body_html text,
header text,
creation_date timestamptz default now()
)
EOT
,"import_mbox" => <<'EOT'
CREATE TABLE import_mbox (
import_id serial PRIMARY KEY,
tag_id int,
mail_status smallint,
apply_filters character,
completion real,
status smallint,
filename text,
auto_purge character
)
EOT
,"import_message" => <<'EOT'
CREATE TABLE import_message (
import_id integer,
mail_number integer,
encoded_mail bytea,
status smallint,
mail_id int
)
EOT
);
my %object_comments=(
"mailing_run.status" => "0=not started, 1=running, 2=stopped, 3=finished",
"mail_addresses.addr_type" => "1=from, 2=to, 3=cc, 4=reply-to, 5=bcc",
"import_message.status" => "0=new, 1=imported, 2=cancelled",
"import_mbox.status" => "0=not started, 1=running, 2=aborted, 3=finished",
"import_mbox.auto_purge" => "Delete the row in this table when the import has successfully completed",
);
my %functions=("insert_mail" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION insert_mail() RETURNS TRIGGER AS $$
BEGIN
IF NEW.status&(256+32+16)=0 THEN
-- The message is not yet sent, archived, or trashed
INSERT INTO mail_status(mail_id,status) VALUES(new.mail_id,new.status);
END IF;
RETURN new;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"update_mail" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION update_mail() RETURNS TRIGGER AS $$
DECLARE
rc int4;
BEGIN
IF new.status!=old.status THEN
IF NEW.status&(256+32+16)=0 THEN
-- The message is not yet sent, archived, or trashed
UPDATE mail_status
SET status = new.status
WHERE mail_id = new.mail_id;
GET DIAGNOSTICS rc = ROW_COUNT;
if rc=0 THEN
INSERT INTO mail_status(mail_id,status) VALUES(new.mail_id,new.status);
END IF;
ELSE
-- The mail has been "processed"
DELETE FROM mail_status
WHERE mail_id = new.mail_id;
END IF;
END IF;
RETURN new;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"delete_mail" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION delete_mail() RETURNS TRIGGER AS $$
BEGIN
DELETE FROM mail_status WHERE mail_id=OLD.mail_id;
RETURN old;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"delete_msg" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION delete_msg(integer) RETURNS integer AS $$
DECLARE
id ALIAS FOR $1;
attch RECORD;
cnt integer;
o oid;
BEGIN
DELETE FROM notes WHERE mail_id=id;
DELETE FROM mail_addresses WHERE mail_id=id;
DELETE FROM header WHERE mail_id=id;
DELETE FROM body WHERE mail_id=id;
DELETE FROM mail_tags WHERE mail_id=id;
FOR attch IN SELECT a.attachment_id,c.content,c.fingerprint FROM attachments a, attachment_contents c WHERE a.mail_id=id AND c.attachment_id=a.attachment_id LOOP
cnt=0;
IF attch.fingerprint IS NOT NULL THEN
-- check if that content is shared with another message's attachment
SELECT count(*) INTO cnt FROM attachment_contents WHERE fingerprint=attch.fingerprint AND attachment_id!=attch.attachment_id;
END IF;
IF (cnt=0) THEN
PERFORM lo_unlink(attch.content);
END IF;
DELETE FROM attachment_contents WHERE attachment_id=attch.attachment_id;
END LOOP;
DELETE FROM attachments WHERE mail_id=id;
UPDATE mail SET in_reply_to=NULL WHERE in_reply_to=id;
SELECT mail_text INTO o FROM raw_mail WHERE mail_id=id;
IF FOUND THEN
PERFORM lo_unlink(o);
DELETE FROM raw_mail WHERE mail_id=id;
END IF;
DELETE FROM mail WHERE mail_id=id;
IF (FOUND) THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"delete_msg_set" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION delete_msg_set(in_array_mail_id int[]) RETURNS int AS $$
DECLARE
cnt int;
BEGIN
cnt:=0;
FOR idx IN array_lower(in_array_mail_id,1)..array_upper(in_array_mail_id,1) LOOP
cnt:=cnt + delete_msg(in_array_mail_id[idx]);
END LOOP;
RETURN cnt;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"replace_header_field" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION replace_header_field(in_mail_id int, field text, val text) RETURNS void
AS $$
declare
cnt int;
BEGIN
IF val IS NULL THEN
-- Remove the entries
UPDATE header SET lines = replace(regexp_replace(lines, '^'||field||':.*?$', E'\n', 'ngi'), E'\n\n', '')
WHERE mail_id=in_mail_id
AND lines ~* ('(?n)^'||field||':.*$');
-- Special case for the first line
UPDATE header SET lines = regexp_replace(lines, '^'||field||E':.*?\n', '', 'i') WHERE mail_id=in_mail_id AND lines ~* ('^' ||field||E':.*?\n');
ELSE
-- First try to replace existing header entries by the new value
UPDATE header SET lines = regexp_replace(lines, '^'||field||E':.*$', field||': '||val, 'gni')
WHERE mail_id=in_mail_id
AND lines ~* ('(?n)^'||field||':.*$');
GET DIAGNOSTICS cnt=ROW_COUNT;
-- If the update didn't find any entry, append the field to the header
IF cnt=0 THEN
UPDATE header SET lines = lines||field||': '||val||chr(10)
WHERE mail_id=in_mail_id;
END IF;
END IF;
END;
$$ LANGUAGE plpgsql
EOFUNCTION
,
"trash_msg" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION trash_msg(in_mail_id integer, in_op integer) RETURNS integer AS $$
DECLARE
new_status int;
BEGIN
UPDATE mail SET status=status|16,mod_user_id=in_op WHERE mail_id=in_mail_id;
SELECT INTO new_status status FROM mail WHERE mail_id=in_mail_id;
RETURN new_status;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"trash_msg_set" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION trash_msg_set(in_array_mail_id int[], in_op int) RETURNS int AS $$
DECLARE
cnt int;
BEGIN
UPDATE mail SET status=status|16, mod_user_id=in_op WHERE mail_id=any(in_array_mail_id);
GET DIAGNOSTICS cnt=ROW_COUNT;
return cnt;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"untrash_msg" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION untrash_msg(in_mail_id int, in_op int) RETURNS int AS $$
DECLARE
new_status int;
BEGIN
UPDATE mail SET status=status&(~16),mod_user_id=in_op WHERE mail_id=in_mail_id;
SELECT INTO new_status status FROM mail WHERE mail_id=in_mail_id;
RETURN new_status;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"update_note_flag" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION update_note_flag() RETURNS trigger AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
UPDATE mail SET flags=flags&(~2) WHERE mail_id=OLD.mail_id;
RETURN OLD;
ELSIF (TG_OP = 'INSERT') THEN
UPDATE mail SET flags=flags|2 WHERE mail_id=NEW.mail_id;
RETURN NEW;
END IF;
END;
$$ LANGUAGE 'plpgsql'
EOFUNCTION
,
"wordsearch_get_parts" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION wordsearch_get_parts(in_words text[]) RETURNS SETOF integer
AS $$
DECLARE
var_nb_words integer := array_upper(in_words,1);
BEGIN
RETURN QUERY select part_no FROM inverted_word_index WHERE word_id in (select word_id from words where wordtext=any(in_words)) GROUP BY part_no HAVING count(*)=var_nb_words;
END;
$$ LANGUAGE plpgsql
EOFUNCTION
,
"wordsearch" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION wordsearch(in_words text[],out_words text[]) RETURNS SETOF integer
AS $$
DECLARE
var_nb_words integer := array_upper(in_words,1);
var_part_no integer;
cnt integer;
b1 integer;
b2 integer;
len integer;
i integer;
j integer;
var_vect bytea;
var2_vect bytea;
and_vect bytea:=null; -- vectors ANDed together
excl_vect bytea; -- for words that must not be in the text (out_words)
var_nz_offset integer;
BEGIN
FOR var_part_no IN (select part_no FROM inverted_word_index WHERE word_id in (select word_id from words where wordtext=any(in_words)) GROUP BY part_no HAVING count(*)=var_nb_words ORDER BY part_no)
LOOP
cnt:=0;
FOR var_vect,var_nz_offset IN SELECT mailvec,nz_offset FROM inverted_word_index WHERE word_id in (select word_id from words where wordtext=any(in_words)) AND part_no=var_part_no LOOP
IF (var_nz_offset>0) THEN
var_vect:=repeat(E'\\000', var_nz_offset)::bytea || var_vect;
END IF;
IF (cnt=0) THEN
IF array_upper(out_words,1)>0 THEN
excl_vect:=null;
FOR var2_vect IN SELECT repeat(E'\\000', nz_offset)::bytea||mailvec FROM inverted_word_index WHERE word_id in (select word_id from words where wordtext=any(out_words)) AND part_no=var_part_no
LOOP
IF excl_vect is null THEN
excl_vect:=var2_vect;
ELSE
IF (length(excl_vect) > length(var2_vect)) THEN
var2_vect:=var2_vect||repeat(E'\\000', length(excl_vect)-length(var2_vect));
ELSEIF (length(excl_vect) < length(var2_vect)) THEN
excl_vect:= excl_vect || repeat(E'\\000', length(var2_vect)-length(excl_vect));
END IF;
-- excl_vect := excl_vect OR var2_vect
len:=length(excl_vect)-1;
FOR i in 0..len LOOP
b1:=get_byte(excl_vect, i);
b2:=get_byte(var2_vect, i);
IF (b1|b2 <> b1) THEN
SELECT set_byte(excl_vect, i, b1|b2) INTO excl_vect;
END IF;
END LOOP;
END IF;
END LOOP; -- for each word to exclude
IF excl_vect is not null THEN
-- invert excl_vect (bitwise NOT) to make it an AND mask against
-- the vectors of the words included in the search
len:=length(excl_vect)-1;
FOR i in 0..len LOOP
b1:=get_byte(excl_vect, i);
SELECT set_byte(excl_vect, i, ~b1) INTO excl_vect;
END LOOP;
END IF;
END IF; -- out_words is not empty
and_vect:=excl_vect;
END IF;
IF and_vect IS NOT NULL THEN
-- reduce result if necessary
IF (length(and_vect) > length(var_vect)) THEN
and_vect:=substring(and_vect for length(var_vect));
END IF;
len:=length(and_vect)-1;
FOR i in 0..len LOOP
b1:=get_byte(and_vect, i);
b2:=get_byte(var_vect, i);
IF (b1&b2 <> b1) THEN
SELECT set_byte(and_vect, i, b1&b2) INTO and_vect;
END IF;
END LOOP;
ELSE
and_vect:=var_vect;
END IF;
cnt:=cnt+1;
END LOOP; -- on vectors of the same part_no
-- extract the set of mail_id's for this part_no from the vector
len:=length(and_vect)-1;
IF (len>=0) THEN -- len might be NULL OR -1 if no result at all
FOR i IN 0..len LOOP
b1:=get_byte(and_vect,i);
FOR j IN 0..7 LOOP
IF ((b1&(1<<j))!=0) THEN
RETURN NEXT var_part_no*16384+(i*8)+j+1; -- hit
END IF;
j:=j+1;
END LOOP;
END LOOP;
END IF;
END LOOP; -- on part_no
RETURN;
END;
$$ LANGUAGE plpgsql
EOFUNCTION
,
"wordsearch_part" => <<'EOFUNCTION'
CREATE OR REPLACE FUNCTION wordsearch_part(in_words text[], in_part_no integer) RETURNS SETOF integer
AS $$
DECLARE
var_nb_words integer := array_upper(in_words,1);
cnt integer;
b1 integer;
b2 integer;
len integer;
i integer;
j integer;
var_vect bytea;
and_vect bytea; -- vectors ANDed together
var_nz_offset integer;
BEGIN
cnt:=0;
FOR var_vect,var_nz_offset IN SELECT mailvec,nz_offset FROM inverted_word_index WHERE word_id in (select word_id from words where wordtext=any(in_words)) AND part_no=in_part_no LOOP
IF (var_nz_offset>0) THEN
var_vect:=repeat(E'\\000', var_nz_offset)::bytea || var_vect;
END IF;
IF (cnt=0) THEN
and_vect:=var_vect; -- first vector
ELSE
-- next vectors
-- reduce result if necessary
IF (length(and_vect) > length(var_vect)) THEN
and_vect:=substring(and_vect for length(var_vect));
END IF;
len:=length(and_vect)-1;
FOR i in 0..len LOOP
b1:=get_byte(and_vect, i);
b2:=get_byte(var_vect, i);
IF (b1&b2 <> b1) THEN
SELECT set_byte(and_vect, i, b1&b2) INTO and_vect;
END IF;
END LOOP;
END IF;
cnt:=cnt+1;
END LOOP; -- on vectors
-- extract the set of mail_id's for this part_no from the vector
len:=length(and_vect)-1;
IF (len>=0) THEN -- len might be NULL OR -1 if no result at all
FOR i IN 0..len LOOP
b1:=get_byte(and_vect,i);
FOR j IN 0..7 LOOP
IF ((b1&(1<<j))!=0) THEN
RETURN NEXT in_part_no*16384+(i*8)+j+1; -- hit
END IF;
j:=j+1;
END LOOP;
END LOOP;
END IF;
RETURN;
END;
$$ LANGUAGE plpgsql
EOFUNCTION
);
my %triggers=(
"update_mail" => q{CREATE TRIGGER update_mail AFTER UPDATE ON mail
FOR EACH ROW EXECUTE PROCEDURE update_mail()},
"insert_mail" => q{CREATE TRIGGER insert_mail AFTER INSERT ON mail
FOR EACH ROW EXECUTE PROCEDURE insert_mail()},
"delete_mail" => q{CREATE TRIGGER delete_mail AFTER DELETE ON mail
FOR EACH ROW EXECUTE PROCEDURE delete_mail()},
"update_note" => q{CREATE TRIGGER update_note AFTER INSERT OR DELETE ON notes
FOR EACH ROW EXECUTE PROCEDURE update_note_flag()}
);
sub extract_statements {
my @statements;;
foreach (split( /\s*;\s*/m, $create_script)) {
if ($_ ne "") {
push @statements, $_;
}
}
return @statements;
}
sub create_table_statements {
my @stmt=extract_statements($create_script);
for my $t (qw(mailing_definition mailing_run mailing_data mail_template import_mbox import_message)) {
push @stmt, $tables{$t};
}
foreach my $c (keys %object_comments) {
push @stmt, sql_comment($c);
}
return @stmt;
}
sub create_function_statements {
return (values %functions);
}
sub create_trigger_statements {
return (values %triggers);
}
sub create_sequence_statements {
return map { "CREATE SEQUENCE $_" } ("seq_tag_id","seq_mail_id", "seq_thread_id", "seq_addr_id", "seq_attachment_id", "seq_identity_id", "seq_filter_expr_id", "seq_word_id");
}
sub create_data_statements {
my %types=
('txt' => 'text/plain',
'htm' => 'text/html',
'html' => 'text/html',
'xml' => 'text/xml',
'rtf' => 'application/rtf',
'zip' => 'application/zip',
'doc' => 'application/msword',
'xls' => 'application/vnd.ms-excel',
'pdf' => 'application/pdf',
'tar' => 'application/x-tar',
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'bmp' => 'image/bmp'
);
my @statements;
while (my ($k,$v) = each %types) {
push @statements, "INSERT INTO mime_types VALUES('$k', '$v')";
}
return @statements;
}
sub sql_comment {
my $col=shift;
die "Non-existing SQL comment for column $col" if (!exists $object_comments{$col});
# TODO: see how we could use $dbh->quote() to protect the comment
# right now we don't have $dbh
return "COMMENT ON COLUMN $col IS '" . $object_comments{$col} . "'";
}
sub function_exists {
my ($dbh,$fn)=@_;
my $sth=$dbh->prepare("SELECT 1 FROM information_schema.routines WHERE routine_schema='public' AND routine_name=?");
$sth->execute($fn) or die $sth->errstr;
my @r=$sth->fetchrow_array;
return @r>0 && $r[0]==1;
}
sub table_constraint_exists {
my ($dbh,$tbl,$constraint)=@_;
my $sth=$dbh->prepare("SELECT 1 FROM information_schema.table_constraints WHERE table_name=? AND constraint_name=? AND table_schema='public'");
$sth->execute($tbl, $constraint) or die $sth->errstr;
my @r=$sth->fetchrow_array;
return @r>0 && $r[0]==1;
}
sub upgrade_schema_statements {
my ($dbh,$from,$to)=@_;
my @stmt;
if ($from eq "0.9.12" && $to eq "1.0.0") {
push @stmt,
("ALTER TABLE body DROP COLUMN textsize",
"ALTER TABLE body ADD bodyhtml TEXT",
"ALTER TABLE header DROP COLUMN header_size",
"ALTER TABLE mail DROP COLUMN msg_day",
"ALTER TABLE mail DROP COLUMN operator",
"ALTER TABLE mail RENAME attachments TO flags",
"ALTER TABLE mail RENAME COLUMN mod_userid TO mod_user_id",
"UPDATE mail SET flags=1 WHERE flags!=0",
"UPDATE mail SET flags=flags|2 WHERE mail_id in (select mail_id from notes)"
);
push @stmt, "CREATE TABLE global_notepad (contents text, last_modified timestamptz)";
push @stmt, $functions{"update_note_flag"};
push @stmt, $triggers{"update_note"};
push @stmt, $functions{"trash_msg"};
push @stmt, $functions{"trash_msg_set"};
push @stmt, $functions{"untrash_msg"};
}
elsif ($from eq "1.0.0" && $to eq "1.0.1") {
push @stmt, ("ALTER TABLE users ADD email TEXT",
"ALTER TABLE users ADD custom_field1 TEXT",
"ALTER TABLE users ADD custom_field2 TEXT",
"ALTER TABLE users ADD custom_field3 TEXT",
"ALTER TABLE users ADD CONSTRAINT user_id_gt0 CHECK(user_id>0)"
);
}
elsif ($from eq "1.0.1" && $to eq "1.0.2") {
}
elsif ($from eq "1.0.2" && $to eq "1.1.0") {
push @stmt, "ALTER TABLE jobs_queue ALTER COLUMN job_type TYPE varchar(100)";
push @stmt, $tables{"mailing_definition"};
push @stmt, $tables{"mailing_run"};
push @stmt, $tables{"mailing_data"};
push @stmt, $tables{"mail_template"};
push @stmt, sql_comment("mailing_run.status");
}
elsif ($from eq "1.1.0" && $to eq "1.2.0") {
push @stmt, $functions{"wordsearch"} if (!function_exists($dbh, "wordsearch"));
push @stmt, $functions{"wordsearch_get_parts"} if (!function_exists($dbh, "wordsearch_get_parts"));
push @stmt, $functions{"wordsearch_part"} if (!function_exists($dbh, "wordsearch_part"));
push @stmt, ( # Filters
"ALTER TABLE filter_expr ADD apply_order real UNIQUE",
"ALTER TABLE filter_expr ADD last_hit timestamptz",
"UPDATE filter_expr SET apply_order=expr_id",
"CREATE SEQUENCE seq_filter_expr_id",
"SELECT setval('seq_filter_expr_id', 1+coalesce(x,0)) from (select max(expr_id) as x from filter_expr) m",
"UPDATE filter_action SET action_type='discard',action_arg='trash' WHERE action_type='status' AND action_arg='T'",
# Merge of mailboxes into identities
"CREATE SEQUENCE seq_identity_id",
"ALTER TABLE identities ADD identity_id INT",
"INSERT INTO identities (email_addr,identity_id) SELECT name,mbox_id FROM mailboxes WHERE NOT EXISTS (select 1 from identities where email_addr=mailboxes.name)",
"UPDATE identities i SET identity_id=(SELECT mbox_id FROM mailboxes m WHERE m.name=i.email_addr)",
"SELECT setval('seq_identity_id',1+coalesce(x,0)) from (select max(identity_id) as x from identities) m",
"UPDATE identities SET identity_id=nextval('seq_identity_id') WHERE identity_id is null",
"ALTER TABLE identities ALTER COLUMN identity_id SET DEFAULT nextval('seq_identity_id')",
"ALTER TABLE identities ALTER COLUMN email_addr TYPE text",
"ALTER TABLE identities ALTER COLUMN username TYPE text",
"ALTER TABLE mail RENAME COLUMN mbox_id TO identity_id",
"DROP TABLE mailboxes CASCADE",
"ALTER TABLE identities ADD PRIMARY KEY (identity_id)",
"ALTER TABLE mail ADD CONSTRAINT mail_identity_id_fkey FOREIGN KEY(identity_id) REFERENCES identities(identity_id)",
# New columns
"ALTER TABLE mail ADD raw_size INT",
"ALTER TABLE mail RENAME toname TO recipients",
"ALTER TABLE mail ALTER COLUMN recipients TYPE text",
"UPDATE mail m set recipients=(select (regexp_matches(lines, E'\\nTo: (.*?)\\n'))[1] from header where mail_id=m.mail_id)",
"INSERT INTO config(conf_key,value) VALUES('display/auto_sender_column','1')"
);
push @stmt, sql_comment("mail_addresses.addr_type");
push @stmt, $functions{"replace_header_field"};
}
elsif ($from eq "1.2.0" && $to eq "1.3.0") {
push @stmt, "ALTER TABLE jobs_queue ADD status SMALLINT";
push @stmt, $tables{"import_mbox"};
push @stmt, $tables{"import_message"};
push @stmt, $functions{"wordsearch"};
}
elsif ($from eq "1.3.0" && $to eq "1.3.1") {
# no change in schema
}
return @stmt;
}
1;
HTML source code generated by GNU Source-Highlight plus some custom post-processing
List of all available source files