pgsql/*: Added tables, triggers and functions for quota support. v0.6.x
authorPascal Volk <neverseen@users.sourceforge.net>
Fri, 04 Feb 2011 17:29:35 +0000
branchv0.6.x
changeset 382 5e6bcb2e010e
parent 381 98223e5c95e0
child 383 ac5ac03b58da
pgsql/*: Added tables, triggers and functions for quota support.
pgsql/create_optional_types_and_functions-dovecot-1.2.x.pgsql
pgsql/create_optional_types_and_functions.pgsql
pgsql/create_tables-dovecot-1.2.x.pgsql
pgsql/create_tables.pgsql
pgsql/update_tables_0.5.x-0.6-dovecot-1.2.x.pgsql
pgsql/update_tables_0.5.x-0.6.pgsql
pgsql/update_types_and_functions_0.5.x-0.6-dovecot-1.2.x.pgsql
pgsql/update_types_and_functions_0.5.x-0.6.pgsql
--- a/pgsql/create_optional_types_and_functions-dovecot-1.2.x.pgsql	Wed Feb 02 21:09:50 2011 +0000
+++ b/pgsql/create_optional_types_and_functions-dovecot-1.2.x.pgsql	Fri Feb 04 17:29:35 2011 +0000
@@ -287,6 +287,17 @@
     home        text,
     mail        text
 );
+-- ---
+-- Data type for function dovecotquotauser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotquotauser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text,
+    quota_rule  text
+);
 
 -- ---
 -- Parameters (from login name [localpart@the_domain]):
@@ -327,6 +338,44 @@
 RETURNS NULL ON NULL INPUT
 EXTERNAL SECURITY INVOKER;
 
+-- ---
+-- Nearly the same as function dovecotuser above. It returns additionally the
+-- field quota_rule.
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT
+--          ON users, domain_data, domain_name, maillocation, mailboxformat,
+--             quotalimit
+--          TO dovecot;
+-- ---
+CREATE OR REPLACE FUNCTION dovecotquotauser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
+AS $$
+    DECLARE
+        record dovecotquotauser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail, '*:bytes=' ||
+                   bytes || ':messages=' || messages AS quota_rule
+              FROM users, domain_data, mailboxformat, maillocation, quotalimit
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+               AND quotalimit.qid = users.qid
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+
 -- ########################################################################## --
 
 -- ---
--- a/pgsql/create_optional_types_and_functions.pgsql	Wed Feb 02 21:09:50 2011 +0000
+++ b/pgsql/create_optional_types_and_functions.pgsql	Fri Feb 04 17:29:35 2011 +0000
@@ -287,6 +287,17 @@
     home        text,
     mail        text
 );
+-- ---
+-- Data type for function dovecotquotauser(varchar, varchar) 
+-- ---
+CREATE TYPE dovecotquotauser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text,
+    quota_rule  text
+);
 
 -- ---
 -- Parameters (from login name [localpart@the_domain]):
@@ -327,6 +338,44 @@
 RETURNS NULL ON NULL INPUT
 EXTERNAL SECURITY INVOKER;
 
+-- ---
+-- Nearly the same as function dovecotuser above. It returns additionally the
+-- field quota_rule.
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT
+--          ON users, domain_data, domain_name, maillocation, mailboxformat,
+--             quotalimit
+--          TO dovecot;
+-- ---
+CREATE OR REPLACE FUNCTION dovecotquotauser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
+AS $$
+    DECLARE
+        record dovecotquotauser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail, '*:bytes=' ||
+                   bytes || ':messages=' || messages AS quota_rule
+              FROM users, domain_data, mailboxformat, maillocation, quotalimit
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+               AND quotalimit.qid = users.qid
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+
 -- ########################################################################## --
 
 -- ---
--- a/pgsql/create_tables-dovecot-1.2.x.pgsql	Wed Feb 02 21:09:50 2011 +0000
+++ b/pgsql/create_tables-dovecot-1.2.x.pgsql	Fri Feb 04 17:29:35 2011 +0000
@@ -8,6 +8,8 @@
 
 CREATE SEQUENCE maillocation_id;
 
+CREATE SEQUENCE quotalimit_id;
+
 CREATE SEQUENCE domain_gid
     START WITH 70000
     INCREMENT BY 1
@@ -55,13 +57,26 @@
 -- Insert default Maildir-folder name
 INSERT INTO maillocation(directory) VALUES ('Maildir');
 
+CREATE TABLE quotalimit (
+    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
+    bytes       bigint NOT NULL,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
+    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
+);
+-- Insert default (non) quota limit
+INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
+
 CREATE TABLE domain_data (
     gid         bigint NOT NULL DEFAULT nextval('domain_gid'),
     tid         bigint NOT NULL DEFAULT 1, -- default transport
+    qid         bigint NOT NULL DEFAULT 1, -- default quota limit
     domaindir   varchar(40) NOT NULL, --/srv/mail/$RAND/4294967294
     CONSTRAINT  pkey_domain_data PRIMARY KEY (gid),
     CONSTRAINT  fkey_domain_data_tid_transport FOREIGN KEY (tid)
-        REFERENCES transport (tid)
+        REFERENCES transport (tid),
+    CONSTRAINT  fkey_domain_data_qid_quotalimit FOREIGN KEY (qid)
+        REFERENCES quotalimit (qid)
 );
 
 CREATE TABLE domain_name (
@@ -81,6 +96,7 @@
     gid         bigint NOT NULL,
     mid         bigint NOT NULL DEFAULT 1,
     tid         bigint NOT NULL DEFAULT 1,
+    qid         bigint NOT NULL DEFAULT 1,
     smtp        boolean NOT NULL DEFAULT TRUE,
     pop3        boolean NOT NULL DEFAULT TRUE,
     imap        boolean NOT NULL DEFAULT TRUE,
@@ -92,7 +108,18 @@
     CONSTRAINT  fkey_users_mid_maillocation FOREIGN KEY (mid)
         REFERENCES maillocation (mid),
     CONSTRAINT  fkey_users_tid_transport FOREIGN KEY (tid)
-        REFERENCES transport (tid)
+        REFERENCES transport (tid),
+    CONSTRAINT  fkey_users_qid_quotalimit FOREIGN KEY (qid)
+        REFERENCES quotalimit (qid)
+);
+
+CREATE TABLE userquota (
+    uid         bigint NOT NULL,
+    bytes       bigint NOT NULL DEFAULT 0,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota PRIMARY KEY (uid),
+    CONSTRAINT  fkey_userquota_uid_users FOREIGN KEY (uid)
+        REFERENCES users (uid)
 );
 
 CREATE TABLE alias (
@@ -211,3 +238,43 @@
 
 CREATE TRIGGER primary_count_upd AFTER UPDATE ON domain_name
     FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
+
+
+CREATE OR REPLACE FUNCTION merge_userquota() RETURNS TRIGGER AS $$
+BEGIN
+    IF NEW.messages < 0 OR NEW.messages IS NULL THEN
+        IF NEW.messages IS NULL THEN
+            NEW.messages = 0;
+        ELSE
+            NEW.messages = -NEW.messages;
+        END IF;
+        RETURN NEW;
+    END IF;
+    LOOP
+        UPDATE userquota
+           SET bytes = bytes + NEW.bytes, messages = messages + NEW.messages
+         WHERE uid = NEW.uid;
+        IF found THEN
+            RETURN NULL;
+        END IF;
+        BEGIN
+            IF NEW.messages = 0 THEN
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, NULL);
+            ELSE
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, -NEW.messages);
+            END IF;
+            RETURN NULL;
+        EXCEPTION
+            WHEN unique_violation THEN
+                -- do nothing, and loop to try the UPDATE again
+            WHEN foreign_key_violation THEN
+                -- break the loop: a non matching uid means no such user
+                RETURN NULL;
+        END;
+    END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+
+CREATE TRIGGER mergeuserquota BEFORE INSERT ON userquota
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota();
--- a/pgsql/create_tables.pgsql	Wed Feb 02 21:09:50 2011 +0000
+++ b/pgsql/create_tables.pgsql	Fri Feb 04 17:29:35 2011 +0000
@@ -6,6 +6,8 @@
 
 CREATE SEQUENCE mailboxformat_id;
 
+CREATE SEQUENCE quotalimit_id;
+
 CREATE SEQUENCE maillocation_id;
 
 CREATE SEQUENCE domain_gid
@@ -55,13 +57,26 @@
 -- Insert default Maildir-folder name
 INSERT INTO maillocation(directory) VALUES ('Maildir');
 
+CREATE TABLE quotalimit (
+    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
+    bytes       bigint NOT NULL,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
+    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
+);
+-- Insert default (non) quota limit
+INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
+
 CREATE TABLE domain_data (
     gid         bigint NOT NULL DEFAULT nextval('domain_gid'),
     tid         bigint NOT NULL DEFAULT 1, -- default transport
+    qid         bigint NOT NULL DEFAULT 1, -- default quota limit
     domaindir   varchar(40) NOT NULL, --/srv/mail/$RAND/4294967294
     CONSTRAINT  pkey_domain_data PRIMARY KEY (gid),
     CONSTRAINT  fkey_domain_data_tid_transport FOREIGN KEY (tid)
-        REFERENCES transport (tid)
+        REFERENCES transport (tid),
+    CONSTRAINT  fkey_domain_data_qid_quotalimit FOREIGN KEY (qid)
+        REFERENCES quotalimit (qid)
 );
 
 CREATE TABLE domain_name (
@@ -81,6 +96,7 @@
     gid         bigint NOT NULL,
     mid         bigint NOT NULL DEFAULT 1,
     tid         bigint NOT NULL DEFAULT 1,
+    qid         bigint NOT NULL DEFAULT 1,
     smtp        boolean NOT NULL DEFAULT TRUE,
     pop3        boolean NOT NULL DEFAULT TRUE,
     imap        boolean NOT NULL DEFAULT TRUE,
@@ -92,7 +108,18 @@
     CONSTRAINT  fkey_users_mid_maillocation FOREIGN KEY (mid)
         REFERENCES maillocation (mid),
     CONSTRAINT  fkey_users_tid_transport FOREIGN KEY (tid)
-        REFERENCES transport (tid)
+        REFERENCES transport (tid),
+    CONSTRAINT  fkey_users_qid_quotalimit FOREIGN KEY (qid)
+        REFERENCES quotalimit (qid)
+);
+
+CREATE TABLE userquota_11 (
+    uid         bigint NOT NULL,
+    path        varchar(16) NOT NULL,
+    current     bigint NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota_11 PRIMARY KEY (uid, path),
+    CONSTRAINT  fkey_userquota_11_uid_users FOREIGN KEY (uid)
+        REFERENCES users (uid)
 );
 
 CREATE TABLE alias (
@@ -211,3 +238,20 @@
 
 CREATE TRIGGER primary_count_upd AFTER UPDATE ON domain_name
     FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
+
+
+CREATE OR REPLACE FUNCTION merge_userquota_11() RETURNS TRIGGER AS $$
+BEGIN
+    UPDATE userquota_11
+       SET current = current + NEW.current
+     WHERE uid = NEW.uid AND path = NEW.path;
+    IF found THEN
+        RETURN NULL;
+    ELSE
+        RETURN NEW;
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mergeuserquota_11 BEFORE INSERT ON userquota_11
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota_11();
--- a/pgsql/update_tables_0.5.x-0.6-dovecot-1.2.x.pgsql	Wed Feb 02 21:09:50 2011 +0000
+++ b/pgsql/update_tables_0.5.x-0.6-dovecot-1.2.x.pgsql	Fri Feb 04 17:29:35 2011 +0000
@@ -22,6 +22,7 @@
 DROP VIEW postfix_maildir;
 
 CREATE SEQUENCE mailboxformat_id;
+CREATE SEQUENCE quotalimit_id;
 
 CREATE TABLE mailboxformat (
     fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
@@ -44,6 +45,78 @@
     FOREIGN KEY (fid) REFERENCES mailboxformat (fid);
 
 
+-- ---
+-- Add quota stuff
+-- ---
+CREATE TABLE quotalimit (
+    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
+    bytes       bigint NOT NULL,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
+    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
+);
+-- Insert default (non) quota limit
+INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
+
+-- Adjust tables
+ALTER TABLE domain_data ADD COLUMN qid bigint NOT NULL DEFAULT 1;
+ALTER TABLE domain_data ADD CONSTRAINT fkey_domain_data_qid_quotalimit
+    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
+
+ALTER TABLE users ADD COLUMN qid bigint NOT NULL DEFAULT 1;
+ALTER TABLE users ADD CONSTRAINT fkey_users_qid_quotalimit
+    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
+
+CREATE TABLE userquota (
+    uid         bigint NOT NULL,
+    bytes       bigint NOT NULL DEFAULT 0,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota PRIMARY KEY (uid),
+    CONSTRAINT  fkey_userquota_uid_users FOREIGN KEY (uid)
+        REFERENCES users (uid)
+);
+
+CREATE OR REPLACE FUNCTION merge_userquota() RETURNS TRIGGER AS $$
+BEGIN
+    IF NEW.messages < 0 OR NEW.messages IS NULL THEN
+        IF NEW.messages IS NULL THEN
+            NEW.messages = 0;
+        ELSE
+            NEW.messages = -NEW.messages;
+        END IF;
+        RETURN NEW;
+    END IF;
+    LOOP
+        UPDATE userquota
+           SET bytes = bytes + NEW.bytes, messages = messages + NEW.messages
+         WHERE uid = NEW.uid;
+        IF found THEN
+            RETURN NULL;
+        END IF;
+        BEGIN
+            IF NEW.messages = 0 THEN
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, NULL);
+            ELSE
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, -NEW.messages);
+            END IF;
+            RETURN NULL;
+        EXCEPTION
+            WHEN unique_violation THEN
+                -- do nothing, and loop to try the UPDATE again
+            WHEN foreign_key_violation THEN
+                -- break the loop: a non matching uid means no such user
+                RETURN NULL;
+        END;
+    END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mergeuserquota BEFORE INSERT ON userquota
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota();
+
+-- ---
+-- Restore views
+-- ---
 CREATE VIEW dovecot_user AS
     SELECT local_part || '@' || domain_name.domainname AS userid,
            uid, gid, domain_data.domaindir || '/' || uid AS home,
--- a/pgsql/update_tables_0.5.x-0.6.pgsql	Wed Feb 02 21:09:50 2011 +0000
+++ b/pgsql/update_tables_0.5.x-0.6.pgsql	Fri Feb 04 17:29:35 2011 +0000
@@ -22,6 +22,7 @@
 DROP VIEW postfix_maildir;
 
 CREATE SEQUENCE mailboxformat_id;
+CREATE SEQUENCE quotalimit_id;
 
 CREATE TABLE mailboxformat (
     fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
@@ -44,6 +45,56 @@
     FOREIGN KEY (fid) REFERENCES mailboxformat (fid);
 
 
+-- ---
+-- Add quota stuff
+-- ---
+CREATE TABLE quotalimit (
+    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
+    bytes       bigint NOT NULL,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
+    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
+);
+-- Insert default (non) quota limit
+INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
+
+-- Adjust tables …
+ALTER TABLE domain_data ADD COLUMN qid bigint NOT NULL DEFAULT 1;
+ALTER TABLE domain_data ADD CONSTRAINT fkey_domain_data_qid_quotalimit
+    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
+
+ALTER TABLE users ADD COLUMN qid bigint NOT NULL DEFAULT 1;
+ALTER TABLE users ADD CONSTRAINT fkey_users_qid_quotalimit
+    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
+
+CREATE TABLE userquota_11 (
+    uid         bigint NOT NULL,
+    path        varchar(16) NOT NULL,
+    current     bigint NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota_11 PRIMARY KEY (uid, path),
+    CONSTRAINT  fkey_userquota_11_uid_users FOREIGN KEY (uid)
+        REFERENCES users (uid)
+);
+
+CREATE OR REPLACE FUNCTION merge_userquota_11() RETURNS TRIGGER AS $$
+BEGIN
+    UPDATE userquota_11
+       SET current = current + NEW.current
+     WHERE uid = NEW.uid AND path = NEW.path;
+    IF found THEN
+        RETURN NULL;
+    ELSE
+        RETURN NEW;
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mergeuserquota_11 BEFORE INSERT ON userquota_11
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota_11();
+
+-- ---
+-- Restore views
+-- ---
 CREATE VIEW dovecot_user AS
     SELECT local_part || '@' || domain_name.domainname AS userid,
            uid, gid, domain_data.domaindir || '/' || uid AS home,
--- a/pgsql/update_types_and_functions_0.5.x-0.6-dovecot-1.2.x.pgsql	Wed Feb 02 21:09:50 2011 +0000
+++ b/pgsql/update_types_and_functions_0.5.x-0.6-dovecot-1.2.x.pgsql	Fri Feb 04 17:29:35 2011 +0000
@@ -84,6 +84,17 @@
 RETURNS NULL ON NULL INPUT
 EXTERNAL SECURITY INVOKER;
 -- ---
+-- Data type for function dovecotquotauser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotquotauser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text,
+    quota_rule  text
+);
+-- ---
 -- Parameters (from login name [localpart@the_domain]):
 --      varchar localpart
 --      varchar the_domain
@@ -121,3 +132,40 @@
 $$ LANGUAGE plpgsql STABLE
 RETURNS NULL ON NULL INPUT
 EXTERNAL SECURITY INVOKER;
+-- ---
+-- Nearly the same as function dovecotuser above. It returns additionally the
+-- field quota_rule.
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT
+--          ON users, domain_data, domain_name, maillocation, mailboxformat,
+--             quotalimit
+--          TO dovecot;
+-- ---
+CREATE OR REPLACE FUNCTION dovecotquotauser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
+AS $$
+    DECLARE
+        record dovecotquotauser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail, '*:bytes=' ||
+                   bytes || ':messages=' || messages AS quota_rule
+              FROM users, domain_data, mailboxformat, maillocation, quotalimit
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+               AND quotalimit.qid = users.qid
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
--- a/pgsql/update_types_and_functions_0.5.x-0.6.pgsql	Wed Feb 02 21:09:50 2011 +0000
+++ b/pgsql/update_types_and_functions_0.5.x-0.6.pgsql	Fri Feb 04 17:29:35 2011 +0000
@@ -121,3 +121,51 @@
 $$ LANGUAGE plpgsql STABLE
 RETURNS NULL ON NULL INPUT
 EXTERNAL SECURITY INVOKER;
+-- ---
+-- Data type for function dovecotquotauser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotquotauser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text,
+    quota_rule  text
+);
+-- ---
+-- Nearly the same as function dovecotuser above. It returns additionally the
+-- field quota_rule.
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT
+--          ON users, domain_data, domain_name, maillocation, mailboxformat,
+--             quotalimit
+--          TO dovecot;
+-- ---
+CREATE OR REPLACE FUNCTION dovecotquotauser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
+AS $$
+    DECLARE
+        record dovecotquotauser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail, '*:bytes=' ||
+                   bytes || ':messages=' || messages AS quota_rule
+              FROM users, domain_data, mailboxformat, maillocation, quotalimit
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+               AND quotalimit.qid = users.qid
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;