LP#1689608: stamp schema update
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 1056.schema.patron_batch_update.sql
1 BEGIN;
2
3 INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1056', :eg_version); -- miker/gmcharlt
4
5 INSERT INTO permission.perm_list (id,code,description) VALUES (592,'CONTAINER_BATCH_UPDATE','Allow batch update via buckets');
6
7 INSERT INTO container.user_bucket_type (code,label) SELECT code,label FROM container.copy_bucket_type where code = 'staff_client';
8
9 CREATE TABLE action.fieldset_group (
10     id              SERIAL  PRIMARY KEY,
11     name            TEXT        NOT NULL,
12     create_time     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13     complete_time   TIMESTAMPTZ,
14     container       INT,        -- Points to a container of some type ...
15     container_type  TEXT,       -- One of 'biblio_record_entry', 'user', 'call_number', 'copy'
16     can_rollback    BOOL        DEFAULT TRUE,
17     rollback_group  INT         REFERENCES action.fieldset_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
18     rollback_time   TIMESTAMPTZ,
19     creator         INT         NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
20     owning_lib      INT         NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
21 );
22
23 ALTER TABLE action.fieldset ADD COLUMN fieldset_group INT REFERENCES action.fieldset_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
24 ALTER TABLE action.fieldset ADD COLUMN error_msg TEXT;
25 ALTER TABLE container.biblio_record_entry_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
26 ALTER TABLE container.user_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
27 ALTER TABLE container.call_number_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
28 ALTER TABLE container.copy_bucket ADD COLUMN owning_lib INT REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
29
30 UPDATE query.stored_query SET id = id + 1000 WHERE id < 1000;
31 UPDATE query.from_relation SET id = id + 1000 WHERE id < 1000;
32 UPDATE query.expression SET id = id + 1000 WHERE id < 1000;
33
34 SELECT SETVAL('query.stored_query_id_seq', 1, FALSE);
35 SELECT SETVAL('query.from_relation_id_seq', 1, FALSE);
36 SELECT SETVAL('query.expression_id_seq', 1, FALSE);
37
38 INSERT INTO query.bind_variable (name,type,description,label)
39     SELECT  'bucket','number','ID of the bucket to pull items from','Bucket ID'
40       WHERE NOT EXISTS (SELECT 1 FROM query.bind_variable WHERE name = 'bucket');
41
42 -- Assumes completely empty 'query' schema
43 INSERT INTO query.stored_query (type, use_distinct) VALUES ('SELECT', TRUE); -- 1
44
45 INSERT INTO query.from_relation (type, table_name, class_name, table_alias) VALUES ('RELATION', 'container.user_bucket_item', 'cubi', 'cubi'); -- 1
46 UPDATE query.stored_query SET from_clause = 1;
47
48 INSERT INTO query.expr_xcol (table_alias, column_name) VALUES ('cubi', 'target_user'); -- 1
49 INSERT INTO query.select_item (stored_query,seq_no,expression) VALUES (1,1,1);
50
51 INSERT INTO query.expr_xcol (table_alias, column_name) VALUES ('cubi', 'bucket'); -- 2
52 INSERT INTO query.expr_xbind (bind_variable) VALUES ('bucket'); -- 3
53
54 INSERT INTO query.expr_xop (left_operand, operator, right_operand) VALUES (2, '=', 3); -- 4
55 UPDATE query.stored_query SET where_clause = 4;
56
57 SELECT SETVAL('query.stored_query_id_seq', 1000, TRUE) FROM query.stored_query;
58 SELECT SETVAL('query.from_relation_id_seq', 1000, TRUE) FROM query.from_relation;
59 SELECT SETVAL('query.expression_id_seq', 10000, TRUE) FROM query.expression;
60
61 CREATE OR REPLACE FUNCTION action.apply_fieldset(
62     fieldset_id IN INT,        -- id from action.fieldset
63     table_name  IN TEXT,       -- table to be updated
64     pkey_name   IN TEXT,       -- name of primary key column in that table
65     query       IN TEXT        -- query constructed by qstore (for query-based
66                                --    fieldsets only; otherwise null
67 )
68 RETURNS TEXT AS $$
69 DECLARE
70     statement TEXT;
71     where_clause TEXT;
72     fs_status TEXT;
73     fs_pkey_value TEXT;
74     fs_query TEXT;
75     sep CHAR;
76     status_code TEXT;
77     msg TEXT;
78     fs_id INT;
79     fsg_id INT;
80     update_count INT;
81     cv RECORD;
82     fs_obj action.fieldset%ROWTYPE;
83     fs_group action.fieldset_group%ROWTYPE;
84     rb_row RECORD;
85 BEGIN
86     -- Sanity checks
87     IF fieldset_id IS NULL THEN
88         RETURN 'Fieldset ID parameter is NULL';
89     END IF;
90     IF table_name IS NULL THEN
91         RETURN 'Table name parameter is NULL';
92     END IF;
93     IF pkey_name IS NULL THEN
94         RETURN 'Primary key name parameter is NULL';
95     END IF;
96
97     SELECT
98         status,
99         quote_literal( pkey_value )
100     INTO
101         fs_status,
102         fs_pkey_value
103     FROM
104         action.fieldset
105     WHERE
106         id = fieldset_id;
107
108     --
109     -- Build the WHERE clause.  This differs according to whether it's a
110     -- single-row fieldset or a query-based fieldset.
111     --
112     IF query IS NULL        AND fs_pkey_value IS NULL THEN
113         RETURN 'Incomplete fieldset: neither a primary key nor a query available';
114     ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
115         fs_query := rtrim( query, ';' );
116         where_clause := 'WHERE ' || pkey_name || ' IN ( '
117                      || fs_query || ' )';
118     ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
119         where_clause := 'WHERE ' || pkey_name || ' = ';
120         IF pkey_name = 'id' THEN
121             where_clause := where_clause || fs_pkey_value;
122         ELSIF pkey_name = 'code' THEN
123             where_clause := where_clause || quote_literal(fs_pkey_value);
124         ELSE
125             RETURN 'Only know how to handle "id" and "code" pkeys currently, received ' || pkey_name;
126         END IF;
127     ELSE  -- both are not null
128         RETURN 'Ambiguous fieldset: both a primary key and a query provided';
129     END IF;
130
131     IF fs_status IS NULL THEN
132         RETURN 'No fieldset found for id = ' || fieldset_id;
133     ELSIF fs_status = 'APPLIED' THEN
134         RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
135     END IF;
136
137     SELECT * INTO fs_obj FROM action.fieldset WHERE id = fieldset_id;
138     SELECT * INTO fs_group FROM action.fieldset_group WHERE id = fs_obj.fieldset_group;
139
140     IF fs_group.can_rollback THEN
141         -- This is part of a non-rollback group.  We need to record the current values for future rollback.
142
143         INSERT INTO action.fieldset_group (can_rollback, name, creator, owning_lib, container, container_type)
144             VALUES (FALSE, 'ROLLBACK: '|| fs_group.name, fs_group.creator, fs_group.owning_lib, fs_group.container, fs_group.container_type);
145
146         fsg_id := CURRVAL('action.fieldset_group_id_seq');
147
148         FOR rb_row IN EXECUTE 'SELECT * FROM ' || table_name || ' ' || where_clause LOOP
149             IF pkey_name = 'id' THEN
150                 fs_pkey_value := rb_row.id;
151             ELSIF pkey_name = 'code' THEN
152                 fs_pkey_value := rb_row.code;
153             ELSE
154                 RETURN 'Only know how to handle "id" and "code" pkeys currently, received ' || pkey_name;
155             END IF;
156             INSERT INTO action.fieldset (fieldset_group,owner,owning_lib,status,classname,name,pkey_value)
157                 VALUES (fsg_id, fs_obj.owner, fs_obj.owning_lib, 'PENDING', fs_obj.classname, fs_obj.name || ' ROLLBACK FOR ' || fs_pkey_value, fs_pkey_value);
158
159             fs_id := CURRVAL('action.fieldset_id_seq');
160             sep := '';
161             FOR cv IN
162                 SELECT  DISTINCT col
163                 FROM    action.fieldset_col_val
164                 WHERE   fieldset = fieldset_id
165             LOOP
166                 EXECUTE 'INSERT INTO action.fieldset_col_val (fieldset, col, val) ' || 
167                     'SELECT '|| fs_id || ', '||quote_literal(cv.col)||', '||cv.col||' FROM '||table_name||' WHERE '||pkey_name||' = '||fs_pkey_value;
168             END LOOP;
169         END LOOP;
170     END IF;
171
172     statement := 'UPDATE ' || table_name || ' SET';
173
174     sep := '';
175     FOR cv IN
176         SELECT  col,
177                 val
178         FROM    action.fieldset_col_val
179         WHERE   fieldset = fieldset_id
180     LOOP
181         statement := statement || sep || ' ' || cv.col
182                      || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
183         sep := ',';
184     END LOOP;
185
186     IF sep = '' THEN
187         RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
188     END IF;
189     statement := statement || ' ' || where_clause;
190
191     --
192     -- Execute the update
193     --
194     BEGIN
195         EXECUTE statement;
196         GET DIAGNOSTICS update_count = ROW_COUNT;
197
198         IF update_count = 0 THEN
199             RAISE data_exception;
200         END IF;
201
202         IF fsg_id IS NOT NULL THEN
203             UPDATE action.fieldset_group SET rollback_group = fsg_id WHERE id = fs_group.id;
204         END IF;
205
206         IF fs_group.id IS NOT NULL THEN
207             UPDATE action.fieldset_group SET complete_time = now() WHERE id = fs_group.id;
208         END IF;
209
210         UPDATE action.fieldset SET status = 'APPLIED', applied_time = now() WHERE id = fieldset_id;
211
212     EXCEPTION WHEN data_exception THEN
213         msg := 'No eligible rows found for fieldset ' || fieldset_id;
214         UPDATE action.fieldset SET status = 'ERROR', applied_time = now() WHERE id = fieldset_id;
215         RETURN msg;
216
217     END;
218
219     RETURN msg;
220
221 EXCEPTION WHEN OTHERS THEN
222     msg := 'Unable to apply fieldset ' || fieldset_id || ': ' || sqlerrm;
223     UPDATE action.fieldset SET status = 'ERROR', applied_time = now() WHERE id = fieldset_id;
224     RETURN msg;
225
226 END;
227 $$ LANGUAGE plpgsql;
228
229 COMMIT;
230