toward splitting things out
[migration-tools.git] / sql / base / 00-infrastructure.sql
1 -- Copyright 2009-2012, Equinox Software, Inc.
2 --
3 -- This program is free software; you can redistribute it and/or
4 -- modify it under the terms of the GNU General Public License
5 -- as published by the Free Software Foundation; either version 2
6 -- of the License, or (at your option) any later version.
7 --
8 -- This program is distributed in the hope that it will be useful,
9 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
10 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 -- GNU General Public License for more details.
12 --
13 -- You should have received a copy of the GNU General Public License
14 -- along with this program; if not, write to the Free Software
15 -- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16
17 --------------------------------------------------------------------------
18 -- An example of how to use:
19 -- 
20 -- DROP SCHEMA foo CASCADE; CREATE SCHEMA foo; 
21 -- \i base.sql
22 -- SELECT migration_tools.init('foo');
23 -- SELECT migration_tools.build('foo');
24 -- SELECT * FROM foo.fields_requiring_mapping;
25 -- \d foo.actor_usr
26 -- create some incoming ILS specific staging tables, like CREATE foo.legacy_items ( l_barcode TEXT, .. ) INHERITS (foo.asset_copy);
27 -- Do some mapping, like UPDATE foo.legacy_items SET barcode = TRIM(BOTH ' ' FROM l_barcode);
28 -- Then, to move into production, do things like:
29 --  insert into asset.copy select * from asset_copy where id not in (select id from asset_copy_legacy where not x_migrate);
30
31 DROP SCHEMA IF EXISTS migration_tools CASCADE;
32 CREATE SCHEMA migration_tools;
33
34 CREATE OR REPLACE FUNCTION migration_tools.production_tables (TEXT) RETURNS TEXT[] AS $$
35     DECLARE
36         migration_schema ALIAS FOR $1;
37         output  RECORD;
38     BEGIN
39         FOR output IN
40             EXECUTE 'SELECT string_to_array(value,'','') AS tables FROM ' || migration_schema || '.config WHERE key = ''production_tables'';'
41         LOOP
42             RETURN output.tables;
43         END LOOP;
44     END;
45 $$ LANGUAGE PLPGSQL STRICT STABLE;
46
47 CREATE OR REPLACE FUNCTION migration_tools.init (TEXT) RETURNS VOID AS $$
48     DECLARE
49         migration_schema ALIAS FOR $1;
50         sql TEXT;
51     BEGIN
52         EXECUTE 'DROP TABLE IF EXISTS ' || migration_schema || '.config;' );
53         EXECUTE 'CREATE TABLE ' || migration_schema || '.config ( key TEXT UNIQUE, value TEXT);' );
54         EXECUTE 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''production_tables'', ''asset.call_number,asset.call_number_prefix,asset.call_number_suffix,asset.copy_location,asset.copy,asset.copy_alert,asset.stat_cat,asset.stat_cat_entry,asset.stat_cat_entry_copy_map,asset.copy_note,actor.usr,actor.card,actor.usr_address,actor.stat_cat,actor.stat_cat_entry,actor.stat_cat_entry_usr_map,actor.usr_note,actor.usr_standing_penalty,actor.usr_setting,action.circulation,action.hold_request,action.hold_notification,action.hold_request_note,action.hold_transit_copy,action.transit_copy,money.grocery,money.billing,money.cash_payment,money.forgive_payment,acq.provider,acq.provider_address,acq.provider_note,acq.provider_contact,acq.provider_contact_address,acq.fund,acq.fund_allocation,acq.fund_tag,acq.fund_tag_map,acq.funding_source,acq.funding_source_credit,acq.lineitem,acq.purchase_order,acq.po_item,acq.invoice,acq.invoice_item,acq.invoice_entry,acq.lineitem_detail,acq.fund_debit,acq.fund_transfer,acq.po_note,config.circ_matrix_matchpoint,config.circ_matrix_limit_set_map,config.hold_matrix_matchpoint,asset.copy_tag,asset.copy_tag_copy_map,config.copy_tag_type,serial.item,serial.item_note,serial.record_entry,biblio.record_entry'' );' );
55     END;
56 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
57
58 CREATE OR REPLACE FUNCTION migration_tools.build (TEXT) RETURNS VOID AS $$
59     DECLARE
60         migration_schema ALIAS FOR $1;
61         production_tables TEXT[];
62     BEGIN
63         SELECT migration_tools.production_tables(migration_schema) INTO STRICT production_tables;
64         PERFORM migration_tools.build_base_staging_tables(migration_schema,production_tables);
65         EXECUTE 'CREATE UNIQUE INDEX ' || migration_schema || '_patron_barcode_key ON ' || migration_schema || '.actor_card ( barcode );' );
66         EXECUTE 'CREATE UNIQUE INDEX ' || migration_schema || '_patron_usrname_key ON ' || migration_schema || '.actor_usr ( usrname );' );
67         EXECUTE 'CREATE UNIQUE INDEX ' || migration_schema || '_copy_barcode_key ON ' || migration_schema || '.asset_copy ( barcode );' );
68         EXECUTE 'CREATE UNIQUE INDEX ' || migration_schema || '_copy_id_key ON ' || migration_schema || '.asset_copy ( id );' );
69         EXECUTE 'CREATE INDEX ' || migration_schema || '_callnum_record_idx ON ' || migration_schema || '.asset_call_number ( record );' );
70         EXECUTE 'CREATE INDEX ' || migration_schema || '_callnum_upper_label_id_lib_idx ON ' || migration_schema || '.asset_call_number ( UPPER(label),id,owning_lib );' );
71         EXECUTE 'CREATE UNIQUE INDEX ' || migration_schema || '_callnum_label_once_per_lib ON ' || migration_schema || '.asset_call_number ( record,owning_lib,label,prefix,suffix );' );
72     END;
73 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
74
75 CREATE OR REPLACE FUNCTION migration_tools.build_base_staging_tables (TEXT,TEXT[]) RETURNS VOID AS $$
76     DECLARE
77         migration_schema ALIAS FOR $1;
78         production_tables ALIAS FOR $2;
79     BEGIN
80         FOR i IN array_lower(production_tables,1) .. array_upper(production_tables,1) LOOP
81             PERFORM migration_tools.build_specific_base_staging_table(migration_schema,production_tables[i]);
82         END LOOP;
83     END;
84 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
85
86 CREATE OR REPLACE FUNCTION migration_tools.build_specific_base_staging_table (TEXT,TEXT) RETURNS VOID AS $$
87     DECLARE
88         migration_schema ALIAS FOR $1;
89         production_table ALIAS FOR $2;
90         base_staging_table TEXT;
91         columns RECORD;
92     BEGIN
93         base_staging_table = REPLACE( production_table, '.', '_' );
94         EXECUTE 'CREATE TABLE ' || migration_schema || '.' || base_staging_table || ' ( LIKE ' || production_table || ' INCLUDING DEFAULTS EXCLUDING CONSTRAINTS );' );
95         FOR columns IN 
96             SELECT table_schema, table_name, column_name, data_type
97             FROM information_schema.columns 
98             WHERE table_schema = migration_schema AND table_name = base_staging_table AND is_nullable = 'NO' AND column_default IS NULL
99         LOOP
100             EXECUTE( $1, 'ALTER TABLE ' || columns.table_schema || '.' || columns.table_name || ' ALTER COLUMN ' || columns.column_name || ' DROP NOT NULL;' );
101         END LOOP;
102     END;
103 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
104
105 CREATE OR REPLACE FUNCTION migration_tools.create_linked_legacy_table_from (TEXT,TEXT,TEXT) RETURNS VOID AS $$
106     DECLARE
107         migration_schema ALIAS FOR $1;
108         parent_table ALIAS FOR $2;
109         source_table ALIAS FOR $3;
110         columns RECORD;
111         create_sql TEXT;
112         insert_sql TEXT;
113         column_list TEXT := '';
114         column_count INTEGER := 0;
115     BEGIN
116         create_sql := 'CREATE TABLE ' || migration_schema || '.' || parent_table || '_legacy ( ';
117         FOR columns IN
118             SELECT table_schema, table_name, column_name, data_type
119             FROM information_schema.columns
120             WHERE table_schema = migration_schema AND table_name = source_table
121         LOOP
122             column_count := column_count + 1;
123             if column_count > 1 then
124                 create_sql := create_sql || ', ';
125                 column_list := column_list || ', ';
126             end if;
127             create_sql := create_sql || columns.column_name || ' ';
128             if columns.data_type = 'ARRAY' then
129                 create_sql := create_sql || 'TEXT[]';
130             else
131                 create_sql := create_sql || columns.data_type;
132             end if;
133             column_list := column_list || columns.column_name;
134         END LOOP;
135         create_sql := create_sql || ' ) INHERITS ( ' || migration_schema || '.' || parent_table || ' );';
136         --RAISE INFO 'create_sql = %', create_sql;
137         EXECUTE create_sql;
138         insert_sql := 'INSERT INTO ' || migration_schema || '.' || parent_table || '_legacy (' || column_list || ') SELECT ' || column_list || ' FROM ' || migration_schema || '.' || source_table || ';';
139         --RAISE INFO 'insert_sql = %', insert_sql;
140         EXECUTE insert_sql;
141     END;
142 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
143
144 CREATE OR REPLACE FUNCTION migration_tools.attempt_cast (TEXT, TEXT) RETURNS TEXT AS $$
145     DECLARE
146         attempt_value ALIAS FOR $1;
147         datatype ALIAS FOR $2;
148     BEGIN
149         EXECUTE 'SELECT ' || quote_literal(attempt_value) || '::' || datatype || ' AS a;';
150         RETURN attempt_value;
151     EXCEPTION
152         WHEN OTHERS THEN RETURN NULL;
153     END;
154 $$ LANGUAGE PLPGSQL STRICT STABLE;
155
156 CREATE OR REPLACE FUNCTION migration_tools.attempt_date (TEXT,TEXT) RETURNS DATE AS $$
157     DECLARE
158         attempt_value ALIAS FOR $1;
159         fail_value ALIAS FOR $2;
160         output DATE;
161     BEGIN
162         FOR output IN
163             EXECUTE 'SELECT ' || quote_literal(REGEXP_REPLACE(attempt_value,'^(\d\d)(\d\d)(\d\d)$','\1-\2-\3')) || '::date AS a;'
164         LOOP
165             RETURN output;
166         END LOOP;
167     EXCEPTION
168         WHEN OTHERS THEN
169             FOR output IN
170                 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::date AS a;'
171             LOOP
172                 RETURN output;
173             END LOOP;
174     END;
175 $$ LANGUAGE PLPGSQL STRICT STABLE;
176
177 CREATE OR REPLACE FUNCTION migration_tools.attempt_timestamptz (TEXT,TEXT) RETURNS TIMESTAMPTZ AS $$
178     DECLARE
179         attempt_value ALIAS FOR $1;
180         fail_value ALIAS FOR $2;
181         output TIMESTAMPTZ;
182     BEGIN
183         FOR output IN
184             EXECUTE 'SELECT ' || quote_literal(attempt_value) || '::TIMESTAMPTZ AS a;'
185         LOOP
186             RETURN output;
187         END LOOP;
188     EXCEPTION
189         WHEN OTHERS THEN
190             FOR output IN
191                 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::TIMESTAMPTZ AS a;'
192             LOOP
193                 RETURN output;
194             END LOOP;
195     END;
196 $$ LANGUAGE PLPGSQL STRICT STABLE;
197
198 CREATE OR REPLACE FUNCTION migration_tools.attempt_money (TEXT,TEXT) RETURNS NUMERIC(8,2) AS $$
199     DECLARE
200         attempt_value ALIAS FOR $1;
201         fail_value ALIAS FOR $2;
202         output NUMERIC(8,2);
203     BEGIN
204         FOR output IN
205             EXECUTE 'SELECT ' || quote_literal(REPLACE(REPLACE(attempt_value,'$',''),',','')) || '::NUMERIC(8,2) AS a;'
206         LOOP
207             RETURN output;
208         END LOOP;
209     EXCEPTION
210         WHEN OTHERS THEN
211             FOR output IN
212                 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::NUMERIC(8,2) AS a;'
213             LOOP
214                 RETURN output;
215             END LOOP;
216     END;
217 $$ LANGUAGE PLPGSQL STRICT STABLE;
218
219 CREATE OR REPLACE FUNCTION migration_tools.attempt_money6 (TEXT,TEXT) RETURNS NUMERIC(6,2) AS $$
220     DECLARE
221         attempt_value ALIAS FOR $1;
222         fail_value ALIAS FOR $2;
223         output NUMERIC(6,2);
224     BEGIN
225         FOR output IN
226             EXECUTE 'SELECT ' || quote_literal(REPLACE(REPLACE(attempt_value,'$',''),',','')) || '::NUMERIC(6,2) AS a;'
227         LOOP
228             RETURN output;
229         END LOOP;
230     EXCEPTION
231         WHEN OTHERS THEN
232             FOR output IN
233                 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::NUMERIC(6,2) AS a;'
234             LOOP
235                 RETURN output;
236             END LOOP;
237     END;
238 $$ LANGUAGE PLPGSQL STRICT STABLE;
239
240 CREATE OR REPLACE FUNCTION migration_tools.attempt_money_from_pennies (TEXT,TEXT) RETURNS NUMERIC(8,2) AS $$
241     DECLARE
242         attempt_value ALIAS FOR $1;
243         fail_value ALIAS FOR $2;
244         output NUMERIC(8,2);
245     BEGIN
246         IF length(regexp_replace(attempt_value,'^0+','')) > 10 THEN
247             RAISE EXCEPTION 'too many digits';
248         END IF;
249         FOR output IN
250             EXECUTE 'SELECT ' || quote_literal((left(lpad(regexp_replace(attempt_value,'^0+',''),10,'0'),-2) || '.' || right(lpad(regexp_replace(attempt_value,'^0+',''),10,'0'),2))::numeric(8,2)) || '::NUMERIC(8,2) AS a;'
251         LOOP
252             RETURN output;
253         END LOOP;
254     EXCEPTION
255         WHEN OTHERS THEN
256             FOR output IN
257                 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::NUMERIC(8,2) AS a;'
258             LOOP
259                 RETURN output;
260             END LOOP;
261     END;
262 $$ LANGUAGE PLPGSQL STRICT STABLE;
263
264 CREATE OR REPLACE FUNCTION migration_tools.attempt_money_from_pennies6 (TEXT,TEXT) RETURNS NUMERIC(6,2) AS $$
265     DECLARE
266         attempt_value ALIAS FOR $1;
267         fail_value ALIAS FOR $2;
268         output NUMERIC(6,2);
269     BEGIN
270         IF length(regexp_replace(attempt_value,'^0+','')) > 8 THEN
271             RAISE EXCEPTION 'too many digits';
272         END IF;
273         FOR output IN
274             EXECUTE 'SELECT ' || quote_literal((left(lpad(regexp_replace(attempt_value,'^0+',''),8,'0'),-2) || '.' || right(lpad(regexp_replace(attempt_value,'^0+',''),8,'0'),2))::numeric(6,2)) || '::NUMERIC(6,2) AS a;'
275         LOOP
276             RETURN output;
277         END LOOP;
278     EXCEPTION
279         WHEN OTHERS THEN
280             FOR output IN
281                 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::NUMERIC(6,2) AS a;'
282             LOOP
283                 RETURN output;
284             END LOOP;
285     END;
286 $$ LANGUAGE PLPGSQL STRICT STABLE;
287