1 -- Copyright 2009-2012, Equinox Software, Inc.
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.
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.
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.
17 --------------------------------------------------------------------------
18 -- An example of how to use:
20 -- DROP SCHEMA foo CASCADE; CREATE SCHEMA foo;
22 -- SELECT migration_tools.init('foo');
23 -- SELECT migration_tools.build('foo');
24 -- SELECT * FROM foo.fields_requiring_mapping;
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: select migration_tools.insert_base_into_production('foo')
30 CREATE SCHEMA migration_tools;
32 CREATE OR REPLACE FUNCTION migration_tools.production_tables (TEXT) RETURNS TEXT[] AS $$
34 migration_schema ALIAS FOR $1;
38 EXECUTE 'SELECT string_to_array(value,'','') AS tables FROM ' || migration_schema || '.config WHERE key = ''production_tables'';'
43 $$ LANGUAGE PLPGSQL STRICT STABLE;
45 CREATE OR REPLACE FUNCTION migration_tools.country_code (TEXT) RETURNS TEXT AS $$
47 migration_schema ALIAS FOR $1;
51 EXECUTE 'SELECT value FROM ' || migration_schema || '.config WHERE key = ''country_code'';'
56 $$ LANGUAGE PLPGSQL STRICT STABLE;
59 CREATE OR REPLACE FUNCTION migration_tools.log (TEXT,TEXT,INTEGER) RETURNS VOID AS $$
61 migration_schema ALIAS FOR $1;
65 EXECUTE 'INSERT INTO ' || migration_schema || '.sql_log ( sql, row_count ) VALUES ( ' || quote_literal(sql) || ', ' || nrows || ' );';
67 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
69 CREATE OR REPLACE FUNCTION migration_tools.exec (TEXT,TEXT) RETURNS VOID AS $$
71 migration_schema ALIAS FOR $1;
75 EXECUTE 'UPDATE ' || migration_schema || '.sql_current SET sql = ' || quote_literal(sql) || ';';
76 --RAISE INFO '%', sql;
78 GET DIAGNOSTICS nrows = ROW_COUNT;
79 PERFORM migration_tools.log(migration_schema,sql,nrows);
82 RAISE EXCEPTION '!!!!!!!!!!! state = %, msg = %, sql = %', SQLSTATE, SQLERRM, sql;
84 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
86 CREATE OR REPLACE FUNCTION migration_tools.debug_exec (TEXT,TEXT) RETURNS VOID AS $$
88 migration_schema ALIAS FOR $1;
92 EXECUTE 'UPDATE ' || migration_schema || '.sql_current SET sql = ' || quote_literal(sql) || ';';
93 RAISE INFO 'debug_exec sql = %', sql;
95 GET DIAGNOSTICS nrows = ROW_COUNT;
96 PERFORM migration_tools.log(migration_schema,sql,nrows);
99 RAISE EXCEPTION '!!!!!!!!!!! state = %, msg = %, sql = %', SQLSTATE, SQLERRM, sql;
101 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
103 CREATE OR REPLACE FUNCTION migration_tools.init (TEXT) RETURNS VOID AS $$
105 migration_schema ALIAS FOR $1;
108 EXECUTE 'DROP TABLE IF EXISTS ' || migration_schema || '.sql_current;';
109 EXECUTE 'CREATE TABLE ' || migration_schema || '.sql_current ( sql TEXT);';
110 EXECUTE 'INSERT INTO ' || migration_schema || '.sql_current ( sql ) VALUES ( '''' );';
112 SELECT 'CREATE TABLE ' || migration_schema || '.sql_log ( time TIMESTAMP NOT NULL DEFAULT NOW(), row_count INTEGER, sql TEXT );' INTO STRICT sql;
116 RAISE INFO '!!!!!!!!!!! state = %, msg = %, sql = %', SQLSTATE, SQLERRM, sql;
118 PERFORM migration_tools.exec( $1, 'DROP TABLE IF EXISTS ' || migration_schema || '.config;' );
119 PERFORM migration_tools.exec( $1, 'CREATE TABLE ' || migration_schema || '.config ( key TEXT UNIQUE, value TEXT);' );
120 PERFORM migration_tools.exec( $1, '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.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'' );' );
121 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''country_code'', ''USA'' );' );
122 PERFORM migration_tools.exec( $1, 'DROP TABLE IF EXISTS ' || migration_schema || '.fields_requiring_mapping;' );
123 PERFORM migration_tools.exec( $1, 'CREATE TABLE ' || migration_schema || '.fields_requiring_mapping( table_schema TEXT, table_name TEXT, column_name TEXT, data_type TEXT);' );
124 PERFORM migration_tools.exec( $1, 'DROP TABLE IF EXISTS ' || migration_schema || '.base_profile_map;' );
125 PERFORM migration_tools.exec( $1, 'CREATE TABLE ' || migration_schema || E'.base_profile_map (
128 transcribed_perm_group TEXT,
136 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''base_profile_map'', ''base_profile_map'' );' );
137 PERFORM migration_tools.exec( $1, 'DROP TABLE IF EXISTS ' || migration_schema || '.base_item_dynamic_field_map;' );
138 PERFORM migration_tools.exec( $1, 'CREATE TABLE ' || migration_schema || E'.base_item_dynamic_field_map (
140 evergreen_field TEXT,
141 evergreen_value TEXT,
142 evergreen_datatype TEXT,
150 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_item_dynamic_lf1_idx ON ' || migration_schema || '.base_item_dynamic_field_map (legacy_field1,legacy_value1);' );
151 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_item_dynamic_lf2_idx ON ' || migration_schema || '.base_item_dynamic_field_map (legacy_field2,legacy_value2);' );
152 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_item_dynamic_lf3_idx ON ' || migration_schema || '.base_item_dynamic_field_map (legacy_field3,legacy_value3);' );
153 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''base_item_dynamic_field_map'', ''base_item_dynamic_field_map'' );' );
154 PERFORM migration_tools.exec( $1, 'DROP TABLE IF EXISTS ' || migration_schema || '.base_copy_location_map;' );
155 PERFORM migration_tools.exec( $1, 'CREATE TABLE ' || migration_schema || E'.base_copy_location_map (
158 holdable BOOLEAN NOT NULL DEFAULT TRUE,
159 hold_verify BOOLEAN NOT NULL DEFAULT FALSE,
160 opac_visible BOOLEAN NOT NULL DEFAULT TRUE,
161 circulate BOOLEAN NOT NULL DEFAULT TRUE,
162 transcribed_location TEXT,
170 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_base_copy_location_lf1_idx ON ' || migration_schema || '.base_copy_location_map (legacy_field1,legacy_value1);' );
171 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_base_copy_location_lf2_idx ON ' || migration_schema || '.base_copy_location_map (legacy_field2,legacy_value2);' );
172 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_base_copy_location_lf3_idx ON ' || migration_schema || '.base_copy_location_map (legacy_field3,legacy_value3);' );
173 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_base_copy_location_loc_idx ON ' || migration_schema || '.base_copy_location_map (transcribed_location);' );
174 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''base_copy_location_map'', ''base_copy_location_map'' );' );
175 PERFORM migration_tools.exec( $1, 'DROP TABLE IF EXISTS ' || migration_schema || '.base_circ_field_map;' );
176 PERFORM migration_tools.exec( $1, 'CREATE TABLE ' || migration_schema || E'.base_circ_field_map (
194 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_circ_dynamic_lf1_idx ON ' || migration_schema || '.base_circ_field_map (item_field1,item_value1);' );
195 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_circ_dynamic_lf2_idx ON ' || migration_schema || '.base_circ_field_map (item_field2,item_value2);' );
196 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_circ_dynamic_lf3_idx ON ' || migration_schema || '.base_circ_field_map (patron_field1,patron_value1);' );
197 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_circ_dynamic_lf4_idx ON ' || migration_schema || '.base_circ_field_map (patron_field2,patron_value2);' );
198 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''base_circ_field_map'', ''base_circ_field_map'' );' );
201 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_init'', now() );' );
203 WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_init'';' );
206 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
208 CREATE OR REPLACE FUNCTION migration_tools.build (TEXT) RETURNS VOID AS $$
210 migration_schema ALIAS FOR $1;
211 production_tables TEXT[];
213 --RAISE INFO 'In migration_tools.build(%)', migration_schema;
214 SELECT migration_tools.production_tables(migration_schema) INTO STRICT production_tables;
215 PERFORM migration_tools.build_base_staging_tables(migration_schema,production_tables);
216 PERFORM migration_tools.exec( $1, 'CREATE UNIQUE INDEX ' || migration_schema || '_patron_barcode_key ON ' || migration_schema || '.actor_card ( barcode );' );
217 PERFORM migration_tools.exec( $1, 'CREATE UNIQUE INDEX ' || migration_schema || '_patron_usrname_key ON ' || migration_schema || '.actor_usr ( usrname );' );
218 PERFORM migration_tools.exec( $1, 'CREATE UNIQUE INDEX ' || migration_schema || '_copy_barcode_key ON ' || migration_schema || '.asset_copy ( barcode );' );
219 PERFORM migration_tools.exec( $1, 'CREATE UNIQUE INDEX ' || migration_schema || '_copy_id_key ON ' || migration_schema || '.asset_copy ( id );' );
220 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_callnum_record_idx ON ' || migration_schema || '.asset_call_number ( record );' );
221 PERFORM migration_tools.exec( $1, 'CREATE INDEX ' || migration_schema || '_callnum_upper_label_id_lib_idx ON ' || migration_schema || '.asset_call_number ( UPPER(label),id,owning_lib );' );
222 PERFORM migration_tools.exec( $1, 'CREATE UNIQUE INDEX ' || migration_schema || '_callnum_label_once_per_lib ON ' || migration_schema || '.asset_call_number ( record,owning_lib,label,prefix,suffix );' );
224 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
226 CREATE OR REPLACE FUNCTION migration_tools.build_base_staging_tables (TEXT,TEXT[]) RETURNS VOID AS $$
228 migration_schema ALIAS FOR $1;
229 production_tables ALIAS FOR $2;
231 --RAISE INFO 'In migration_tools.build_base_staging_tables(%,%)', migration_schema, production_tables;
232 FOR i IN array_lower(production_tables,1) .. array_upper(production_tables,1) LOOP
233 PERFORM migration_tools.build_specific_base_staging_table(migration_schema,production_tables[i]);
236 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
238 CREATE OR REPLACE FUNCTION migration_tools.build_specific_base_staging_table (TEXT,TEXT) RETURNS VOID AS $$
240 migration_schema ALIAS FOR $1;
241 production_table ALIAS FOR $2;
242 base_staging_table TEXT;
245 base_staging_table = REPLACE( production_table, '.', '_' );
246 --RAISE INFO 'In migration_tools.build_specific_base_staging_table(%,%) -> %', migration_schema, production_table, base_staging_table;
247 PERFORM migration_tools.exec( $1, 'CREATE UNLOGGED TABLE ' || migration_schema || '.' || base_staging_table || ' ( LIKE ' || production_table || ' INCLUDING DEFAULTS EXCLUDING CONSTRAINTS );' );
248 PERFORM migration_tools.exec( $1, '
249 INSERT INTO ' || migration_schema || '.fields_requiring_mapping
250 SELECT table_schema, table_name, column_name, data_type
251 FROM information_schema.columns
252 WHERE table_schema = ''' || migration_schema || ''' AND table_name = ''' || base_staging_table || ''' AND is_nullable = ''NO'' AND column_default IS NULL;
255 SELECT table_schema, table_name, column_name, data_type
256 FROM information_schema.columns
257 WHERE table_schema = migration_schema AND table_name = base_staging_table AND is_nullable = 'NO' AND column_default IS NULL
259 PERFORM migration_tools.exec( $1, 'ALTER TABLE ' || columns.table_schema || '.' || columns.table_name || ' ALTER COLUMN ' || columns.column_name || ' DROP NOT NULL;' );
262 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
264 CREATE OR REPLACE FUNCTION migration_tools.create_linked_legacy_table_from (TEXT,TEXT,TEXT) RETURNS VOID AS $$
266 migration_schema ALIAS FOR $1;
267 parent_table ALIAS FOR $2;
268 source_table ALIAS FOR $3;
272 column_list TEXT := '';
273 column_count INTEGER := 0;
275 create_sql := 'CREATE UNLOGGED TABLE ' || migration_schema || '.' || parent_table || '_legacy ( ';
277 SELECT table_schema, table_name, column_name, data_type
278 FROM information_schema.columns
279 WHERE table_schema = migration_schema AND table_name = source_table
281 column_count := column_count + 1;
282 if column_count > 1 then
283 create_sql := create_sql || ', ';
284 column_list := column_list || ', ';
286 create_sql := create_sql || columns.column_name || ' ' || columns.data_type;
287 column_list := column_list || columns.column_name;
289 create_sql := create_sql || ' ) INHERITS ( ' || migration_schema || '.' || parent_table || ' );';
290 --RAISE INFO 'create_sql = %', create_sql;
292 insert_sql := 'INSERT INTO ' || migration_schema || '.' || parent_table || '_legacy (' || column_list || ') SELECT ' || column_list || ' FROM ' || migration_schema || '.' || source_table || ';';
293 --RAISE INFO 'insert_sql = %', insert_sql;
296 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
298 CREATE OR REPLACE FUNCTION migration_tools.insert_base_into_production (TEXT) RETURNS VOID AS $$
300 migration_schema ALIAS FOR $1;
301 production_tables TEXT[];
303 --RAISE INFO 'In migration_tools.insert_into_production(%)', migration_schema;
304 SELECT migration_tools.production_tables(migration_schema) INTO STRICT production_tables;
305 FOR i IN array_lower(production_tables,1) .. array_upper(production_tables,1) LOOP
306 PERFORM migration_tools.insert_into_production(migration_schema,production_tables[i]);
309 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
311 CREATE OR REPLACE FUNCTION migration_tools.insert_into_production (TEXT,TEXT) RETURNS VOID AS $$
313 migration_schema ALIAS FOR $1;
314 production_table ALIAS FOR $2;
315 base_staging_table TEXT;
318 base_staging_table = REPLACE( production_table, '.', '_' );
319 --RAISE INFO 'In migration_tools.insert_into_production(%,%) -> %', migration_schema, production_table, base_staging_table;
320 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || production_table || ' SELECT * FROM ' || migration_schema || '.' || base_staging_table || ';' );
322 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
324 CREATE OR REPLACE FUNCTION migration_tools.name_parse_out_first_middle_last_comma_suffix (TEXT) RETURNS TEXT[] AS $$
326 full_name TEXT := $1;
328 family_name TEXT := '';
329 first_given_name TEXT := '';
330 second_given_name TEXT := '';
334 before_comma := BTRIM( REGEXP_REPLACE(full_name,E'^(.+),.+$',E'\\1') );
335 suffix := CASE WHEN full_name ~ ',' THEN BTRIM( REGEXP_REPLACE(full_name,E'^.+,(.+)$',E'\\1') ) ELSE '' END;
337 IF suffix = before_comma THEN
341 family_name := BTRIM( REGEXP_REPLACE(before_comma,E'^.+\\s(.+)$',E'\\1') );
342 first_given_name := BTRIM( REGEXP_REPLACE(before_comma,E'^(.+?)\\s.+$',E'\\1') );
343 second_given_name := BTRIM( CASE WHEN before_comma ~ '^.+\s.+\s.+$' THEN REGEXP_REPLACE(before_comma,E'^.+\\s(.+)\\s.+$',E'\\1') ELSE '' END );
345 RETURN ARRAY[ family_name, prefix, first_given_name, second_given_name, suffix ];
347 $$ LANGUAGE PLPGSQL STRICT IMMUTABLE;
349 CREATE OR REPLACE FUNCTION migration_tools.name_parse_out_last_comma_prefix_first_middle_suffix (TEXT) RETURNS TEXT[] AS $$
351 full_name TEXT := $1;
353 family_name TEXT := '';
354 first_given_name TEXT := '';
355 second_given_name TEXT := '';
360 -- Use values, not structure, for prefix/suffix, unless we come up with a better idea
361 IF temp ilike '%MR.%' THEN
363 temp := REGEXP_REPLACE( temp, E'MR\.\\s*', '', 'i' );
365 IF temp ilike '%MRS.%' THEN
367 temp := REGEXP_REPLACE( temp, E'MRS\.\\s*', '', 'i' );
369 IF temp ilike '%MS.%' THEN
371 temp := REGEXP_REPLACE( temp, E'MS\.\\s*', '', 'i' );
373 IF temp ilike '%DR.%' THEN
375 temp := REGEXP_REPLACE( temp, E'DR\.\\s*', '', 'i' );
377 IF temp ilike '%JR%' THEN
379 temp := REGEXP_REPLACE( temp, E'JR\.?\\s*', '', 'i' );
381 IF temp ilike '%JR,%' THEN
383 temp := REGEXP_REPLACE( temp, E'JR,\\s*', ',', 'i' );
385 IF temp ilike '%SR%' THEN
387 temp := REGEXP_REPLACE( temp, E'SR\.?\\s*', '', 'i' );
389 IF temp ilike '%SR,%' THEN
391 temp := REGEXP_REPLACE( temp, E'SR,\\s*', ',', 'i' );
393 IF temp ~ E'\\sII$' THEN
395 temp := REGEXP_REPLACE( temp, E'II$', '', 'i' );
397 IF temp ~ E'\\sIII$' THEN
399 temp := REGEXP_REPLACE( temp, E'III$', '', 'i' );
401 IF temp ~ E'\\sIV$' THEN
403 temp := REGEXP_REPLACE( temp, E'IV$', '', 'i' );
406 family_name := BTRIM( REGEXP_REPLACE(temp,E'^([^,]*)\\s*,.*$',E'\\1') );
407 first_given_name := BTRIM( CASE WHEN temp ~ ',' THEN REGEXP_REPLACE(temp,E'^[^,]*\\s*,\\s*([^,\\s]*)\\s*.*$',E'\\1') ELSE 'N/A' END );
408 second_given_name := BTRIM( CASE WHEN temp ~ ',' THEN REGEXP_REPLACE(temp,E'^[^,]*\\s*,\\s*[^,\\s]*\\s*(.*)$',E'\\1') ELSE '' END );
410 RETURN ARRAY[ family_name, prefix, first_given_name, second_given_name, suffix ];
412 $$ LANGUAGE PLPGSQL STRICT IMMUTABLE;
414 CREATE OR REPLACE FUNCTION migration_tools.name_parse_out_last_first_middle_and_random_affix (TEXT) RETURNS TEXT[] AS $$
416 full_name TEXT := $1;
418 family_name TEXT := '';
419 first_given_name TEXT := '';
420 second_given_name TEXT := '';
425 -- Use values, not structure, for prefix/suffix, unless we come up with a better idea
426 IF temp ilike '%MR.%' THEN
428 temp := REGEXP_REPLACE( temp, E'MR\.\\s*', '', 'i' );
430 IF temp ilike '%MRS.%' THEN
432 temp := REGEXP_REPLACE( temp, E'MRS\.\\s*', '', 'i' );
434 IF temp ilike '%MS.%' THEN
436 temp := REGEXP_REPLACE( temp, E'MS\.\\s*', '', 'i' );
438 IF temp ilike '%DR.%' THEN
440 temp := REGEXP_REPLACE( temp, E'DR\.\\s*', '', 'i' );
442 IF temp ilike '%JR.%' THEN
444 temp := REGEXP_REPLACE( temp, E'JR\.\\s*', '', 'i' );
446 IF temp ilike '%JR,%' THEN
448 temp := REGEXP_REPLACE( temp, E'JR,\\s*', ',', 'i' );
450 IF temp ilike '%SR.%' THEN
452 temp := REGEXP_REPLACE( temp, E'SR\.\\s*', '', 'i' );
454 IF temp ilike '%SR,%' THEN
456 temp := REGEXP_REPLACE( temp, E'SR,\\s*', ',', 'i' );
458 IF temp like '%III%' THEN
460 temp := REGEXP_REPLACE( temp, E'III', '' );
462 IF temp like '%II%' THEN
464 temp := REGEXP_REPLACE( temp, E'II', '' );
466 IF temp like '%IV%' THEN
468 temp := REGEXP_REPLACE( temp, E'IV', '' );
471 temp := REGEXP_REPLACE( temp, '\(\)', '');
472 family_name := BTRIM( REGEXP_REPLACE(temp,E'^(\\S+).*$',E'\\1') );
473 family_name := REGEXP_REPLACE( family_name, ',', '' );
474 first_given_name := CASE WHEN temp ~ E'^\\S+$' THEN 'N/A' ELSE BTRIM( REGEXP_REPLACE(temp,E'^\\S+\\s+(\\S+).*$',E'\\1') ) END;
475 first_given_name := REGEXP_REPLACE( first_given_name, ',', '' );
476 second_given_name := CASE WHEN temp ~ E'^\\S+$' THEN '' ELSE BTRIM( REGEXP_REPLACE(temp,E'^\\S+\\s+\\S+\\s*(.*)$',E'\\1') ) END;
477 second_given_name := REGEXP_REPLACE( second_given_name, ',', '' );
479 RETURN ARRAY[ family_name, prefix, first_given_name, second_given_name, suffix ];
481 $$ LANGUAGE PLPGSQL STRICT IMMUTABLE;
483 CREATE OR REPLACE FUNCTION migration_tools.name_parse_out_fuller_last_first_middle_and_random_affix (TEXT) RETURNS TEXT[] AS $$
485 full_name TEXT := $1;
487 family_name TEXT := '';
488 first_given_name TEXT := '';
489 second_given_name TEXT := '';
493 temp := BTRIM(full_name);
494 -- Use values, not structure, for prefix/suffix, unless we come up with a better idea
495 --IF temp ~ '^\S{2,}\.' THEN
496 -- prefix := REGEXP_REPLACE(temp, '^(\S{2,}\.).*$','\1');
497 -- temp := BTRIM(REGEXP_REPLACE(temp, '^\S{2,}\.(.*)$','\1'));
499 --IF temp ~ '\S{2,}\.$' THEN
500 -- suffix := REGEXP_REPLACE(temp, '^.*(\S{2,}\.)$','\1');
501 -- temp := REGEXP_REPLACE(temp, '^(.*)\S{2,}\.$','\1');
503 IF temp ilike '%MR.%' THEN
505 temp := BTRIM(REGEXP_REPLACE( temp, E'MR\.\\s*', '', 'i' ));
507 IF temp ilike '%MRS.%' THEN
509 temp := BTRIM(REGEXP_REPLACE( temp, E'MRS\.\\s*', '', 'i' ));
511 IF temp ilike '%MS.%' THEN
513 temp := BTRIM(REGEXP_REPLACE( temp, E'MS\.\\s*', '', 'i' ));
515 IF temp ilike '%DR.%' THEN
517 temp := BTRIM(REGEXP_REPLACE( temp, E'DR\.\\s*', '', 'i' ));
519 IF temp ilike '%JR.%' THEN
521 temp := BTRIM(REGEXP_REPLACE( temp, E'JR\.\\s*', '', 'i' ));
523 IF temp ilike '%JR,%' THEN
525 temp := BTRIM(REGEXP_REPLACE( temp, E'JR,\\s*', ',', 'i' ));
527 IF temp ilike '%SR.%' THEN
529 temp := BTRIM(REGEXP_REPLACE( temp, E'SR\.\\s*', '', 'i' ));
531 IF temp ilike '%SR,%' THEN
533 temp := BTRIM(REGEXP_REPLACE( temp, E'SR,\\s*', ',', 'i' ));
535 IF temp like '%III%' THEN
537 temp := BTRIM(REGEXP_REPLACE( temp, E'III', '' ));
539 IF temp like '%II%' THEN
541 temp := BTRIM(REGEXP_REPLACE( temp, E'II', '' ));
545 family_name = BTRIM(REGEXP_REPLACE(temp,'^(.*?,).*$','\1'));
546 temp := BTRIM(REPLACE( temp, family_name, '' ));
547 family_name := REPLACE( family_name, ',', '' );
549 first_given_name := BTRIM( REGEXP_REPLACE(temp,'^(.+)\s(.+)$','\1') );
550 second_given_name := BTRIM( REGEXP_REPLACE(temp,'^(.+)\s(.+)$','\2') );
552 first_given_name := temp;
553 second_given_name := '';
556 IF temp ~ '^\S+\s+\S+\s+\S+$' THEN
557 first_given_name := BTRIM( REGEXP_REPLACE(temp,'^(\S+)\s*(\S+)\s*(\S+)$','\1') );
558 second_given_name := BTRIM( REGEXP_REPLACE(temp,'^(\S+)\s*(\S+)\s*(\S+)$','\2') );
559 family_name := BTRIM( REGEXP_REPLACE(temp,'^(\S+)\s*(\S+)\s*(\S+)$','\3') );
561 first_given_name := BTRIM( REGEXP_REPLACE(temp,'^(\S+)\s*(\S+)$','\1') );
562 second_given_name := temp;
563 family_name := BTRIM( REGEXP_REPLACE(temp,'^(\S+)\s*(\S+)$','\2') );
567 RETURN ARRAY[ family_name, prefix, first_given_name, second_given_name, suffix ];
569 $$ LANGUAGE PLPGSQL STRICT IMMUTABLE;
571 CREATE OR REPLACE FUNCTION migration_tools.address_parse_out_citystatezip (TEXT) RETURNS TEXT[] AS $$
573 city_state_zip TEXT := $1;
578 zip := CASE WHEN city_state_zip ~ E'\\d\\d\\d\\d\\d' THEN REGEXP_REPLACE( city_state_zip, E'^.*(\\d\\d\\d\\d\\d-?\\d*).*$', E'\\1' ) ELSE '' END;
579 city_state_zip := REGEXP_REPLACE( city_state_zip, E'^(.*)\\d\\d\\d\\d\\d-?\\d*(.*)$', E'\\1\\2');
580 IF city_state_zip ~ ',' THEN
581 state := REGEXP_REPLACE( city_state_zip, E'^(.*),(.*)$', E'\\2');
582 city := REGEXP_REPLACE( city_state_zip, E'^(.*),(.*)$', E'\\1');
584 IF city_state_zip ~ E'\\s+[A-Z][A-Z]\\s*$' THEN
585 state := REGEXP_REPLACE( city_state_zip, E'^.*,?\\s+([A-Z][A-Z])\\s*$', E'\\1' );
586 city := REGEXP_REPLACE( city_state_zip, E'^(.*?),?\\s+[A-Z][A-Z](\\s*)$', E'\\1\\2' );
588 IF city_state_zip ~ E'^\\S+$' THEN
589 city := city_state_zip;
592 state := REGEXP_REPLACE( city_state_zip, E'^(.*?),?\\s*(\\S+)\\s*$', E'\\2');
593 city := REGEXP_REPLACE( city_state_zip, E'^(.*?),?\\s*(\\S+)\\s*$', E'\\1');
597 RETURN ARRAY[ TRIM(BOTH ' ' FROM city), TRIM(BOTH ' ' FROM state), TRIM(BOTH ' ' FROM zip) ];
599 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
601 -- try to parse data like this: 100 MAIN ST$COVINGTON, GA 30016
602 CREATE OR REPLACE FUNCTION migration_tools.parse_out_address (TEXT) RETURNS TEXT[] AS $$
604 fullstring TEXT := $1;
614 WHEN fullstring ~ E'\\d\\d\\d\\d\\d'
615 THEN REGEXP_REPLACE( fullstring, E'^.*(\\d\\d\\d\\d\\d-?\\d*).*$', E'\\1' )
618 fullstring := REGEXP_REPLACE( fullstring, E'^(.*)\\d\\d\\d\\d\\d-?\\d*(.*)$', E'\\1\\2');
620 IF fullstring ~ ',' THEN
621 state := REGEXP_REPLACE( fullstring, E'^(.*),(.*)$', E'\\2');
622 scratch1 := REGEXP_REPLACE( fullstring, E'^(.*),(.*)$', E'\\1');
624 IF fullstring ~ E'\\s+[A-Z][A-Z]\\s*$' THEN
625 state := REGEXP_REPLACE( fullstring, E'^.*,?\\s+([A-Z][A-Z])\\s*$', E'\\1' );
626 scratch1 := REGEXP_REPLACE( fullstring, E'^(.*?),?\\s+[A-Z][A-Z](\\s*)$', E'\\1\\2' );
628 IF fullstring ~ E'^\\S+$' THEN
629 scratch1 := fullstring;
632 state := REGEXP_REPLACE( fullstring, E'^(.*?),?\\s*(\\S+)\\s*$', E'\\2');
633 scratch1 := REGEXP_REPLACE( fullstring, E'^(.*?),?\\s*(\\S+)\\s*$', E'\\1');
638 IF scratch1 ~ '[\$]' THEN
639 scratch2 := REGEXP_REPLACE( scratch1, E'^(.+)[\$](.+?)$', E'\\1');
640 city := REGEXP_REPLACE( scratch1, E'^(.+)[\$](.+?)$', E'\\2');
642 IF scratch1 ~ '\s' THEN
643 scratch2 := REGEXP_REPLACE( scratch1, E'^(.+)\\s+(.+?)$', E'\\1');
644 city := REGEXP_REPLACE( scratch1, E'^(.+)\\s+(.+?)$', E'\\2');
651 IF scratch2 ~ '^\d' THEN
652 address1 := scratch2;
655 address1 := REGEXP_REPLACE( scratch2, E'^(.+?)(\\d.+)$', E'\\1');
656 address2 := REGEXP_REPLACE( scratch2, E'^(.+?)(\\d.+)$', E'\\2');
660 TRIM(BOTH ' ' FROM address1)
661 ,TRIM(BOTH ' ' FROM address2)
662 ,TRIM(BOTH ' ' FROM city)
663 ,TRIM(BOTH ' ' FROM state)
664 ,TRIM(BOTH ' ' FROM zip)
667 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
669 CREATE OR REPLACE FUNCTION migration_tools.parse_out_address2 (TEXT) RETURNS TEXT AS $$
672 use Geo::StreetAddress::US;
674 my $a = Geo::StreetAddress::US->parse_location($address);
677 "$a->{number} $a->{prefix} $a->{street} $a->{type} $a->{suffix}"
678 ,"$a->{sec_unit_type} $a->{sec_unit_num}"
683 $$ LANGUAGE PLPERLU STABLE;
685 DROP TABLE IF EXISTS migration_tools.usps_suffixes;
686 CREATE UNLOGGED TABLE migration_tools.usps_suffixes ( suffix_from TEXT, suffix_to TEXT );
687 INSERT INTO migration_tools.usps_suffixes VALUES
720 ('BOULEVARD','BLVD'),
824 ('EXPRESSWAY','EXPY'),
829 ('EXTENSIONS','EXTS'),
940 ('JUNCTIONS','JCTS'),
1000 ('MOTORWAY','MTWY'),
1002 ('MOUNTAINS','MTNS'),
1016 ('OVERPASS','OPAS'),
1021 ('PARKWAYS','PKWY'),
1127 ('STRAVENUE','STRA'),
1147 ('THROUGHWAY','TRWY'),
1154 ('TRAFFICWAY','TRFY'),
1173 ('TURNPIKE','TPKE'),
1175 ('UNDERPASS','UPAS'),
1189 ('VILLAGES','VLGS'),
1220 -- this function should get a smaller range of inputs and benefit more from STABLE, hopefully speeding things up
1221 CREATE OR REPLACE FUNCTION migration_tools._normalize_address_suffix (TEXT) RETURNS TEXT AS $$
1226 --RAISE INFO 'suffix = %', suffix;
1227 FOR _r IN (SELECT * FROM migration_tools.usps_suffixes) LOOP
1228 suffix := REGEXP_REPLACE( suffix, _r.suffix_from, _r.suffix_to, 'i');
1232 $$ LANGUAGE PLPGSQL STRICT STABLE;
1234 CREATE OR REPLACE FUNCTION migration_tools.normalize_address_suffix (TEXT) RETURNS TEXT AS $$
1237 WHEN $1 ~ '\s\S+$' THEN REGEXP_REPLACE( $1, '^(.*\s)(\S+)$', '\1' ) || migration_tools._normalize_address_suffix( REGEXP_REPLACE( $1, '^(.*\s)(\S+)$', '\2' ) )
1241 $$ LANGUAGE PLPGSQL STRICT STABLE;
1243 CREATE OR REPLACE FUNCTION migration_tools.rebarcode (o TEXT, t BIGINT) RETURNS TEXT AS $$
1247 IF o ~ E'^\\d+$' AND o !~ E'^0' AND length(o) < 19 THEN -- for reference, the max value for a bigint is 9223372036854775807. May also want to consider the case where folks want to add prefixes to non-numeric barcodes
1248 IF o::BIGINT < t THEN
1255 $$ LANGUAGE PLPGSQL STRICT IMMUTABLE;
1257 CREATE OR REPLACE FUNCTION migration_tools.base_profile_map (TEXT) RETURNS TEXT AS $$
1259 migration_schema ALIAS FOR $1;
1263 EXECUTE 'SELECT ''' || migration_schema || '.'' || value FROM ' || migration_schema || '.config WHERE key = ''base_profile_map'';'
1268 $$ LANGUAGE PLPGSQL STRICT STABLE;
1270 CREATE OR REPLACE FUNCTION migration_tools.base_item_dynamic_field_map (TEXT) RETURNS TEXT AS $$
1272 migration_schema ALIAS FOR $1;
1276 EXECUTE 'SELECT ''' || migration_schema || '.'' || value FROM ' || migration_schema || '.config WHERE key = ''base_item_dynamic_field_map'';'
1281 $$ LANGUAGE PLPGSQL STRICT STABLE;
1283 CREATE OR REPLACE FUNCTION migration_tools.base_copy_location_map (TEXT) RETURNS TEXT AS $$
1285 migration_schema ALIAS FOR $1;
1289 EXECUTE 'SELECT ''' || migration_schema || '.'' || value FROM ' || migration_schema || '.config WHERE key = ''base_copy_location_map'';'
1294 $$ LANGUAGE PLPGSQL STRICT STABLE;
1296 CREATE OR REPLACE FUNCTION migration_tools.base_circ_field_map (TEXT) RETURNS TEXT AS $$
1298 migration_schema ALIAS FOR $1;
1302 EXECUTE 'SELECT ''' || migration_schema || '.'' || value FROM ' || migration_schema || '.config WHERE key = ''base_circ_field_map'';'
1307 $$ LANGUAGE PLPGSQL STRICT STABLE;
1309 CREATE OR REPLACE FUNCTION migration_tools.map_base_patron_profile (TEXT,TEXT,INTEGER) RETURNS VOID AS $$
1311 migration_schema ALIAS FOR $1;
1313 patron_table ALIAS FOR $2;
1314 default_patron_profile ALIAS FOR $3;
1317 sql_where1 TEXT := '';
1318 sql_where2 TEXT := '';
1319 sql_where3 TEXT := '';
1322 SELECT migration_tools.base_profile_map(migration_schema) INTO STRICT profile_map;
1324 EXECUTE 'SELECT * FROM ' || profile_map || E' ORDER BY id;'
1326 sql_update := 'UPDATE ' || patron_table || ' AS u SET profile = perm_grp_id FROM ' || profile_map || ' AS m WHERE ';
1327 sql_where1 := NULLIF(output.legacy_field1,'') || ' = ' || quote_literal( output.legacy_value1 ) || ' AND legacy_field1 = ' || quote_literal(output.legacy_field1) || ' AND legacy_value1 = ' || quote_literal(output.legacy_value1);
1328 sql_where2 := NULLIF(output.legacy_field2,'') || ' = ' || quote_literal( output.legacy_value2 ) || ' AND legacy_field2 = ' || quote_literal(output.legacy_field2) || ' AND legacy_value2 = ' || quote_literal(output.legacy_value2);
1329 sql_where3 := NULLIF(output.legacy_field3,'') || ' = ' || quote_literal( output.legacy_value3 ) || ' AND legacy_field3 = ' || quote_literal(output.legacy_field3) || ' AND legacy_value3 = ' || quote_literal(output.legacy_value3);
1330 sql := sql_update || COALESCE(sql_where1,'') || CASE WHEN sql_where1 <> '' AND sql_where2<> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where2,'') || CASE WHEN sql_where2 <> '' AND sql_where3 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where3,'') || ';';
1331 --RAISE INFO 'sql = %', sql;
1332 PERFORM migration_tools.exec( $1, sql );
1334 PERFORM migration_tools.exec( $1, 'UPDATE ' || patron_table || ' AS u SET profile = ' || quote_literal(default_patron_profile) || ' WHERE profile IS NULL;' );
1336 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_base_patron_mapping_profile'', now() );' );
1338 WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_base_patron_mapping_profile'';' );
1341 $$ LANGUAGE PLPGSQL STRICT STABLE;
1343 CREATE OR REPLACE FUNCTION migration_tools.map_base_item_table_dynamic (TEXT,TEXT) RETURNS VOID AS $$
1345 migration_schema ALIAS FOR $1;
1347 item_table ALIAS FOR $2;
1350 sql_where1 TEXT := '';
1351 sql_where2 TEXT := '';
1352 sql_where3 TEXT := '';
1355 SELECT migration_tools.base_item_dynamic_field_map(migration_schema) INTO STRICT field_map;
1357 EXECUTE 'SELECT * FROM ' || field_map || E' ORDER BY id;'
1359 sql_update := 'UPDATE ' || item_table || ' AS i SET ' || output.evergreen_field || E' = ' || quote_literal(output.evergreen_value) || '::' || output.evergreen_datatype || E' FROM ' || field_map || ' AS m WHERE ';
1360 sql_where1 := NULLIF(output.legacy_field1,'') || ' = ' || quote_literal( output.legacy_value1 ) || ' AND legacy_field1 = ' || quote_literal(output.legacy_field1) || ' AND legacy_value1 = ' || quote_literal(output.legacy_value1);
1361 sql_where2 := NULLIF(output.legacy_field2,'') || ' = ' || quote_literal( output.legacy_value2 ) || ' AND legacy_field2 = ' || quote_literal(output.legacy_field2) || ' AND legacy_value2 = ' || quote_literal(output.legacy_value2);
1362 sql_where3 := NULLIF(output.legacy_field3,'') || ' = ' || quote_literal( output.legacy_value3 ) || ' AND legacy_field3 = ' || quote_literal(output.legacy_field3) || ' AND legacy_value3 = ' || quote_literal(output.legacy_value3);
1363 sql := sql_update || COALESCE(sql_where1,'') || CASE WHEN sql_where1 <> '' AND sql_where2<> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where2,'') || CASE WHEN sql_where2 <> '' AND sql_where3 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where3,'') || ';';
1364 --RAISE INFO 'sql = %', sql;
1365 PERFORM migration_tools.exec( $1, sql );
1368 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_base_item_mapping_dynamic'', now() );' );
1370 WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_base_item_mapping_dynamic'';' );
1373 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
1375 CREATE OR REPLACE FUNCTION migration_tools.map_base_item_table_locations (TEXT,TEXT) RETURNS VOID AS $$
1377 migration_schema ALIAS FOR $1;
1378 base_copy_location_map TEXT;
1379 item_table ALIAS FOR $2;
1382 sql_where1 TEXT := '';
1383 sql_where2 TEXT := '';
1384 sql_where3 TEXT := '';
1387 SELECT migration_tools.base_copy_location_map(migration_schema) INTO STRICT base_copy_location_map;
1389 EXECUTE 'SELECT * FROM ' || base_copy_location_map || E' ORDER BY id;'
1391 sql_update := 'UPDATE ' || item_table || ' AS i SET location = m.location FROM ' || base_copy_location_map || ' AS m WHERE ';
1392 sql_where1 := NULLIF(output.legacy_field1,'') || ' = ' || quote_literal( output.legacy_value1 ) || ' AND legacy_field1 = ' || quote_literal(output.legacy_field1) || ' AND legacy_value1 = ' || quote_literal(output.legacy_value1);
1393 sql_where2 := NULLIF(output.legacy_field2,'') || ' = ' || quote_literal( output.legacy_value2 ) || ' AND legacy_field2 = ' || quote_literal(output.legacy_field2) || ' AND legacy_value2 = ' || quote_literal(output.legacy_value2);
1394 sql_where3 := NULLIF(output.legacy_field3,'') || ' = ' || quote_literal( output.legacy_value3 ) || ' AND legacy_field3 = ' || quote_literal(output.legacy_field3) || ' AND legacy_value3 = ' || quote_literal(output.legacy_value3);
1395 sql := sql_update || COALESCE(sql_where1,'') || CASE WHEN sql_where1 <> '' AND sql_where2<> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where2,'') || CASE WHEN sql_where2 <> '' AND sql_where3 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where3,'') || ';';
1396 --RAISE INFO 'sql = %', sql;
1397 PERFORM migration_tools.exec( $1, sql );
1400 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_base_item_mapping_locations'', now() );' );
1402 WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_base_item_mapping_locations'';' );
1405 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
1407 -- circulate loan period max renewals max out fine amount fine interval max fine item field 1 item value 1 item field 2 item value 2 patron field 1 patron value 1 patron field 2 patron value 2
1408 CREATE OR REPLACE FUNCTION migration_tools.map_base_circ_table_dynamic (TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
1410 migration_schema ALIAS FOR $1;
1412 circ_table ALIAS FOR $2;
1413 item_table ALIAS FOR $3;
1414 patron_table ALIAS FOR $4;
1417 sql_where1 TEXT := '';
1418 sql_where2 TEXT := '';
1419 sql_where3 TEXT := '';
1420 sql_where4 TEXT := '';
1423 SELECT migration_tools.base_circ_field_map(migration_schema) INTO STRICT field_map;
1425 EXECUTE 'SELECT * FROM ' || field_map || E' ORDER BY id;'
1427 sql_update := 'UPDATE ' || circ_table || ' AS c SET duration = ' || quote_literal(output.loan_period) || '::INTERVAL, renewal_remaining = ' || quote_literal(output.max_renewals) || '::INTEGER, recuring_fine = ' || quote_literal(output.fine_amount) || '::NUMERIC(6,2), fine_interval = ' || quote_literal(output.fine_interval) || '::INTERVAL, max_fine = ' || quote_literal(output.max_fine) || '::NUMERIC(6,2) FROM ' || field_map || ' AS m, ' || item_table || ' AS i, ' || patron_table || ' AS u WHERE c.usr = u.id AND c.target_copy = i.id AND ';
1428 sql_where1 := NULLIF(output.item_field1,'') || ' = ' || quote_literal( output.item_value1 ) || ' AND item_field1 = ' || quote_literal(output.item_field1) || ' AND item_value1 = ' || quote_literal(output.item_value1);
1429 sql_where2 := NULLIF(output.item_field2,'') || ' = ' || quote_literal( output.item_value2 ) || ' AND item_field2 = ' || quote_literal(output.item_field2) || ' AND item_value2 = ' || quote_literal(output.item_value2);
1430 sql_where3 := NULLIF(output.patron_field1,'') || ' = ' || quote_literal( output.patron_value1 ) || ' AND patron_field1 = ' || quote_literal(output.patron_field1) || ' AND patron_value1 = ' || quote_literal(output.patron_value1);
1431 sql_where4 := NULLIF(output.patron_field2,'') || ' = ' || quote_literal( output.patron_value2 ) || ' AND patron_field2 = ' || quote_literal(output.patron_field2) || ' AND patron_value2 = ' || quote_literal(output.patron_value2);
1432 sql := sql_update || COALESCE(sql_where1,'') || CASE WHEN sql_where1 <> '' AND sql_where2<> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where2,'') || CASE WHEN sql_where2 <> '' AND sql_where3 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where3,'') || CASE WHEN sql_where3 <> '' AND sql_where4 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where4,'') || ';';
1433 --RAISE INFO 'sql = %', sql;
1434 PERFORM migration_tools.exec( $1, sql );
1437 PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_base_circ_field_mapping'', now() );' );
1439 WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_base_circ_field_mapping'';' );
1442 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
1445 -- $barcode source barcode
1446 -- $prefix prefix to add to barcode, NULL = add no prefix
1447 -- $maxlen maximum length of barcode; default to 14 if left NULL
1448 -- $pad padding string to apply to left of source barcode before adding
1449 -- prefix and suffix; set to NULL or '' if no padding is desired
1450 -- $suffix suffix to add to barcode, NULL = add no suffix
1452 -- Returns a new string consisting of prefix concatenated with padded barcode and suffix.
1453 -- If new barcode would be longer than $maxlen, the original barcode is returned instead.
1455 CREATE OR REPLACE FUNCTION migration_tools.expand_barcode (TEXT, TEXT, INTEGER, TEXT, TEXT) RETURNS TEXT AS $$
1456 my ($barcode, $prefix, $maxlen, $pad, $suffix) = @_;
1459 return unless defined $barcode;
1461 $prefix = '' unless defined $prefix;
1463 $pad = '0' unless defined $pad;
1464 $suffix = '' unless defined $suffix;
1466 # bail out if adding prefix and suffix would bring new barcode over max length
1467 return $barcode if (length($prefix) + length($barcode) + length($suffix)) > $maxlen;
1469 my $new_barcode = $barcode;
1471 my $pad_length = $maxlen - length($prefix) - length($suffix);
1472 if (length($barcode) < $pad_length) {
1473 # assuming we always want padding on the left
1474 # also assuming that it is possible to have the pad string be longer than 1 character
1475 $new_barcode = substr($pad x ($pad_length - length($barcode)), 0, $pad_length - length($barcode)) . $new_barcode;
1479 # bail out if adding prefix and suffix would bring new barcode over max length
1480 return $barcode if (length($prefix) + length($new_barcode) + length($suffix)) > $maxlen;
1482 return "$prefix$new_barcode$suffix";
1483 $$ LANGUAGE PLPERLU STABLE;
1485 -- remove previous version of this function
1486 DROP FUNCTION IF EXISTS migration_tools.attempt_cast(TEXT, TEXT, TEXT);
1488 CREATE OR REPLACE FUNCTION migration_tools.attempt_cast (TEXT, TEXT) RETURNS TEXT AS $$
1490 attempt_value ALIAS FOR $1;
1491 datatype ALIAS FOR $2;
1493 EXECUTE 'SELECT ' || quote_literal(attempt_value) || '::' || datatype || ' AS a;';
1494 RETURN attempt_value;
1496 WHEN OTHERS THEN RETURN NULL;
1498 $$ LANGUAGE PLPGSQL STRICT STABLE;
1500 CREATE OR REPLACE FUNCTION migration_tools.attempt_date (TEXT,TEXT) RETURNS DATE AS $$
1502 attempt_value ALIAS FOR $1;
1503 fail_value ALIAS FOR $2;
1507 EXECUTE 'SELECT ' || quote_literal(REGEXP_REPLACE(attempt_value,'^(\d\d)(\d\d)(\d\d)$','\1-\2-\3')) || '::date AS a;'
1514 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::date AS a;'
1519 $$ LANGUAGE PLPGSQL STRICT STABLE;
1521 CREATE OR REPLACE FUNCTION migration_tools.attempt_timestamptz (TEXT,TEXT) RETURNS TIMESTAMPTZ AS $$
1523 attempt_value ALIAS FOR $1;
1524 fail_value ALIAS FOR $2;
1528 EXECUTE 'SELECT ' || quote_literal(attempt_value) || '::TIMESTAMPTZ AS a;'
1535 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::TIMESTAMPTZ AS a;'
1540 $$ LANGUAGE PLPGSQL STRICT STABLE;
1542 CREATE OR REPLACE FUNCTION migration_tools.attempt_hz_date (TEXT,TEXT) RETURNS DATE AS $$
1544 attempt_value ALIAS FOR $1;
1545 fail_value ALIAS FOR $2;
1549 EXECUTE E'SELECT (\'1970-01-01\'::date + \'' || attempt_value || E' days\'::interval)::date AS a;'
1556 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::date AS a;'
1561 $$ LANGUAGE PLPGSQL STRICT STABLE;
1563 CREATE OR REPLACE FUNCTION migration_tools.attempt_sierra_timestamp (TEXT,TEXT) RETURNS TIMESTAMP AS $$
1565 attempt_value ALIAS FOR $1;
1566 fail_value ALIAS FOR $2;
1569 output := REGEXP_REPLACE(attempt_value,E'^(..)(..)(..)(..)(..)$',E'20\\1-\\2-\\3 \\4:\\5')::TIMESTAMP;
1574 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::TIMESTAMP AS a;'
1579 $$ LANGUAGE PLPGSQL STRICT STABLE;
1581 CREATE OR REPLACE FUNCTION migration_tools.attempt_money (TEXT,TEXT) RETURNS NUMERIC(8,2) AS $$
1583 attempt_value ALIAS FOR $1;
1584 fail_value ALIAS FOR $2;
1585 output NUMERIC(8,2);
1588 EXECUTE 'SELECT ' || quote_literal(REPLACE(REPLACE(attempt_value,'$',''),',','')) || '::NUMERIC(8,2) AS a;'
1595 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::NUMERIC(8,2) AS a;'
1600 $$ LANGUAGE PLPGSQL STRICT STABLE;
1602 CREATE OR REPLACE FUNCTION migration_tools.attempt_money6 (TEXT,TEXT) RETURNS NUMERIC(6,2) AS $$
1604 attempt_value ALIAS FOR $1;
1605 fail_value ALIAS FOR $2;
1606 output NUMERIC(6,2);
1609 EXECUTE 'SELECT ' || quote_literal(REPLACE(REPLACE(attempt_value,'$',''),',','')) || '::NUMERIC(6,2) AS a;'
1616 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::NUMERIC(6,2) AS a;'
1621 $$ LANGUAGE PLPGSQL STRICT STABLE;
1623 CREATE OR REPLACE FUNCTION migration_tools.attempt_money_from_pennies (TEXT,TEXT) RETURNS NUMERIC(8,2) AS $$
1625 attempt_value ALIAS FOR $1;
1626 fail_value ALIAS FOR $2;
1627 output NUMERIC(8,2);
1629 IF length(regexp_replace(attempt_value,'^0+','')) > 10 THEN
1630 RAISE EXCEPTION 'too many digits';
1633 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;'
1640 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::NUMERIC(8,2) AS a;'
1645 $$ LANGUAGE PLPGSQL STRICT STABLE;
1647 CREATE OR REPLACE FUNCTION migration_tools.attempt_money_from_pennies6 (TEXT,TEXT) RETURNS NUMERIC(6,2) AS $$
1649 attempt_value ALIAS FOR $1;
1650 fail_value ALIAS FOR $2;
1651 output NUMERIC(6,2);
1653 IF length(regexp_replace(attempt_value,'^0+','')) > 8 THEN
1654 RAISE EXCEPTION 'too many digits';
1657 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;'
1664 EXECUTE 'SELECT ' || quote_literal(fail_value) || '::NUMERIC(6,2) AS a;'
1669 $$ LANGUAGE PLPGSQL STRICT STABLE;
1671 -- add_codabar_checkdigit
1672 -- $barcode source barcode
1674 -- If the source string is 13 or 14 characters long and contains only digits, adds or replaces the 14
1675 -- character with a checkdigit computed according to the usual algorithm for library barcodes
1676 -- using the Codabar symbology - see <http://www.makebarcode.com/specs/codabar.html>. If the
1677 -- input string does not meet those requirements, it is returned unchanged.
1679 CREATE OR REPLACE FUNCTION migration_tools.add_codabar_checkdigit (TEXT) RETURNS TEXT AS $$
1680 my $barcode = shift;
1682 return $barcode if $barcode !~ /^\d{13,14}$/;
1683 $barcode = substr($barcode, 0, 13); # ignore 14th digit
1684 my @digits = split //, $barcode;
1686 $total += $digits[$_] foreach (1, 3, 5, 7, 9, 11);
1687 $total += (2 * $digits[$_] >= 10) ? (2 * $digits[$_] - 9) : (2 * $digits[$_]) foreach (0, 2, 4, 6, 8, 10, 12);
1688 my $remainder = $total % 10;
1689 my $checkdigit = ($remainder == 0) ? $remainder : 10 - $remainder;
1690 return $barcode . $checkdigit;
1691 $$ LANGUAGE PLPERLU STRICT STABLE;
1693 -- add_code39mod43_checkdigit
1694 -- $barcode source barcode
1696 -- If the source string is 13 or 14 characters long and contains only valid
1697 -- Code 39 mod 43 characters, adds or replaces the 14th
1698 -- character with a checkdigit computed according to the usual algorithm for library barcodes
1699 -- using the Code 39 mod 43 symbology - see <http://en.wikipedia.org/wiki/Code_39#Code_39_mod_43>. If the
1700 -- input string does not meet those requirements, it is returned unchanged.
1702 CREATE OR REPLACE FUNCTION migration_tools.add_code39mod43_checkdigit (TEXT) RETURNS TEXT AS $$
1703 my $barcode = shift;
1705 return $barcode if $barcode !~ /^[0-9A-Z. $\/+%-]{13,14}$/;
1706 $barcode = substr($barcode, 0, 13); # ignore 14th character
1708 my @valid_chars = split //, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%';
1709 my %nums = map { $valid_chars[$_] => $_ } (0..42);
1712 $total += $nums{$_} foreach split(//, $barcode);
1713 my $remainder = $total % 43;
1714 my $checkdigit = $valid_chars[$remainder];
1715 return $barcode . $checkdigit;
1716 $$ LANGUAGE PLPERLU STRICT STABLE;
1718 -- add_mod16_checkdigit
1719 -- $barcode source barcode
1721 -- https://www.activebarcode.com/codes/checkdigit/modulo16.html
1723 CREATE OR REPLACE FUNCTION migration_tools.add_mod16_checkdigit (TEXT) RETURNS TEXT AS $$
1724 my $barcode = shift;
1726 my @digits = split //, $barcode;
1728 foreach $digit (@digits) {
1729 if ($digit =~ /[0-9]/) { $total += $digit;
1730 } elsif ($digit eq '-') { $total += 10;
1731 } elsif ($digit eq '$') { $total += 11;
1732 } elsif ($digit eq ':') { $total += 12;
1733 } elsif ($digit eq '/') { $total += 13;
1734 } elsif ($digit eq '.') { $total += 14;
1735 } elsif ($digit eq '+') { $total += 15;
1736 } elsif ($digit eq 'A') { $total += 16;
1737 } elsif ($digit eq 'B') { $total += 17;
1738 } elsif ($digit eq 'C') { $total += 18;
1739 } elsif ($digit eq 'D') { $total += 19;
1740 } else { die "invalid digit <$digit>";
1743 my $remainder = $total % 16;
1744 my $difference = 16 - $remainder;
1746 if ($difference < 10) { $checkdigit = $difference;
1747 } elsif ($difference == 10) { $checkdigit = '-';
1748 } elsif ($difference == 11) { $checkdigit = '$';
1749 } elsif ($difference == 12) { $checkdigit = ':';
1750 } elsif ($difference == 13) { $checkdigit = '/';
1751 } elsif ($difference == 14) { $checkdigit = '.';
1752 } elsif ($difference == 15) { $checkdigit = '+';
1753 } else { die "error calculating checkdigit";
1756 return $barcode . $checkdigit;
1757 $$ LANGUAGE PLPERLU STRICT STABLE;
1759 CREATE OR REPLACE FUNCTION migration_tools.attempt_phone (TEXT,TEXT) RETURNS TEXT AS $$
1762 areacode TEXT := $2;
1765 n_digits INTEGER := 0;
1768 temp := REGEXP_REPLACE(temp, '^1*[^0-9]*(?=[0-9])', '');
1769 temp := REGEXP_REPLACE(temp, '[^0-9]*([0-9]{3})[^0-9]*([0-9]{3})[^0-9]*([0-9]{4})', E'\\1-\\2-\\3');
1770 n_digits := LENGTH(REGEXP_REPLACE(REGEXP_REPLACE(temp, '(.*)?[a-zA-Z].*', E'\\1') , '[^0-9]', '', 'g'));
1771 IF n_digits = 7 AND areacode <> '' THEN
1772 temp := REGEXP_REPLACE(temp, '[^0-9]*([0-9]{3})[^0-9]*([0-9]{4})', E'\\1-\\2');
1773 output := (areacode || '-' || temp);
1780 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
1782 CREATE OR REPLACE FUNCTION migration_tools.set_leader (TEXT, INT, TEXT) RETURNS TEXT AS $$
1783 my ($marcxml, $pos, $value) = @_;
1786 use MARC::File::XML;
1790 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
1791 my $leader = $marc->leader();
1792 substr($leader, $pos, 1) = $value;
1793 $marc->leader($leader);
1794 $xml = $marc->as_xml_record;
1795 $xml =~ s/^<\?.+?\?>$//mo;
1797 $xml =~ s/>\s+</></sgo;
1800 $$ LANGUAGE PLPERLU STABLE;
1802 CREATE OR REPLACE FUNCTION migration_tools.set_008 (TEXT, INT, TEXT) RETURNS TEXT AS $$
1803 my ($marcxml, $pos, $value) = @_;
1806 use MARC::File::XML;
1810 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
1811 my $f008 = $marc->field('008');
1814 my $field = $f008->data();
1815 substr($field, $pos, 1) = $value;
1816 $f008->update($field);
1817 $xml = $marc->as_xml_record;
1818 $xml =~ s/^<\?.+?\?>$//mo;
1820 $xml =~ s/>\s+</></sgo;
1824 $$ LANGUAGE PLPERLU STABLE;
1827 CREATE OR REPLACE FUNCTION migration_tools.is_staff_profile (INT) RETURNS BOOLEAN AS $$
1829 profile ALIAS FOR $1;
1831 RETURN CASE WHEN 'Staff' IN (select (permission.grp_ancestors(profile)).name) THEN TRUE ELSE FALSE END;
1833 $$ LANGUAGE PLPGSQL STRICT STABLE;
1836 CREATE OR REPLACE FUNCTION migration_tools.is_blank (TEXT) RETURNS BOOLEAN AS $$
1838 RETURN CASE WHEN $1 = '' THEN TRUE ELSE FALSE END;
1840 $$ LANGUAGE PLPGSQL STRICT STABLE;
1843 CREATE OR REPLACE FUNCTION migration_tools.insert_tags (TEXT, TEXT) RETURNS TEXT AS $$
1845 my ($marcxml, $tags) = @_;
1848 use MARC::File::XML;
1853 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
1854 my $to_insert = MARC::Record->new_from_xml("<record>$tags</record>", 'UTF-8');
1856 my @incumbents = ();
1858 foreach my $field ( $marc->fields() ) {
1859 push @incumbents, $field->as_formatted();
1862 foreach $field ( $to_insert->fields() ) {
1863 if (!grep {$_ eq $field->as_formatted()} @incumbents) {
1864 $marc->insert_fields_ordered( ($field) );
1868 $xml = $marc->as_xml_record;
1869 $xml =~ s/^<\?.+?\?>$//mo;
1871 $xml =~ s/>\s+</></sgo;
1876 $$ LANGUAGE PLPERLU STABLE;
1878 CREATE OR REPLACE FUNCTION migration_tools.apply_circ_matrix( tablename TEXT ) RETURNS VOID AS $$
1882 -- First make sure the circ matrix is loaded and the circulations
1883 -- have been staged to the extent possible (but at the very least
1884 -- circ_lib, target_copy, usr, and *_renewal). User profiles and
1885 -- circ modifiers must also be in place.
1887 -- SELECT migration_tools.apply_circ_matrix('m_pioneer.action_circulation');
1895 this_duration_rule INT;
1897 this_max_fine_rule INT;
1898 rcd config.rule_circ_duration%ROWTYPE;
1899 rrf config.rule_recurring_fine%ROWTYPE;
1900 rmf config.rule_max_fine%ROWTYPE;
1907 EXECUTE 'SELECT COUNT(*) FROM ' || tablename || ';' INTO n_circs;
1909 FOR circ IN EXECUTE ('SELECT id FROM ' || tablename) LOOP
1911 -- Fetch the correct rules for this circulation
1918 WHEN phone_renewal OR desk_renewal OR opac_renewal THEN TRUE
1921 FROM ' || tablename || ' WHERE id = ' || circ || ';')
1922 INTO circ_lib, target_copy, usr, is_renewal ;
1924 INTO this_duration_rule,
1928 recurring_fine_rule,
1930 FROM action.item_user_circ_test(
1936 SELECT INTO rcd * FROM config.rule_circ_duration
1937 WHERE id = this_duration_rule;
1938 SELECT INTO rrf * FROM config.rule_recurring_fine
1939 WHERE id = this_fine_rule;
1940 SELECT INTO rmf * FROM config.rule_max_fine
1941 WHERE id = this_max_fine_rule;
1943 -- Apply the rules to this circulation
1944 EXECUTE ('UPDATE ' || tablename || ' c
1946 duration_rule = rcd.name,
1947 recurring_fine_rule = rrf.name,
1948 max_fine_rule = rmf.name,
1949 duration = rcd.normal,
1950 recurring_fine = rrf.normal,
1953 WHEN TRUE THEN (rmf.amount / 100.0) * ac.price
1956 renewal_remaining = rcd.max_renewals
1958 config.rule_circ_duration rcd,
1959 config.rule_recurring_fine rrf,
1960 config.rule_max_fine rmf,
1963 rcd.id = ' || this_duration_rule || ' AND
1964 rrf.id = ' || this_fine_rule || ' AND
1965 rmf.id = ' || this_max_fine_rule || ' AND
1966 ac.id = c.target_copy AND
1967 c.id = ' || circ || ';');
1969 -- Keep track of where we are in the process
1971 IF (n % 100 = 0) THEN
1972 RAISE INFO '%', n || ' of ' || n_circs
1973 || ' (' || (100*n/n_circs) || '%) circs updated.';
1981 $$ LANGUAGE plpgsql;
1983 CREATE OR REPLACE FUNCTION migration_tools.apply_circ_matrix_before_20( tablename TEXT ) RETURNS VOID AS $$
1987 -- First make sure the circ matrix is loaded and the circulations
1988 -- have been staged to the extent possible (but at the very least
1989 -- circ_lib, target_copy, usr, and *_renewal). User profiles and
1990 -- circ modifiers must also be in place.
1992 -- SELECT migration_tools.apply_circ_matrix('m_pioneer.action_circulation');
2000 this_duration_rule INT;
2002 this_max_fine_rule INT;
2003 rcd config.rule_circ_duration%ROWTYPE;
2004 rrf config.rule_recurring_fine%ROWTYPE;
2005 rmf config.rule_max_fine%ROWTYPE;
2012 EXECUTE 'SELECT COUNT(*) FROM ' || tablename || ';' INTO n_circs;
2014 FOR circ IN EXECUTE ('SELECT id FROM ' || tablename) LOOP
2016 -- Fetch the correct rules for this circulation
2023 WHEN phone_renewal OR desk_renewal OR opac_renewal THEN TRUE
2026 FROM ' || tablename || ' WHERE id = ' || circ || ';')
2027 INTO circ_lib, target_copy, usr, is_renewal ;
2029 INTO this_duration_rule,
2035 FROM action.find_circ_matrix_matchpoint(
2041 SELECT INTO rcd * FROM config.rule_circ_duration
2042 WHERE id = this_duration_rule;
2043 SELECT INTO rrf * FROM config.rule_recurring_fine
2044 WHERE id = this_fine_rule;
2045 SELECT INTO rmf * FROM config.rule_max_fine
2046 WHERE id = this_max_fine_rule;
2048 -- Apply the rules to this circulation
2049 EXECUTE ('UPDATE ' || tablename || ' c
2051 duration_rule = rcd.name,
2052 recuring_fine_rule = rrf.name,
2053 max_fine_rule = rmf.name,
2054 duration = rcd.normal,
2055 recuring_fine = rrf.normal,
2058 WHEN TRUE THEN (rmf.amount / 100.0) * ac.price
2061 renewal_remaining = rcd.max_renewals
2063 config.rule_circ_duration rcd,
2064 config.rule_recuring_fine rrf,
2065 config.rule_max_fine rmf,
2068 rcd.id = ' || this_duration_rule || ' AND
2069 rrf.id = ' || this_fine_rule || ' AND
2070 rmf.id = ' || this_max_fine_rule || ' AND
2071 ac.id = c.target_copy AND
2072 c.id = ' || circ || ';');
2074 -- Keep track of where we are in the process
2076 IF (n % 100 = 0) THEN
2077 RAISE INFO '%', n || ' of ' || n_circs
2078 || ' (' || (100*n/n_circs) || '%) circs updated.';
2086 $$ LANGUAGE plpgsql;
2088 CREATE OR REPLACE FUNCTION migration_tools.apply_circ_matrix_after_20( tablename TEXT ) RETURNS VOID AS $$
2092 -- First make sure the circ matrix is loaded and the circulations
2093 -- have been staged to the extent possible (but at the very least
2094 -- circ_lib, target_copy, usr, and *_renewal). User profiles and
2095 -- circ modifiers must also be in place.
2097 -- SELECT migration_tools.apply_circ_matrix('m_pioneer.action_circulation');
2105 this_duration_rule INT;
2107 this_max_fine_rule INT;
2108 rcd config.rule_circ_duration%ROWTYPE;
2109 rrf config.rule_recurring_fine%ROWTYPE;
2110 rmf config.rule_max_fine%ROWTYPE;
2117 EXECUTE 'SELECT COUNT(*) FROM ' || tablename || ';' INTO n_circs;
2119 FOR circ IN EXECUTE ('SELECT id FROM ' || tablename) LOOP
2121 -- Fetch the correct rules for this circulation
2128 WHEN phone_renewal OR desk_renewal OR opac_renewal THEN TRUE
2131 FROM ' || tablename || ' WHERE id = ' || circ || ';')
2132 INTO circ_lib, target_copy, usr, is_renewal ;
2134 INTO this_duration_rule,
2137 (matchpoint).duration_rule,
2138 (matchpoint).recurring_fine_rule,
2139 (matchpoint).max_fine_rule
2140 FROM action.find_circ_matrix_matchpoint(
2146 SELECT INTO rcd * FROM config.rule_circ_duration
2147 WHERE id = this_duration_rule;
2148 SELECT INTO rrf * FROM config.rule_recurring_fine
2149 WHERE id = this_fine_rule;
2150 SELECT INTO rmf * FROM config.rule_max_fine
2151 WHERE id = this_max_fine_rule;
2153 -- Apply the rules to this circulation
2154 EXECUTE ('UPDATE ' || tablename || ' c
2156 duration_rule = rcd.name,
2157 recurring_fine_rule = rrf.name,
2158 max_fine_rule = rmf.name,
2159 duration = rcd.normal,
2160 recurring_fine = rrf.normal,
2163 WHEN TRUE THEN (rmf.amount / 100.0) * ac.price
2166 renewal_remaining = rcd.max_renewals,
2167 grace_period = rrf.grace_period
2169 config.rule_circ_duration rcd,
2170 config.rule_recurring_fine rrf,
2171 config.rule_max_fine rmf,
2174 rcd.id = ' || this_duration_rule || ' AND
2175 rrf.id = ' || this_fine_rule || ' AND
2176 rmf.id = ' || this_max_fine_rule || ' AND
2177 ac.id = c.target_copy AND
2178 c.id = ' || circ || ';');
2180 -- Keep track of where we are in the process
2182 IF (n % 100 = 0) THEN
2183 RAISE INFO '%', n || ' of ' || n_circs
2184 || ' (' || (100*n/n_circs) || '%) circs updated.';
2192 $$ LANGUAGE plpgsql;
2194 -- TODO: make another version of the procedure below that can work with specified copy staging tables
2195 -- The following should track the logic of OpenILS::Application::AppUtils::get_copy_price
2196 CREATE OR REPLACE FUNCTION migration_tools.get_copy_price( item BIGINT ) RETURNS NUMERIC AS $$
2199 charge_lost_on_zero BOOLEAN;
2202 default_price NUMERIC;
2203 working_price NUMERIC;
2207 SELECT INTO context_lib CASE WHEN call_number = -1 THEN circ_lib ELSE owning_lib END
2208 FROM asset.copy ac, asset.call_number acn WHERE ac.call_number = acn.id AND ac.id = item;
2210 SELECT INTO charge_lost_on_zero value
2211 FROM actor.org_unit_ancestor_setting('circ.charge_lost_on_zero',context_lib);
2213 SELECT INTO min_price value
2214 FROM actor.org_unit_ancestor_setting('circ.min_item_price',context_lib);
2216 SELECT INTO max_price value
2217 FROM actor.org_unit_ancestor_setting('circ.max_item_price',context_lib);
2219 SELECT INTO default_price value
2220 FROM actor.org_unit_ancestor_setting('cat.default_item_price',context_lib);
2222 SELECT INTO working_price price FROM asset.copy WHERE id = item;
2224 IF (working_price IS NULL OR (working_price = 0 AND charge_lost_on_zero)) THEN
2225 working_price := default_price;
2228 IF (max_price IS NOT NULL AND working_price > max_price) THEN
2229 working_price := max_price;
2232 IF (min_price IS NOT NULL AND working_price < min_price) THEN
2233 IF (working_price <> 0 OR charge_lost_on_zero IS NULL OR charge_lost_on_zero) THEN
2234 working_price := min_price;
2238 RETURN working_price;
2242 $$ LANGUAGE plpgsql;
2244 CREATE OR REPLACE FUNCTION migration_tools.apply_circ_matrix_to_specific_circ( tablename TEXT, circ BIGINT ) RETURNS VOID AS $$
2248 -- First make sure the circ matrix is loaded and the circulations
2249 -- have been staged to the extent possible (but at the very least
2250 -- circ_lib, target_copy, usr, and *_renewal). User profiles and
2251 -- circ modifiers must also be in place.
2253 -- SELECT migration_tools.apply_circ_matrix_to_specific_circ('m_nwrl.action_circulation', 18391960);
2261 this_duration_rule INT;
2263 this_max_fine_rule INT;
2264 rcd config.rule_circ_duration%ROWTYPE;
2265 rrf config.rule_recurring_fine%ROWTYPE;
2266 rmf config.rule_max_fine%ROWTYPE;
2272 --EXECUTE 'SELECT COUNT(*) FROM ' || tablename || ';' INTO n_circs;
2274 --FOR circ IN EXECUTE ('SELECT id FROM ' || tablename) LOOP
2276 -- Fetch the correct rules for this circulation
2283 WHEN phone_renewal OR desk_renewal OR opac_renewal THEN TRUE
2286 FROM ' || tablename || ' WHERE id = ' || circ || ';')
2287 INTO circ_lib, target_copy, usr, is_renewal ;
2289 INTO this_duration_rule,
2292 (matchpoint).duration_rule,
2293 (matchpoint).recurring_fine_rule,
2294 (matchpoint).max_fine_rule
2295 FROM action.find_circ_matrix_matchpoint(
2301 SELECT INTO rcd * FROM config.rule_circ_duration
2302 WHERE id = this_duration_rule;
2303 SELECT INTO rrf * FROM config.rule_recurring_fine
2304 WHERE id = this_fine_rule;
2305 SELECT INTO rmf * FROM config.rule_max_fine
2306 WHERE id = this_max_fine_rule;
2308 -- Apply the rules to this circulation
2309 EXECUTE ('UPDATE ' || tablename || ' c
2311 duration_rule = rcd.name,
2312 recurring_fine_rule = rrf.name,
2313 max_fine_rule = rmf.name,
2314 duration = rcd.normal,
2315 recurring_fine = rrf.normal,
2318 WHEN TRUE THEN (rmf.amount / 100.0) * migration_tools.get_copy_price(ac.id)
2321 renewal_remaining = rcd.max_renewals,
2322 grace_period = rrf.grace_period
2324 config.rule_circ_duration rcd,
2325 config.rule_recurring_fine rrf,
2326 config.rule_max_fine rmf,
2329 rcd.id = ' || this_duration_rule || ' AND
2330 rrf.id = ' || this_fine_rule || ' AND
2331 rmf.id = ' || this_max_fine_rule || ' AND
2332 ac.id = c.target_copy AND
2333 c.id = ' || circ || ';');
2335 -- Keep track of where we are in the process
2337 IF (n % 100 = 0) THEN
2338 RAISE INFO '%', n || ' of ' || n_circs
2339 || ' (' || (100*n/n_circs) || '%) circs updated.';
2347 $$ LANGUAGE plpgsql;
2352 CREATE OR REPLACE FUNCTION migration_tools.stage_not_applicable_asset_stat_cats( schemaname TEXT ) RETURNS VOID AS $$
2354 -- USAGE: Make sure the stat_cat and stat_cat_entry tables are populated, including exactly one 'Not Applicable' entry per stat cat.
2355 -- Then SELECT migration_tools.stage_not_applicable_asset_stat_cats('m_foo');
2357 -- TODO: Make a variant that will go directly to production tables -- which would be useful for retrofixing the absence of N/A cats.
2358 -- TODO: Add a similar tool for actor stat cats, which behave differently.
2361 c TEXT := schemaname || '.asset_copy_legacy';
2362 sc TEXT := schemaname || '.asset_stat_cat';
2363 sce TEXT := schemaname || '.asset_stat_cat_entry';
2364 scecm TEXT := schemaname || '.asset_stat_cat_entry_copy_map';
2370 FOR stat_cat IN EXECUTE ('SELECT id FROM ' || sc) LOOP
2372 EXECUTE ('SELECT id FROM ' || sce || ' WHERE stat_cat = ' || stat_cat || E' AND value = \'Not Applicable\';') INTO stat_cat_entry;
2374 EXECUTE ('INSERT INTO ' || scecm || ' (owning_copy, stat_cat, stat_cat_entry)
2375 SELECT c.id, ' || stat_cat || ', ' || stat_cat_entry || ' FROM ' || c || ' c WHERE c.id NOT IN
2376 (SELECT owning_copy FROM ' || scecm || ' WHERE stat_cat = ' || stat_cat || ');');
2383 $$ LANGUAGE plpgsql;
2385 CREATE OR REPLACE FUNCTION migration_tools.assign_standing_penalties ( ) RETURNS VOID AS $$
2387 -- USAGE: Once circulation data has been loaded, and group penalty thresholds have been set up, run this.
2388 -- This will assign standing penalties as needed.
2396 FOR org_unit IN EXECUTE ('SELECT DISTINCT org_unit FROM permission.grp_penalty_threshold;') LOOP
2398 FOR usr IN EXECUTE ('SELECT id FROM actor.usr WHERE NOT deleted;') LOOP
2400 EXECUTE('SELECT actor.calculate_system_penalties(' || usr || ', ' || org_unit || ');');
2410 $$ LANGUAGE plpgsql;
2413 CREATE OR REPLACE FUNCTION migration_tools.insert_metarecords_for_pristine_database () RETURNS VOID AS $$
2416 INSERT INTO metabib.metarecord (fingerprint, master_record)
2417 SELECT DISTINCT ON (b.fingerprint) b.fingerprint, b.id
2418 FROM biblio.record_entry b
2420 AND b.id IN (SELECT r.id FROM biblio.record_entry r LEFT JOIN metabib.metarecord_source_map k ON (k.source = r.id) WHERE k.id IS NULL AND r.fingerprint IS NOT NULL)
2421 AND NOT EXISTS ( SELECT 1 FROM metabib.metarecord WHERE fingerprint = b.fingerprint )
2422 ORDER BY b.fingerprint, b.quality DESC;
2423 INSERT INTO metabib.metarecord_source_map (metarecord, source)
2425 FROM biblio.record_entry r
2426 JOIN metabib.metarecord m USING (fingerprint)
2427 WHERE NOT r.deleted;
2430 $$ LANGUAGE plpgsql;
2433 CREATE OR REPLACE FUNCTION migration_tools.insert_metarecords_for_incumbent_database () RETURNS VOID AS $$
2436 INSERT INTO metabib.metarecord (fingerprint, master_record)
2437 SELECT DISTINCT ON (b.fingerprint) b.fingerprint, b.id
2438 FROM biblio.record_entry b
2440 AND b.id IN (SELECT r.id FROM biblio.record_entry r LEFT JOIN metabib.metarecord_source_map k ON (k.source = r.id) WHERE k.id IS NULL AND r.fingerprint IS NOT NULL)
2441 AND NOT EXISTS ( SELECT 1 FROM metabib.metarecord WHERE fingerprint = b.fingerprint )
2442 ORDER BY b.fingerprint, b.quality DESC;
2443 INSERT INTO metabib.metarecord_source_map (metarecord, source)
2445 FROM biblio.record_entry r
2446 JOIN metabib.metarecord m USING (fingerprint)
2448 AND r.id IN (SELECT b.id FROM biblio.record_entry b LEFT JOIN metabib.metarecord_source_map k ON (k.source = b.id) WHERE k.id IS NULL);
2451 $$ LANGUAGE plpgsql;
2454 CREATE OR REPLACE FUNCTION migration_tools.create_cards( schemaname TEXT ) RETURNS VOID AS $$
2456 -- USAGE: Make sure the patrons are staged in schemaname.actor_usr_legacy and have 'usrname' assigned.
2457 -- Then SELECT migration_tools.create_cards('m_foo');
2460 u TEXT := schemaname || '.actor_usr_legacy';
2461 c TEXT := schemaname || '.actor_card';
2465 EXECUTE ('DELETE FROM ' || c || ';');
2466 EXECUTE ('INSERT INTO ' || c || ' (usr, barcode) SELECT id, usrname FROM ' || u || ';');
2467 EXECUTE ('UPDATE ' || u || ' u SET card = c.id FROM ' || c || ' c WHERE c.usr = u.id;');
2473 $$ LANGUAGE plpgsql;
2476 CREATE OR REPLACE FUNCTION migration_tools.insert_856_9_conditional (TEXT, TEXT) RETURNS TEXT AS $$
2478 ## USAGE: UPDATE biblio.record_entry SET marc = migration_tools.insert_856_9(marc, 'ABC') WHERE [...];
2480 my ($marcxml, $shortname) = @_;
2483 use MARC::File::XML;
2488 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
2490 foreach my $field ( $marc->field('856') ) {
2491 if ( scalar(grep( /(contentreserve|netlibrary|overdrive)\.com/i, $field->subfield('u'))) > 0 &&
2492 ! ( $field->as_string('9') =~ m/$shortname/ ) ) {
2493 $field->add_subfields( '9' => $shortname );
2494 $field->update( ind2 => '0');
2498 $xml = $marc->as_xml_record;
2499 $xml =~ s/^<\?.+?\?>$//mo;
2501 $xml =~ s/>\s+</></sgo;
2506 $$ LANGUAGE PLPERLU STABLE;
2508 CREATE OR REPLACE FUNCTION migration_tools.insert_856_9 (TEXT, TEXT) RETURNS TEXT AS $$
2510 ## USAGE: UPDATE biblio.record_entry SET marc = migration_tools.insert_856_9(marc, 'ABC') WHERE [...];
2512 my ($marcxml, $shortname) = @_;
2515 use MARC::File::XML;
2520 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
2522 foreach my $field ( $marc->field('856') ) {
2523 if ( ! $field->as_string('9') ) {
2524 $field->add_subfields( '9' => $shortname );
2528 $xml = $marc->as_xml_record;
2529 $xml =~ s/^<\?.+?\?>$//mo;
2531 $xml =~ s/>\s+</></sgo;
2536 $$ LANGUAGE PLPERLU STABLE;
2539 CREATE OR REPLACE FUNCTION migration_tools.change_call_number(copy_id BIGINT, new_label TEXT, cn_class BIGINT) RETURNS VOID AS $$
2551 -- Bail out if asked to change the label to ##URI##
2552 IF new_label = '##URI##' THEN
2556 -- Gather information
2557 SELECT call_number INTO old_volume FROM asset.copy WHERE id = copy_id;
2558 SELECT record INTO bib FROM asset.call_number WHERE id = old_volume;
2559 SELECT owning_lib, label INTO owner, old_label FROM asset.call_number WHERE id = old_volume;
2561 -- Bail out if the label already is ##URI##
2562 IF old_label = '##URI##' THEN
2566 -- Bail out if the call number label is already correct
2567 IF new_volume = old_volume THEN
2571 -- Check whether we already have a destination volume available
2572 SELECT id INTO new_volume FROM asset.call_number
2575 owning_lib = owner AND
2576 label = new_label AND
2579 -- Create destination volume if needed
2581 INSERT INTO asset.call_number (creator, editor, record, owning_lib, label, label_class)
2582 VALUES (1, 1, bib, owner, new_label, cn_class);
2583 SELECT id INTO new_volume FROM asset.call_number
2586 owning_lib = owner AND
2587 label = new_label AND
2591 -- Move copy to destination
2592 UPDATE asset.copy SET call_number = new_volume WHERE id = copy_id;
2594 -- Delete source volume if it is now empty
2595 SELECT id INTO remainder FROM asset.copy WHERE call_number = old_volume AND NOT deleted;
2597 DELETE FROM asset.call_number WHERE id = old_volume;
2602 $$ LANGUAGE plpgsql;
2604 CREATE OR REPLACE FUNCTION migration_tools.zip_to_city_state_county (TEXT) RETURNS TEXT[] AS $$
2609 open (FH, '<', '/openils/var/data/zips.txt') or return ('No File Found', 'No File Found', 'No File Found');
2613 my ($junk, $state, $city, $zip, $foo, $bar, $county, $baz, $morejunk) = split(/\|/);
2614 $zipdata{$zip} = [$city, $state, $county];
2617 if (defined $zipdata{$input}) {
2618 my ($city, $state, $county) = @{$zipdata{$input}};
2619 return [$city, $state, $county];
2620 } elsif (defined $zipdata{substr $input, 0, 5}) {
2621 my ($city, $state, $county) = @{$zipdata{substr $input, 0, 5}};
2622 return [$city, $state, $county];
2624 return ['ZIP not found', 'ZIP not found', 'ZIP not found'];
2627 $$ LANGUAGE PLPERLU STABLE;
2629 CREATE OR REPLACE FUNCTION migration_tools.check_ou_depth ( ) RETURNS VOID AS $$
2636 errors_found BOOLEAN;
2638 parent_shortname TEXT;
2644 type_parent_depth INT;
2649 errors_found := FALSE;
2651 -- Checking actor.org_unit_type
2653 FOR type_id IN EXECUTE ('SELECT id FROM actor.org_unit_type ORDER BY id;') LOOP
2655 SELECT depth FROM actor.org_unit_type WHERE id = type_id INTO type_depth;
2656 SELECT parent FROM actor.org_unit_type WHERE id = type_id INTO type_parent;
2658 IF type_parent IS NOT NULL THEN
2660 SELECT depth FROM actor.org_unit_type WHERE id = type_parent INTO type_parent_depth;
2662 IF type_depth - type_parent_depth <> 1 THEN
2663 SELECT name FROM actor.org_unit_type WHERE id = type_id INTO ou_type_name;
2664 SELECT name FROM actor.org_unit_type WHERE id = type_parent INTO parent_type;
2665 RAISE INFO 'The % org unit type has a depth of %, but its parent org unit type, %, has a depth of %.',
2666 ou_type_name, type_depth, parent_type, type_parent_depth;
2667 errors_found := TRUE;
2675 -- Checking actor.org_unit
2677 FOR ou IN EXECUTE ('SELECT id FROM actor.org_unit ORDER BY shortname;') LOOP
2679 SELECT parent_ou FROM actor.org_unit WHERE id = ou INTO ou_parent;
2680 SELECT t.depth FROM actor.org_unit_type t, actor.org_unit o WHERE o.ou_type = t.id and o.id = ou INTO org_unit_depth;
2681 SELECT t.depth FROM actor.org_unit_type t, actor.org_unit o WHERE o.ou_type = t.id and o.id = ou_parent INTO parent_depth;
2682 SELECT shortname FROM actor.org_unit WHERE id = ou INTO ou_shortname;
2683 SELECT shortname FROM actor.org_unit WHERE id = ou_parent INTO parent_shortname;
2684 SELECT t.name FROM actor.org_unit_type t, actor.org_unit o WHERE o.ou_type = t.id and o.id = ou INTO ou_type_name;
2685 SELECT t.name FROM actor.org_unit_type t, actor.org_unit o WHERE o.ou_type = t.id and o.id = ou_parent INTO parent_type;
2687 IF ou_parent IS NOT NULL THEN
2689 IF (org_unit_depth - parent_depth <> 1) OR (
2690 (SELECT parent FROM actor.org_unit_type WHERE name = ou_type_name) <> (SELECT id FROM actor.org_unit_type WHERE name = parent_type)
2692 RAISE INFO '% (org unit %) is a % (depth %) but its parent, % (org unit %), is a % (depth %).',
2693 ou_shortname, ou, ou_type_name, org_unit_depth, parent_shortname, ou_parent, parent_type, parent_depth;
2694 errors_found := TRUE;
2701 IF NOT errors_found THEN
2702 RAISE INFO 'No errors found.';
2709 $$ LANGUAGE plpgsql;
2712 CREATE OR REPLACE FUNCTION migration_tools.refresh_opac_visible_copies ( ) RETURNS VOID AS $$
2716 DELETE FROM asset.opac_visible_copies;
2718 INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
2720 cp.id, cp.circ_lib, cn.record
2723 JOIN asset.call_number cn ON (cn.id = cp.call_number)
2724 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
2725 JOIN asset.copy_location cl ON (cp.location = cl.id)
2726 JOIN config.copy_status cs ON (cp.status = cs.id)
2727 JOIN biblio.record_entry b ON (cn.record = b.id)
2736 cp.id NOT IN (SELECT id FROM asset.opac_visible_copies);
2740 $$ LANGUAGE plpgsql;
2743 CREATE OR REPLACE FUNCTION migration_tools.change_owning_lib(copy_id BIGINT, new_owning_lib INTEGER) RETURNS VOID AS $$
2749 old_owning_lib INTEGER;
2755 -- Gather information
2756 SELECT call_number INTO old_volume FROM asset.copy WHERE id = copy_id;
2757 SELECT record INTO bib FROM asset.call_number WHERE id = old_volume;
2758 SELECT owning_lib, label INTO old_owning_lib, old_label FROM asset.call_number WHERE id = old_volume;
2760 -- Bail out if the new_owning_lib is not the ID of an org_unit
2761 IF new_owning_lib NOT IN (SELECT id FROM actor.org_unit) THEN
2763 '% is not a valid actor.org_unit ID; no change made.',
2768 -- Bail out discreetly if the owning_lib is already correct
2769 IF new_owning_lib = old_owning_lib THEN
2773 -- Check whether we already have a destination volume available
2774 SELECT id INTO new_volume FROM asset.call_number
2777 owning_lib = new_owning_lib AND
2778 label = old_label AND
2781 -- Create destination volume if needed
2783 INSERT INTO asset.call_number (creator, editor, record, owning_lib, label)
2784 VALUES (1, 1, bib, new_owning_lib, old_label);
2785 SELECT id INTO new_volume FROM asset.call_number
2788 owning_lib = new_owning_lib AND
2789 label = old_label AND
2793 -- Move copy to destination
2794 UPDATE asset.copy SET call_number = new_volume WHERE id = copy_id;
2796 -- Delete source volume if it is now empty
2797 SELECT id INTO remainder FROM asset.copy WHERE call_number = old_volume AND NOT deleted;
2799 DELETE FROM asset.call_number WHERE id = old_volume;
2804 $$ LANGUAGE plpgsql;
2807 CREATE OR REPLACE FUNCTION migration_tools.change_owning_lib(copy_id BIGINT, new_owner TEXT) RETURNS VOID AS $$
2809 -- You can use shortnames with this function, which looks up the org unit ID and passes it to change_owning_lib(BIGINT,INTEGER).
2812 new_owning_lib INTEGER;
2816 -- Parse the new_owner as an org unit ID or shortname
2817 IF new_owner IN (SELECT shortname FROM actor.org_unit) THEN
2818 SELECT id INTO new_owning_lib FROM actor.org_unit WHERE shortname = new_owner;
2819 PERFORM migration_tools.change_owning_lib(copy_id, new_owning_lib);
2820 ELSIF new_owner ~ E'^[0-9]+$' THEN
2821 IF new_owner::INTEGER IN (SELECT id FROM actor.org_unit) THEN
2824 E'You don\'t need to put the actor.org_unit ID in quotes; '
2825 || E'if you put it in quotes, I\'m going to try to parse it as a shortname first.';
2826 new_owning_lib := new_owner::INTEGER;
2827 PERFORM migration_tools.change_owning_lib(copy_id, new_owning_lib);
2831 '% is not a valid actor.org_unit shortname or ID; no change made.',
2838 $$ LANGUAGE plpgsql;
2840 CREATE OR REPLACE FUNCTION migration_tools.marc_parses( TEXT ) RETURNS BOOLEAN AS $func$
2843 use MARC::File::XML (BinaryEncoding => 'UTF-8');
2846 MARC::Charset->assume_unicode(1);
2851 my $r = MARC::Record->new_from_xml( $xml );
2852 my $output_xml = $r->as_xml_record();
2860 $func$ LANGUAGE PLPERLU;
2861 COMMENT ON FUNCTION migration_tools.marc_parses(TEXT) IS 'Return boolean indicating if MARCXML string is parseable by MARC::File::XML';
2863 CREATE OR REPLACE FUNCTION migration_tools.simple_export_library_config(dir TEXT, orgs INT[]) RETURNS VOID AS $FUNC$
2865 EXECUTE $$COPY (SELECT * FROM actor.hours_of_operation WHERE id IN ($$ ||
2866 ARRAY_TO_STRING(orgs, ',') || $$)$$ ||
2867 $$) TO '$$ || dir || $$/actor_hours_of_operation'$$;
2868 EXECUTE $$COPY (SELECT org_unit, close_start, close_end, reason FROM actor.org_unit_closed WHERE org_unit IN ($$ ||
2869 ARRAY_TO_STRING(orgs, ',') || $$)$$ ||
2870 $$) TO '$$ || dir || $$/actor_org_unit_closed'$$;
2871 EXECUTE $$COPY (SELECT org_unit, name, value FROM actor.org_unit_setting WHERE org_unit IN ($$ ||
2872 ARRAY_TO_STRING(orgs, ',') || $$)$$ ||
2873 $$) TO '$$ || dir || $$/actor_org_unit_setting'$$;
2874 EXECUTE $$COPY (SELECT name, owning_lib, holdable, hold_verify, opac_visible, circulate FROM asset.copy_location WHERE owning_lib IN ($$ ||
2875 ARRAY_TO_STRING(orgs, ',') || $$)$$ ||
2876 $$) TO '$$ || dir || $$/asset_copy_location'$$;
2877 EXECUTE $$COPY (SELECT grp, org_unit, penalty, threshold FROM permission.grp_penalty_threshold WHERE org_unit IN ($$ ||
2878 ARRAY_TO_STRING(orgs, ',') || $$)$$ ||
2879 $$) TO '$$ || dir || $$/permission_grp_penalty_threshold'$$;
2880 EXECUTE $$COPY (SELECT owning_lib, label, label_sortkey FROM asset.call_number_prefix WHERE owning_lib IN ($$ ||
2881 ARRAY_TO_STRING(orgs, ',') || $$)$$ ||
2882 $$) TO '$$ || dir || $$/asset_call_number_prefix'$$;
2883 EXECUTE $$COPY (SELECT owning_lib, label, label_sortkey FROM asset.call_number_suffix WHERE owning_lib IN ($$ ||
2884 ARRAY_TO_STRING(orgs, ',') || $$)$$ ||
2885 $$) TO '$$ || dir || $$/asset_call_number_suffix'$$;
2886 EXECUTE $$COPY config.rule_circ_duration TO '$$ || dir || $$/config_rule_circ_duration'$$;
2887 EXECUTE $$COPY config.rule_age_hold_protect TO '$$ || dir || $$/config_rule_age_hold_protect'$$;
2888 EXECUTE $$COPY config.rule_max_fine TO '$$ || dir || $$/config_rule_max_fine'$$;
2889 EXECUTE $$COPY config.rule_recurring_fine TO '$$ || dir || $$/config_rule_recurring_fine'$$;
2890 EXECUTE $$COPY permission.grp_tree TO '$$ || dir || $$/permission_grp_tree'$$;
2892 $FUNC$ LANGUAGE PLPGSQL;
2894 CREATE OR REPLACE FUNCTION migration_tools.simple_import_library_config(dir TEXT) RETURNS VOID AS $FUNC$
2896 EXECUTE $$COPY actor.hours_of_operation FROM '$$ || dir || $$/actor_hours_of_operation'$$;
2897 EXECUTE $$COPY actor.org_unit_closed (org_unit, close_start, close_end, reason) FROM '$$ || dir || $$/actor_org_unit_closed'$$;
2898 EXECUTE $$COPY actor.org_unit_setting (org_unit, name, value) FROM '$$ || dir || $$/actor_org_unit_setting'$$;
2899 EXECUTE $$COPY asset.copy_location (name, owning_lib, holdable, hold_verify, opac_visible, circulate) FROM '$$ || dir || $$/asset_copy_location'$$;
2900 EXECUTE $$COPY permission.grp_penalty_threshold (grp, org_unit, penalty, threshold) FROM '$$ || dir || $$/permission_grp_penalty_threshold'$$;
2901 EXECUTE $$COPY asset.call_number_prefix (owning_lib, label, label_sortkey) FROM '$$ || dir || $$/asset_call_number_prefix'$$;
2902 EXECUTE $$COPY asset.call_number_suffix (owning_lib, label, label_sortkey) FROM '$$ || dir || $$/asset_call_number_suffix'$$;
2904 -- import any new circ rules
2905 PERFORM migration_tools.simple_import_new_rows_by_value(dir, 'config', 'rule_circ_duration', 'id', 'name');
2906 PERFORM migration_tools.simple_import_new_rows_by_value(dir, 'config', 'rule_age_hold_protect', 'id', 'name');
2907 PERFORM migration_tools.simple_import_new_rows_by_value(dir, 'config', 'rule_max_fine', 'id', 'name');
2908 PERFORM migration_tools.simple_import_new_rows_by_value(dir, 'config', 'rule_recurring_fine', 'id', 'name');
2910 -- and permission groups
2911 PERFORM migration_tools.simple_import_new_rows_by_value(dir, 'permission', 'grp_tree', 'id', 'name');
2914 $FUNC$ LANGUAGE PLPGSQL;
2917 CREATE OR REPLACE FUNCTION migration_tools.simple_import_new_rows_by_value(dir TEXT, schemaname TEXT, tablename TEXT, idcol TEXT, matchcol TEXT) RETURNS VOID AS $FUNC$
2926 EXECUTE $$DROP TABLE IF EXISTS tmp_$$ || tablename;
2927 EXECUTE $$CREATE TEMPORARY TABLE tmp_$$ || tablename || $$ AS SELECT * FROM $$ || schemaname || '.' || tablename || $$ LIMIT 0$$;
2928 EXECUTE $$COPY tmp_$$ || tablename || $$ FROM '$$ || dir || '/' || schemaname || '_' || tablename || $$'$$;
2929 loopq := 'SELECT ' || matchcol || ' FROM tmp_' || tablename || ' ORDER BY ' || idcol;
2930 existsq := 'SELECT COUNT(*) FROM ' || schemaname || '.' || tablename || ' WHERE ' || matchcol || ' = $1';
2931 SELECT ARRAY_AGG(column_name::TEXT) INTO cols FROM information_schema.columns WHERE table_schema = schemaname AND table_name = tablename AND column_name <> idcol;
2932 FOR name IN EXECUTE loopq LOOP
2933 EXECUTE existsq INTO ct USING name;
2935 RAISE NOTICE 'inserting %.% row for %', schemaname, tablename, name;
2936 copyst := 'INSERT INTO ' || schemaname || '.' || tablename || ' (' || ARRAY_TO_STRING(cols, ',') || ') SELECT ' || ARRAY_TO_STRING(cols, ',') ||
2937 ' FROM tmp_' || tablename || ' WHERE ' || matchcol || ' = $1';
2938 EXECUTE copyst USING name;
2942 $FUNC$ LANGUAGE PLPGSQL;
2944 CREATE OR REPLACE FUNCTION migration_tools.split_rows_on_column_with_delimiter(schemaname TEXT, tablename TEXT, matchcol TEXT, delimiter TEXT) RETURNS VOID AS $FUNC$
2951 loopq := 'SELECT id FROM ' || schemaname || '.' || tablename || ' WHERE ' || matchcol || ' ~ $1 ORDER BY id';
2952 SELECT ARRAY_AGG(column_name::TEXT) INTO cols FROM information_schema.columns WHERE table_schema = schemaname AND table_name = tablename AND column_name <> 'id' AND column_name <> matchcol;
2953 FOR id IN EXECUTE loopq USING delimiter LOOP
2954 RAISE NOTICE 'splitting row from %.% with id = %', schemaname, tablename, id;
2955 splitst := 'INSERT INTO ' || schemaname || '.' || tablename || ' (' || ARRAY_TO_STRING(cols, ',') || ', ' || matchcol || ') SELECT ' || ARRAY_TO_STRING(cols, ',') || ', s.token ' ||
2956 ' FROM ' || schemaname || '.' || tablename || ' t, UNNEST(STRING_TO_ARRAY(t.' || matchcol || ', $2)) s(token) WHERE id = $1';
2957 EXECUTE splitst USING id, delimiter;
2960 $FUNC$ LANGUAGE PLPGSQL;
2962 CREATE OR REPLACE FUNCTION migration_tools.merge_marc_fields( TEXT, TEXT, TEXT[] ) RETURNS TEXT AS $func$
2968 use MARC::File::XML (BinaryEncoding => 'UTF-8');
2971 MARC::Charset->assume_unicode(1);
2973 my $target_xml = shift;
2974 my $source_xml = shift;
2980 eval { $target = MARC::Record->new_from_xml( $target_xml ); };
2984 eval { $source = MARC::Record->new_from_xml( $source_xml ); };
2989 my $source_id = $source->subfield('901', 'c');
2990 $source_id = $source->subfield('903', 'a') unless $source_id;
2991 my $target_id = $target->subfield('901', 'c');
2992 $target_id = $target->subfield('903', 'a') unless $target_id;
2994 my %existing_fields;
2995 foreach my $tag (@$tags) {
2996 my %existing_fields = map { $_->as_formatted() => 1 } $target->field($tag);
2997 my @to_add = grep { not exists $existing_fields{$_->as_formatted()} } $source->field($tag);
2998 $target->insert_fields_ordered(map { $_->clone() } @to_add);
3000 elog(NOTICE, "Merged $tag tag(s) from $source_id to $target_id");
3004 my $xml = $target->as_xml_record;
3005 $xml =~ s/^<\?.+?\?>$//mo;
3007 $xml =~ s/>\s+</></sgo;
3011 $func$ LANGUAGE PLPERLU;
3012 COMMENT ON FUNCTION migration_tools.merge_marc_fields( TEXT, TEXT, TEXT[] ) IS 'Given two MARCXML strings and an array of tags, returns MARCXML representing the merge of the specified fields from the second MARCXML record into the first.';
3014 CREATE OR REPLACE FUNCTION migration_tools.make_stub_bib (text[], text[]) RETURNS TEXT AS $func$
3020 use MARC::File::XML (BinaryEncoding => 'UTF-8');
3023 my $in_tags = shift;
3024 my $in_values = shift;
3026 # hack-and-slash parsing of array-passed-as-string;
3027 # this can go away once everybody is running Postgres 9.1+
3028 my $csv = Text::CSV->new({binary => 1});
3031 my $status = $csv->parse($in_tags);
3032 my $tags = [ $csv->fields() ];
3033 $in_values =~ s/^{//;
3034 $in_values =~ s/}$//;
3035 $status = $csv->parse($in_values);
3036 my $values = [ $csv->fields() ];
3038 my $marc = MARC::Record->new();
3040 $marc->leader('00000nam a22000007 4500');
3041 $marc->append_fields(MARC::Field->new('008', '000000s 000 eng d'));
3043 foreach my $i (0..$#$tags) {
3045 if ($tags->[$i] =~ /^(\d{3})([0-9a-z])$/) {
3048 $marc->append_fields(MARC::Field->new($tag, ' ', ' ', $sf => $values->[$i])) if $values->[$i] !~ /^\s*$/ and $values->[$i] ne 'NULL';
3049 } elsif ($tags->[$i] =~ /^(\d{3})$/) {
3051 $marc->append_fields(MARC::Field->new($tag, $values->[$i])) if $values->[$i] !~ /^\s*$/ and $values->[$i] ne 'NULL';
3055 my $xml = $marc->as_xml_record;
3056 $xml =~ s/^<\?.+?\?>$//mo;
3058 $xml =~ s/>\s+</></sgo;
3062 $func$ LANGUAGE PLPERLU;
3063 COMMENT ON FUNCTION migration_tools.make_stub_bib (text[], text[]) IS $$Simple function to create a stub MARCXML bib from a set of columns.
3064 The first argument is an array of tag/subfield specifiers, e.g., ARRAY['001', '245a', '500a'].
3065 The second argument is an array of text containing the values to plug into each field.
3066 If the value for a given field is NULL or the empty string, it is not inserted.
3069 CREATE OR REPLACE FUNCTION migration_tools.set_indicator (TEXT, TEXT, INTEGER, CHAR(1)) RETURNS TEXT AS $func$
3071 my ($marcxml, $tag, $pos, $value) = @_;
3074 use MARC::File::XML (BinaryEncoding => 'UTF-8');
3078 MARC::Charset->assume_unicode(1);
3080 elog(ERROR, 'indicator position must be either 1 or 2') unless $pos =~ /^[12]$/;
3081 elog(ERROR, 'MARC tag must be numeric') unless $tag =~ /^\d{3}$/;
3082 elog(ERROR, 'MARC tag must not be control field') if $tag =~ /^00/;
3083 elog(ERROR, 'Value must be exactly one character') unless $value =~ /^.$/;
3087 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
3089 foreach my $field ($marc->field($tag)) {
3090 $field->update("ind$pos" => $value);
3092 $xml = $marc->as_xml_record;
3093 $xml =~ s/^<\?.+?\?>$//mo;
3095 $xml =~ s/>\s+</></sgo;
3099 $func$ LANGUAGE PLPERLU;
3101 COMMENT ON FUNCTION migration_tools.set_indicator(TEXT, TEXT, INTEGER, CHAR(1)) IS $$Set indicator value of a specified MARC field.
3102 The first argument is a MARCXML string.
3103 The second argument is a MARC tag.
3104 The third argument is the indicator position, either 1 or 2.
3105 The fourth argument is the character to set the indicator value to.
3106 All occurences of the specified field will be changed.
3107 The function returns the revised MARCXML string.$$;
3109 CREATE OR REPLACE FUNCTION migration_tools.create_staff_user(
3114 first_name TEXT DEFAULT '',
3115 last_name TEXT DEFAULT ''
3116 ) RETURNS VOID AS $func$
3118 RAISE NOTICE '%', org ;
3119 INSERT INTO actor.usr (usrname, passwd, ident_type, first_given_name, family_name, home_ou, profile)
3120 SELECT username, password, 1, first_name, last_name, aou.id, pgt.id
3121 FROM actor.org_unit aou, permission.grp_tree pgt
3122 WHERE aou.shortname = org
3123 AND pgt.name = perm_group;
3128 -- example: SELECT * FROM migration_tools.duplicate_template(5,'{3,4}');
3129 CREATE OR REPLACE FUNCTION migration_tools.duplicate_template (INTEGER, INTEGER[]) RETURNS VOID AS $$
3131 target_event_def ALIAS FOR $1;
3134 DROP TABLE IF EXISTS new_atevdefs;
3135 CREATE TEMP TABLE new_atevdefs (atevdef INTEGER);
3136 FOR i IN array_lower(orgs,1) .. array_upper(orgs,1) LOOP
3137 INSERT INTO action_trigger.event_definition (
3158 ,name || ' (clone of '||target_event_def||')'
3174 action_trigger.event_definition
3176 id = target_event_def
3178 RAISE INFO 'created atevdef with id = %', currval('action_trigger.event_definition_id_seq');
3179 INSERT INTO new_atevdefs SELECT currval('action_trigger.event_definition_id_seq');
3180 INSERT INTO action_trigger.environment (
3186 currval('action_trigger.event_definition_id_seq')
3191 action_trigger.environment
3193 event_def = target_event_def
3195 INSERT INTO action_trigger.event_params (
3200 currval('action_trigger.event_definition_id_seq')
3204 action_trigger.event_params
3206 event_def = target_event_def
3209 RAISE INFO '-- UPDATE action_trigger.event_definition SET active = CASE WHEN id = % THEN FALSE ELSE TRUE END WHERE id in (%,%);', target_event_def, target_event_def, (SELECT array_to_string(array_agg(atevdef),',') from new_atevdefs);
3211 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3213 -- example: SELECT * FROM migration_tools.duplicate_template_but_change_delay(5,'{3,4}','00:30:00'::INTERVAL);
3214 CREATE OR REPLACE FUNCTION migration_tools.duplicate_template_but_change_delay (INTEGER, INTEGER[], INTERVAL) RETURNS VOID AS $$
3216 target_event_def ALIAS FOR $1;
3218 new_interval ALIAS FOR $3;
3220 DROP TABLE IF EXISTS new_atevdefs;
3221 CREATE TEMP TABLE new_atevdefs (atevdef INTEGER);
3222 FOR i IN array_lower(orgs,1) .. array_upper(orgs,1) LOOP
3223 INSERT INTO action_trigger.event_definition (
3244 ,name || ' (clone of '||target_event_def||')'
3260 action_trigger.event_definition
3262 id = target_event_def
3264 RAISE INFO 'created atevdef with id = %', currval('action_trigger.event_definition_id_seq');
3265 INSERT INTO new_atevdefs SELECT currval('action_trigger.event_definition_id_seq');
3266 INSERT INTO action_trigger.environment (
3272 currval('action_trigger.event_definition_id_seq')
3277 action_trigger.environment
3279 event_def = target_event_def
3281 INSERT INTO action_trigger.event_params (
3286 currval('action_trigger.event_definition_id_seq')
3290 action_trigger.event_params
3292 event_def = target_event_def
3295 RAISE INFO '-- UPDATE action_trigger.event_definition SET active = CASE WHEN id = % THEN FALSE ELSE TRUE END WHERE id in (%,%);', target_event_def, target_event_def, (SELECT array_to_string(array_agg(atevdef),',') from new_atevdefs);
3297 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3299 -- example: SELECT * FROM migration_tools.duplicate_templates(3,'{5,6}');
3300 CREATE OR REPLACE FUNCTION migration_tools.duplicate_templates (INTEGER, INTEGER[]) RETURNS VOID AS $$
3303 target_event_defs ALIAS FOR $2;
3305 DROP TABLE IF EXISTS new_atevdefs;
3306 CREATE TEMP TABLE new_atevdefs (atevdef INTEGER);
3307 FOR i IN array_lower(target_event_defs,1) .. array_upper(target_event_defs,1) LOOP
3308 INSERT INTO action_trigger.event_definition (
3329 ,name || ' (clone of '||target_event_defs[i]||')'
3345 action_trigger.event_definition
3347 id = target_event_defs[i]
3349 RAISE INFO 'created atevdef with id = %', currval('action_trigger.event_definition_id_seq');
3350 INSERT INTO new_atevdefs SELECT currval('action_trigger.event_definition_id_seq');
3351 INSERT INTO action_trigger.environment (
3357 currval('action_trigger.event_definition_id_seq')
3362 action_trigger.environment
3364 event_def = target_event_defs[i]
3366 INSERT INTO action_trigger.event_params (
3371 currval('action_trigger.event_definition_id_seq')
3375 action_trigger.event_params
3377 event_def = target_event_defs[i]
3380 RAISE INFO '-- UPDATE action_trigger.event_definition SET active = TRUE WHERE id in (%);', (SELECT array_to_string(array_agg(atevdef),',') from new_atevdefs);
3382 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3384 CREATE OR REPLACE FUNCTION migration_tools.reset_event (BIGINT) RETURNS VOID AS $$
3386 action_trigger.event
3390 ,complete_time = NULL
3391 ,update_process = NULL
3393 ,template_output = NULL
3394 ,error_output = NULL
3395 ,async_output = NULL
3400 CREATE OR REPLACE FUNCTION migration_tools.get_marc_leader (TEXT) RETURNS TEXT AS $$
3404 use MARC::File::XML;
3409 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
3410 $field = $marc->leader();
3413 $$ LANGUAGE PLPERLU STABLE;
3415 CREATE OR REPLACE FUNCTION migration_tools.get_marc_tag (TEXT, TEXT, TEXT, TEXT) RETURNS TEXT AS $$
3416 my ($marcxml, $tag, $subfield, $delimiter) = @_;
3419 use MARC::File::XML;
3424 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
3425 $field = $marc->field($tag);
3427 return $field->as_string($subfield,$delimiter);
3428 $$ LANGUAGE PLPERLU STABLE;
3430 CREATE OR REPLACE FUNCTION migration_tools.get_marc_tags (TEXT, TEXT, TEXT, TEXT) RETURNS TEXT[] AS $$
3431 my ($marcxml, $tag, $subfield, $delimiter) = @_;
3434 use MARC::File::XML;
3439 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
3440 @fields = $marc->field($tag);
3443 foreach my $field (@fields) {
3444 push @texts, $field->as_string($subfield,$delimiter);
3447 $$ LANGUAGE PLPERLU STABLE;
3449 CREATE OR REPLACE FUNCTION migration_tools.find_hold_matrix_matchpoint (INTEGER) RETURNS INTEGER AS $$
3450 SELECT action.find_hold_matrix_matchpoint(
3451 (SELECT pickup_lib FROM action.hold_request WHERE id = $1),
3452 (SELECT request_lib FROM action.hold_request WHERE id = $1),
3453 (SELECT current_copy FROM action.hold_request WHERE id = $1),
3454 (SELECT usr FROM action.hold_request WHERE id = $1),
3455 (SELECT requestor FROM action.hold_request WHERE id = $1)
3459 CREATE OR REPLACE FUNCTION migration_tools.find_hold_matrix_matchpoint2 (INTEGER) RETURNS SETOF action.matrix_test_result AS $$
3460 SELECT action.hold_request_permit_test(
3461 (SELECT pickup_lib FROM action.hold_request WHERE id = $1),
3462 (SELECT request_lib FROM action.hold_request WHERE id = $1),
3463 (SELECT current_copy FROM action.hold_request WHERE id = $1),
3464 (SELECT usr FROM action.hold_request WHERE id = $1),
3465 (SELECT requestor FROM action.hold_request WHERE id = $1)
3469 CREATE OR REPLACE FUNCTION migration_tools.find_circ_matrix_matchpoint (INTEGER) RETURNS SETOF action.found_circ_matrix_matchpoint AS $$
3470 SELECT action.find_circ_matrix_matchpoint(
3471 (SELECT circ_lib FROM action.circulation WHERE id = $1),
3472 (SELECT target_copy FROM action.circulation WHERE id = $1),
3473 (SELECT usr FROM action.circulation WHERE id = $1),
3475 NULLIF(phone_renewal,false),
3476 NULLIF(desk_renewal,false),
3477 NULLIF(opac_renewal,false),
3479 ) FROM action.circulation WHERE id = $1
3484 CREATE OR REPLACE FUNCTION migration_tools.assert (BOOLEAN) RETURNS VOID AS $$
3489 RAISE EXCEPTION 'assertion';
3492 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3494 CREATE OR REPLACE FUNCTION migration_tools.assert (BOOLEAN,TEXT) RETURNS VOID AS $$
3500 RAISE EXCEPTION '%', msg;
3503 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3505 CREATE OR REPLACE FUNCTION migration_tools.assert (BOOLEAN,TEXT,TEXT) RETURNS TEXT AS $$
3508 fail_msg ALIAS FOR $2;
3509 success_msg ALIAS FOR $3;
3512 RAISE EXCEPTION '%', fail_msg;
3516 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3518 -- push bib sequence and return starting value for reserved range
3519 CREATE OR REPLACE FUNCTION migration_tools.push_bib_sequence(INTEGER) RETURNS BIGINT AS $$
3521 bib_count ALIAS FOR $1;
3524 PERFORM setval('biblio.record_entry_id_seq',(SELECT MAX(id) FROM biblio.record_entry) + bib_count + 2000);
3526 SELECT CEIL(MAX(id)/1000)*1000+1000 FROM biblio.record_entry WHERE id < (SELECT last_value FROM biblio.record_entry_id_seq)
3531 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3533 -- set a new salted password
3535 CREATE OR REPLACE FUNCTION migration_tools.set_salted_passwd(INTEGER,TEXT) RETURNS BOOLEAN AS $$
3537 usr_id ALIAS FOR $1;
3538 plain_passwd ALIAS FOR $2;
3543 SELECT actor.create_salt('main') INTO plain_salt;
3545 SELECT MD5(plain_passwd) INTO md5_passwd;
3547 PERFORM actor.set_passwd(usr_id, 'main', MD5(plain_salt || md5_passwd), plain_salt);
3552 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3555 -- convenience functions for handling copy_location maps
3557 CREATE OR REPLACE FUNCTION migration_tools.handle_shelf (TEXT,TEXT,TEXT,INTEGER) RETURNS VOID AS $$
3559 table_schema ALIAS FOR $1;
3560 table_name ALIAS FOR $2;
3561 org_shortname ALIAS FOR $3;
3562 org_range ALIAS FOR $4;
3565 -- if x_org is on the mapping table, it'll take precedence over the passed org_shortname param
3566 -- though we'll still use the passed org for the full path traversal when needed
3567 x_org_found BOOLEAN;
3572 EXECUTE 'SELECT EXISTS (
3574 FROM information_schema.columns
3575 WHERE table_schema = $1
3577 and column_name = ''desired_shelf''
3578 )' INTO proceed USING table_schema, table_name;
3580 RAISE EXCEPTION 'Missing column desired_shelf';
3583 EXECUTE 'SELECT EXISTS (
3585 FROM information_schema.columns
3586 WHERE table_schema = $1
3588 and column_name = ''x_org''
3589 )' INTO x_org_found USING table_schema, table_name;
3591 SELECT id INTO org FROM actor.org_unit WHERE shortname = org_shortname;
3593 RAISE EXCEPTION 'Cannot find org by shortname';
3596 SELECT INTO org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( org );
3598 EXECUTE 'ALTER TABLE '
3599 || quote_ident(table_name)
3600 || ' DROP COLUMN IF EXISTS x_shelf';
3601 EXECUTE 'ALTER TABLE '
3602 || quote_ident(table_name)
3603 || ' ADD COLUMN x_shelf INTEGER';
3606 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3607 || ' SET x_shelf = id FROM asset_copy_location b'
3608 || ' WHERE BTRIM(UPPER(a.desired_shelf)) = BTRIM(UPPER(b.name))'
3609 || ' AND b.owning_lib = x_org'
3610 || ' AND NOT b.deleted';
3611 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3612 || ' SET x_shelf = id FROM asset.copy_location b'
3613 || ' WHERE BTRIM(UPPER(a.desired_shelf)) = BTRIM(UPPER(b.name))'
3614 || ' AND b.owning_lib = x_org'
3615 || ' AND x_shelf IS NULL'
3616 || ' AND NOT b.deleted';
3618 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3619 || ' SET x_shelf = id FROM asset_copy_location b'
3620 || ' WHERE BTRIM(UPPER(a.desired_shelf)) = BTRIM(UPPER(b.name))'
3621 || ' AND b.owning_lib = $1'
3622 || ' AND NOT b.deleted'
3624 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3625 || ' SET x_shelf = id FROM asset_copy_location b'
3626 || ' WHERE BTRIM(UPPER(a.desired_shelf)) = BTRIM(UPPER(b.name))'
3627 || ' AND b.owning_lib = $1'
3628 || ' AND x_shelf IS NULL'
3629 || ' AND NOT b.deleted'
3633 FOREACH o IN ARRAY org_list LOOP
3634 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3635 || ' SET x_shelf = id FROM asset.copy_location b'
3636 || ' WHERE BTRIM(UPPER(a.desired_shelf)) = BTRIM(UPPER(b.name))'
3637 || ' AND b.owning_lib = $1 AND x_shelf IS NULL'
3638 || ' AND NOT b.deleted'
3642 EXECUTE 'SELECT migration_tools.assert(
3643 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_shelf <> '''' AND x_shelf IS NULL),
3644 ''Cannot find a desired location'',
3645 ''Found all desired locations''
3649 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3651 -- convenience functions for handling circmod maps
3653 CREATE OR REPLACE FUNCTION migration_tools.handle_circmod (TEXT,TEXT) RETURNS VOID AS $$
3655 table_schema ALIAS FOR $1;
3656 table_name ALIAS FOR $2;
3659 EXECUTE 'SELECT EXISTS (
3661 FROM information_schema.columns
3662 WHERE table_schema = $1
3664 and column_name = ''desired_circmod''
3665 )' INTO proceed USING table_schema, table_name;
3667 RAISE EXCEPTION 'Missing column desired_circmod';
3670 EXECUTE 'ALTER TABLE '
3671 || quote_ident(table_name)
3672 || ' DROP COLUMN IF EXISTS x_circmod';
3673 EXECUTE 'ALTER TABLE '
3674 || quote_ident(table_name)
3675 || ' ADD COLUMN x_circmod TEXT';
3677 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3678 || ' SET x_circmod = code FROM config.circ_modifier b'
3679 || ' WHERE BTRIM(UPPER(a.desired_circmod)) = BTRIM(UPPER(b.code))';
3681 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3682 || ' SET x_circmod = code FROM config.circ_modifier b'
3683 || ' WHERE BTRIM(UPPER(a.desired_circmod)) = BTRIM(UPPER(b.name))'
3684 || ' AND x_circmod IS NULL';
3686 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3687 || ' SET x_circmod = code FROM config.circ_modifier b'
3688 || ' WHERE BTRIM(UPPER(a.desired_circmod)) = BTRIM(UPPER(b.description))'
3689 || ' AND x_circmod IS NULL';
3691 EXECUTE 'SELECT migration_tools.assert(
3692 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_circmod <> '''' AND x_circmod IS NULL),
3693 ''Cannot find a desired circulation modifier'',
3694 ''Found all desired circulation modifiers''
3698 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3700 -- convenience functions for handling item status maps
3702 CREATE OR REPLACE FUNCTION migration_tools.handle_status (TEXT,TEXT) RETURNS VOID AS $$
3704 table_schema ALIAS FOR $1;
3705 table_name ALIAS FOR $2;
3708 EXECUTE 'SELECT EXISTS (
3710 FROM information_schema.columns
3711 WHERE table_schema = $1
3713 and column_name = ''desired_status''
3714 )' INTO proceed USING table_schema, table_name;
3716 RAISE EXCEPTION 'Missing column desired_status';
3719 EXECUTE 'ALTER TABLE '
3720 || quote_ident(table_name)
3721 || ' DROP COLUMN IF EXISTS x_status';
3722 EXECUTE 'ALTER TABLE '
3723 || quote_ident(table_name)
3724 || ' ADD COLUMN x_status INTEGER';
3726 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3727 || ' SET x_status = id FROM config.copy_status b'
3728 || ' WHERE BTRIM(UPPER(a.desired_status)) = BTRIM(UPPER(b.name))';
3730 EXECUTE 'SELECT migration_tools.assert(
3731 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_status <> '''' AND x_status IS NULL),
3732 ''Cannot find a desired copy status'',
3733 ''Found all desired copy statuses''
3737 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3739 -- convenience functions for handling org maps
3741 CREATE OR REPLACE FUNCTION migration_tools.handle_org (TEXT,TEXT) RETURNS VOID AS $$
3743 table_schema ALIAS FOR $1;
3744 table_name ALIAS FOR $2;
3747 EXECUTE 'SELECT EXISTS (
3749 FROM information_schema.columns
3750 WHERE table_schema = $1
3752 and column_name = ''desired_org''
3753 )' INTO proceed USING table_schema, table_name;
3755 RAISE EXCEPTION 'Missing column desired_org';
3758 EXECUTE 'ALTER TABLE '
3759 || quote_ident(table_name)
3760 || ' DROP COLUMN IF EXISTS x_org';
3761 EXECUTE 'ALTER TABLE '
3762 || quote_ident(table_name)
3763 || ' ADD COLUMN x_org INTEGER';
3765 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3766 || ' SET x_org = id FROM actor.org_unit b'
3767 || ' WHERE BTRIM(a.desired_org) = BTRIM(b.shortname)';
3769 EXECUTE 'SELECT migration_tools.assert(
3770 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_org <> '''' AND x_org IS NULL),
3771 ''Cannot find a desired org unit'',
3772 ''Found all desired org units''
3776 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3778 -- convenience function for handling desired_not_migrate
3780 CREATE OR REPLACE FUNCTION migration_tools.handle_not_migrate (TEXT,TEXT) RETURNS VOID AS $$
3782 table_schema ALIAS FOR $1;
3783 table_name ALIAS FOR $2;
3786 EXECUTE 'SELECT EXISTS (
3788 FROM information_schema.columns
3789 WHERE table_schema = $1
3791 and column_name = ''desired_not_migrate''
3792 )' INTO proceed USING table_schema, table_name;
3794 RAISE EXCEPTION 'Missing column desired_not_migrate';
3797 EXECUTE 'ALTER TABLE '
3798 || quote_ident(table_name)
3799 || ' DROP COLUMN IF EXISTS x_migrate';
3800 EXECUTE 'ALTER TABLE '
3801 || quote_ident(table_name)
3802 || ' ADD COLUMN x_migrate BOOLEAN';
3804 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3805 || ' SET x_migrate = CASE'
3806 || ' WHEN BTRIM(desired_not_migrate) = ''TRUE'' THEN FALSE'
3807 || ' WHEN BTRIM(desired_not_migrate) = ''DNM'' THEN FALSE'
3808 || ' WHEN BTRIM(desired_not_migrate) = ''Do Not Migrate'' THEN FALSE'
3809 || ' WHEN BTRIM(desired_not_migrate) = ''FALSE'' THEN TRUE'
3810 || ' WHEN BTRIM(desired_not_migrate) = ''Migrate'' THEN TRUE'
3811 || ' WHEN BTRIM(desired_not_migrate) = '''' THEN TRUE'
3814 EXECUTE 'SELECT migration_tools.assert(
3815 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE x_migrate IS NULL),
3816 ''Not all desired_not_migrate values understood'',
3817 ''All desired_not_migrate values understood''
3821 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3823 -- convenience function for handling desired_not_migrate
3825 CREATE OR REPLACE FUNCTION migration_tools.handle_barred_or_blocked (TEXT,TEXT) RETURNS VOID AS $$
3827 table_schema ALIAS FOR $1;
3828 table_name ALIAS FOR $2;
3831 EXECUTE 'SELECT EXISTS (
3833 FROM information_schema.columns
3834 WHERE table_schema = $1
3836 and column_name = ''desired_barred_or_blocked''
3837 )' INTO proceed USING table_schema, table_name;
3839 RAISE EXCEPTION 'Missing column desired_barred_or_blocked';
3842 EXECUTE 'ALTER TABLE '
3843 || quote_ident(table_name)
3844 || ' DROP COLUMN IF EXISTS x_barred';
3845 EXECUTE 'ALTER TABLE '
3846 || quote_ident(table_name)
3847 || ' ADD COLUMN x_barred BOOLEAN';
3849 EXECUTE 'ALTER TABLE '
3850 || quote_ident(table_name)
3851 || ' DROP COLUMN IF EXISTS x_blocked';
3852 EXECUTE 'ALTER TABLE '
3853 || quote_ident(table_name)
3854 || ' ADD COLUMN x_blocked BOOLEAN';
3856 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3857 || ' SET x_barred = CASE'
3858 || ' WHEN BTRIM(desired_barred_or_blocked) = ''Barred'' THEN TRUE'
3859 || ' WHEN BTRIM(desired_barred_or_blocked) = ''Blocked'' THEN FALSE'
3860 || ' WHEN BTRIM(desired_barred_or_blocked) = ''Neither'' THEN FALSE'
3861 || ' WHEN BTRIM(desired_barred_or_blocked) = '''' THEN FALSE'
3864 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3865 || ' SET x_blocked = CASE'
3866 || ' WHEN BTRIM(desired_barred_or_blocked) = ''Blocked'' THEN TRUE'
3867 || ' WHEN BTRIM(desired_barred_or_blocked) = ''Barred'' THEN FALSE'
3868 || ' WHEN BTRIM(desired_barred_or_blocked) = ''Neither'' THEN FALSE'
3869 || ' WHEN BTRIM(desired_barred_or_blocked) = '''' THEN FALSE'
3872 EXECUTE 'SELECT migration_tools.assert(
3873 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE x_barred IS NULL or x_blocked IS NULL),
3874 ''Not all desired_barred_or_blocked values understood'',
3875 ''All desired_barred_or_blocked values understood''
3879 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3881 -- convenience function for handling desired_profile
3883 CREATE OR REPLACE FUNCTION migration_tools.handle_profile (TEXT,TEXT) RETURNS VOID AS $$
3885 table_schema ALIAS FOR $1;
3886 table_name ALIAS FOR $2;
3889 EXECUTE 'SELECT EXISTS (
3891 FROM information_schema.columns
3892 WHERE table_schema = $1
3894 and column_name = ''desired_profile''
3895 )' INTO proceed USING table_schema, table_name;
3897 RAISE EXCEPTION 'Missing column desired_profile';
3900 EXECUTE 'ALTER TABLE '
3901 || quote_ident(table_name)
3902 || ' DROP COLUMN IF EXISTS x_profile';
3903 EXECUTE 'ALTER TABLE '
3904 || quote_ident(table_name)
3905 || ' ADD COLUMN x_profile INTEGER';
3907 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
3908 || ' SET x_profile = b.id FROM permission.grp_tree b'
3909 || ' WHERE BTRIM(UPPER(a.desired_profile)) = BTRIM(UPPER(b.name))';
3911 EXECUTE 'SELECT migration_tools.assert(
3912 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_profile <> '''' AND x_profile IS NULL),
3913 ''Cannot find a desired profile'',
3914 ''Found all desired profiles''
3918 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
3920 -- convenience function for handling desired actor stat cats
3922 CREATE OR REPLACE FUNCTION migration_tools.vivicate_actor_sc_and_sce (TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
3924 table_schema ALIAS FOR $1;
3925 table_name ALIAS FOR $2;
3926 field_suffix ALIAS FOR $3; -- for distinguishing between desired_sce1, desired_sce2, etc.
3927 org_shortname ALIAS FOR $4;
3935 SELECT 'desired_sc' || field_suffix INTO sc;
3936 SELECT 'desired_sce' || field_suffix INTO sce;
3938 EXECUTE 'SELECT EXISTS (
3940 FROM information_schema.columns
3941 WHERE table_schema = $1
3943 and column_name = $3
3944 )' INTO proceed USING table_schema, table_name, sc;
3946 RAISE EXCEPTION 'Missing column %', sc;
3948 EXECUTE 'SELECT EXISTS (
3950 FROM information_schema.columns
3951 WHERE table_schema = $1
3953 and column_name = $3
3954 )' INTO proceed USING table_schema, table_name, sce;
3956 RAISE EXCEPTION 'Missing column %', sce;
3959 SELECT id INTO org FROM actor.org_unit WHERE shortname = org_shortname;
3961 RAISE EXCEPTION 'Cannot find org by shortname';
3963 SELECT INTO org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( org );
3965 -- caller responsible for their own truncates though we try to prevent duplicates
3966 EXECUTE 'INSERT INTO actor_stat_cat (owner, name)
3971 ' || quote_ident(table_name) || '
3973 NULLIF(BTRIM('||sc||'),'''') IS NOT NULL
3977 WHERE owner = ANY ($2)
3978 AND name = BTRIM('||sc||')
3983 WHERE owner = ANY ($2)
3984 AND name = BTRIM('||sc||')
3987 USING org, org_list;
3989 EXECUTE 'INSERT INTO actor_stat_cat_entry (stat_cat, owner, value)
3994 WHERE owner = ANY ($2)
3995 AND BTRIM('||sc||') = BTRIM(name))
3998 WHERE owner = ANY ($2)
3999 AND BTRIM('||sc||') = BTRIM(name))
4004 ' || quote_ident(table_name) || '
4006 NULLIF(BTRIM('||sc||'),'''') IS NOT NULL
4007 AND NULLIF(BTRIM('||sce||'),'''') IS NOT NULL
4010 FROM actor.stat_cat_entry
4014 WHERE owner = ANY ($2)
4015 AND BTRIM('||sc||') = BTRIM(name)
4016 ) AND value = BTRIM('||sce||')
4017 AND owner = ANY ($2)
4021 FROM actor_stat_cat_entry
4025 WHERE owner = ANY ($2)
4026 AND BTRIM('||sc||') = BTRIM(name)
4027 ) AND value = BTRIM('||sce||')
4028 AND owner = ANY ($2)
4031 USING org, org_list;
4033 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4035 CREATE OR REPLACE FUNCTION migration_tools.handle_actor_sc_and_sce (TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4037 table_schema ALIAS FOR $1;
4038 table_name ALIAS FOR $2;
4039 field_suffix ALIAS FOR $3; -- for distinguishing between desired_sce1, desired_sce2, etc.
4040 org_shortname ALIAS FOR $4;
4048 SELECT 'desired_sc' || field_suffix INTO sc;
4049 SELECT 'desired_sce' || field_suffix INTO sce;
4050 EXECUTE 'SELECT EXISTS (
4052 FROM information_schema.columns
4053 WHERE table_schema = $1
4055 and column_name = $3
4056 )' INTO proceed USING table_schema, table_name, sc;
4058 RAISE EXCEPTION 'Missing column %', sc;
4060 EXECUTE 'SELECT EXISTS (
4062 FROM information_schema.columns
4063 WHERE table_schema = $1
4065 and column_name = $3
4066 )' INTO proceed USING table_schema, table_name, sce;
4068 RAISE EXCEPTION 'Missing column %', sce;
4071 SELECT id INTO org FROM actor.org_unit WHERE shortname = org_shortname;
4073 RAISE EXCEPTION 'Cannot find org by shortname';
4076 SELECT INTO org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( org );
4078 EXECUTE 'ALTER TABLE '
4079 || quote_ident(table_name)
4080 || ' DROP COLUMN IF EXISTS x_sc' || field_suffix;
4081 EXECUTE 'ALTER TABLE '
4082 || quote_ident(table_name)
4083 || ' ADD COLUMN x_sc' || field_suffix || ' INTEGER';
4084 EXECUTE 'ALTER TABLE '
4085 || quote_ident(table_name)
4086 || ' DROP COLUMN IF EXISTS x_sce' || field_suffix;
4087 EXECUTE 'ALTER TABLE '
4088 || quote_ident(table_name)
4089 || ' ADD COLUMN x_sce' || field_suffix || ' INTEGER';
4092 EXECUTE 'UPDATE ' || quote_ident(table_name) || '
4094 x_sc' || field_suffix || ' = id
4096 (SELECT id, name, owner FROM actor_stat_cat
4097 UNION SELECT id, name, owner FROM actor.stat_cat) u
4099 BTRIM(UPPER(u.name)) = BTRIM(UPPER(' || sc || '))
4100 AND u.owner = ANY ($1);'
4103 EXECUTE 'UPDATE ' || quote_ident(table_name) || '
4105 x_sce' || field_suffix || ' = id
4107 (SELECT id, stat_cat, owner, value FROM actor_stat_cat_entry
4108 UNION SELECT id, stat_cat, owner, value FROM actor.stat_cat_entry) u
4110 u.stat_cat = x_sc' || field_suffix || '
4111 AND BTRIM(UPPER(u.value)) = BTRIM(UPPER(' || sce || '))
4112 AND u.owner = ANY ($1);'
4115 EXECUTE 'SELECT migration_tools.assert(
4116 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_sc' || field_suffix || ' <> '''' AND x_sc' || field_suffix || ' IS NULL),
4117 ''Cannot find a desired stat cat'',
4118 ''Found all desired stat cats''
4121 EXECUTE 'SELECT migration_tools.assert(
4122 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_sce' || field_suffix || ' <> '''' AND x_sce' || field_suffix || ' IS NULL),
4123 ''Cannot find a desired stat cat entry'',
4124 ''Found all desired stat cat entries''
4128 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4130 -- convenience functions for adding shelving locations
4131 DROP FUNCTION IF EXISTS migration_tools.find_shelf(INT,TEXT);
4132 CREATE OR REPLACE FUNCTION migration_tools.find_shelf(org_id INT, shelf_name TEXT) RETURNS INTEGER AS $$
4138 SELECT INTO d MAX(distance) FROM actor.org_unit_ancestors_distance(org_id);
4141 SELECT INTO cur_id id FROM actor.org_unit_ancestor_at_depth(org_id,d);
4142 SELECT INTO return_id id FROM asset.copy_location WHERE owning_lib = cur_id AND name ILIKE shelf_name;
4143 IF return_id IS NOT NULL THEN
4151 $$ LANGUAGE plpgsql;
4153 -- may remove later but testing using this with new migration scripts and not loading acls until go live
4155 DROP FUNCTION IF EXISTS migration_tools.find_mig_shelf(INT,TEXT);
4156 CREATE OR REPLACE FUNCTION migration_tools.find_mig_shelf(org_id INT, shelf_name TEXT) RETURNS INTEGER AS $$
4162 SELECT INTO d MAX(distance) FROM actor.org_unit_ancestors_distance(org_id);
4165 SELECT INTO cur_id id FROM actor.org_unit_ancestor_at_depth(org_id,d);
4167 SELECT INTO return_id id FROM
4168 (SELECT * FROM asset.copy_location UNION ALL SELECT * FROM asset_copy_location) x
4169 WHERE owning_lib = cur_id AND name ILIKE shelf_name;
4170 IF return_id IS NOT NULL THEN
4178 $$ LANGUAGE plpgsql;
4180 -- yet another subfield 9 function, this one only adds the $9 if the ind1 = 1 or 4 and ind2 = 0 or 1
4181 DROP FUNCTION IF EXISTS migration_tools.strict_add_sf9(TEXT,TEXT);
4182 CREATE OR REPLACE FUNCTION migration_tools.strict_add_sf9(marc TEXT, new_9 TEXT)
4190 use MARC::File::XML (BinaryEncoding => 'utf8');
4192 binmode(STDERR, ':bytes');
4193 binmode(STDOUT, ':utf8');
4194 binmode(STDERR, ':utf8');
4196 my $marc_xml = shift;
4197 my $new_9_to_set = shift;
4199 $marc_xml =~ s/(<leader>.........)./${1}a/;
4202 $marc_xml = MARC::Record->new_from_xml($marc_xml);
4205 #elog("could not parse $bibid: $@\n");
4206 import MARC::File::XML (BinaryEncoding => 'utf8');
4210 my @uris = $marc_xml->field('856');
4211 return $marc_xml->as_xml_record() unless @uris;
4213 foreach my $field (@uris) {
4214 my $ind1 = $field->indicator('1');
4215 if (!defined $ind1) { next; }
4216 if ($ind1 ne '1' && $ind1 ne '4') { next; }
4217 my $ind2 = $field->indicator('2');
4218 if (!defined $ind2) { next; }
4219 if ($ind2 ne '0' && $ind2 ne '1') { next; }
4220 $field->add_subfields( '9' => $new_9_to_set );
4223 return $marc_xml->as_xml_record();
4227 -- yet another subfield 9 function, this one only adds the $9 and forces
4228 -- ind1 = 4 if not already ind1 = 1 or 4 and ind2 = 0 if not already ind2 = 0 or 1
4229 DROP FUNCTION IF EXISTS migration_tools.force_add_sf9(TEXT,TEXT);
4230 CREATE OR REPLACE FUNCTION migration_tools.force_add_sf9(marc TEXT, new_9 TEXT)
4238 use MARC::File::XML (BinaryEncoding => 'utf8');
4240 binmode(STDERR, ':bytes');
4241 binmode(STDOUT, ':utf8');
4242 binmode(STDERR, ':utf8');
4244 my $marc_xml = shift;
4245 my $new_9_to_set = shift;
4247 $marc_xml =~ s/(<leader>.........)./${1}a/;
4250 $marc_xml = MARC::Record->new_from_xml($marc_xml);
4253 #elog("could not parse $bibid: $@\n");
4254 import MARC::File::XML (BinaryEncoding => 'utf8');
4258 my @uris = $marc_xml->field('856');
4259 return $marc_xml->as_xml_record() unless @uris;
4261 foreach my $field (@uris) {
4262 my $ind1 = $field->indicator('1');
4263 if (!defined $ind1) { next; }
4264 if ($ind1 ne '1' && $ind1 ne '4') { $field->set_indicator(1,'4'); }
4265 my $ind2 = $field->indicator('2');
4266 if (!defined $ind2) { next; }
4267 if ($ind2 ne '0' && $ind2 ne '1') { $field->set_indicator(2,'0'); }
4268 $field->add_subfields( '9' => $new_9_to_set );
4271 return $marc_xml->as_xml_record();
4275 -- alternate adding subfield 9 function in that it adds them to existing tags where the 856$u matches a correct value only
4276 DROP FUNCTION IF EXISTS migration_tools.add_sf9(TEXT,TEXT,TEXT);
4277 CREATE OR REPLACE FUNCTION migration_tools.add_sf9(marc TEXT, partial_u TEXT, new_9 TEXT)
4285 use MARC::File::XML (BinaryEncoding => 'utf8');
4287 binmode(STDERR, ':bytes');
4288 binmode(STDOUT, ':utf8');
4289 binmode(STDERR, ':utf8');
4291 my $marc_xml = shift;
4292 my $matching_u_text = shift;
4293 my $new_9_to_set = shift;
4295 $marc_xml =~ s/(<leader>.........)./${1}a/;
4298 $marc_xml = MARC::Record->new_from_xml($marc_xml);
4301 #elog("could not parse $bibid: $@\n");
4302 import MARC::File::XML (BinaryEncoding => 'utf8');
4306 my @uris = $marc_xml->field('856');
4307 return unless @uris;
4309 foreach my $field (@uris) {
4310 my $sfu = $field->subfield('u');
4311 my $ind2 = $field->indicator('2');
4312 if (!defined $ind2) { next; }
4313 if ($ind2 ne '0') { next; }
4314 if (!defined $sfu) { next; }
4315 if ($sfu =~ m/$matching_u_text/ or $matching_u_text eq 'pineapple') {
4316 $field->add_subfields( '9' => $new_9_to_set );
4321 return $marc_xml->as_xml_record();
4325 DROP FUNCTION IF EXISTS migration_tools.add_sf9(BIGINT, TEXT, TEXT, REGCLASS);
4326 CREATE OR REPLACE FUNCTION migration_tools.add_sf9(bib_id BIGINT, target_u_text TEXT, sf9_text TEXT, bib_table REGCLASS)
4335 EXECUTE 'SELECT marc FROM ' || bib_table || ' WHERE id = ' || bib_id INTO source_xml;
4337 SELECT migration_tools.add_sf9(source_xml, target_u_text, sf9_text) INTO new_xml;
4340 new_xml = '$_$' || new_xml || '$_$';
4342 IF new_xml != source_xml THEN
4343 EXECUTE 'UPDATE ' || bib_table || ' SET marc = ' || new_xml || ' WHERE id = ' || bib_id;
4350 $BODY$ LANGUAGE plpgsql;
4353 DROP FUNCTION IF EXISTS migration_tools.strip_tag(TEXT,TEXT);
4354 CREATE OR REPLACE FUNCTION migration_tools.strip_tag(marc TEXT, tag TEXT)
4362 use MARC::File::XML (BinaryEncoding => 'utf8');
4364 binmode(STDERR, ':bytes');
4365 binmode(STDOUT, ':utf8');
4366 binmode(STDERR, ':utf8');
4368 my $marc_xml = shift;
4371 $marc_xml =~ s/(<leader>.........)./${1}a/;
4374 $marc_xml = MARC::Record->new_from_xml($marc_xml);
4377 #elog("could not parse $bibid: $@\n");
4378 import MARC::File::XML (BinaryEncoding => 'utf8');
4382 my @fields = $marc_xml->field($tag);
4383 return $marc_xml->as_xml_record() unless @fields;
4385 $marc_xml->delete_fields(@fields);
4387 return $marc_xml->as_xml_record();
4391 -- convenience function for linking to the item staging table
4393 CREATE OR REPLACE FUNCTION migration_tools.handle_item_barcode (TEXT,TEXT,TEXT,TEXT,BOOLEAN) RETURNS VOID AS $$
4395 table_schema ALIAS FOR $1;
4396 table_name ALIAS FOR $2;
4397 foreign_column_name ALIAS FOR $3;
4398 main_column_name ALIAS FOR $4;
4399 btrim_desired ALIAS FOR $5;
4402 EXECUTE 'SELECT EXISTS (
4404 FROM information_schema.columns
4405 WHERE table_schema = $1
4407 and column_name = $3
4408 )' INTO proceed USING table_schema, table_name, foreign_column_name;
4410 RAISE EXCEPTION '%.% missing column %', table_schema, table_name, foreign_column_name;
4413 EXECUTE 'SELECT EXISTS (
4415 FROM information_schema.columns
4416 WHERE table_schema = $1
4417 AND table_name = ''asset_copy_legacy''
4418 and column_name = $2
4419 )' INTO proceed USING table_schema, main_column_name;
4421 RAISE EXCEPTION 'No %.asset_copy_legacy with column %', table_schema, main_column_name;
4424 EXECUTE 'ALTER TABLE '
4425 || quote_ident(table_name)
4426 || ' DROP COLUMN IF EXISTS x_item';
4427 EXECUTE 'ALTER TABLE '
4428 || quote_ident(table_name)
4429 || ' ADD COLUMN x_item BIGINT';
4431 IF btrim_desired THEN
4432 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
4433 || ' SET x_item = b.id FROM asset_copy_legacy b'
4434 || ' WHERE BTRIM(a.' || quote_ident(foreign_column_name)
4435 || ') = BTRIM(b.' || quote_ident(main_column_name) || ')';
4437 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
4438 || ' SET x_item = b.id FROM asset_copy_legacy b'
4439 || ' WHERE a.' || quote_ident(foreign_column_name)
4440 || ' = b.' || quote_ident(main_column_name);
4443 --EXECUTE 'SELECT migration_tools.assert(
4444 -- NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE ' || quote_ident(foreign_column_name) || ' <> '''' AND x_item IS NULL),
4445 -- ''Cannot link every barcode'',
4446 -- ''Every barcode linked''
4450 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4452 -- convenience function for linking to the user staging table
4454 CREATE OR REPLACE FUNCTION migration_tools.handle_user_barcode (TEXT,TEXT,TEXT,TEXT,BOOLEAN) RETURNS VOID AS $$
4456 table_schema ALIAS FOR $1;
4457 table_name ALIAS FOR $2;
4458 foreign_column_name ALIAS FOR $3;
4459 main_column_name ALIAS FOR $4;
4460 btrim_desired ALIAS FOR $5;
4463 EXECUTE 'SELECT EXISTS (
4465 FROM information_schema.columns
4466 WHERE table_schema = $1
4468 and column_name = $3
4469 )' INTO proceed USING table_schema, table_name, foreign_column_name;
4471 RAISE EXCEPTION '%.% missing column %', table_schema, table_name, foreign_column_name;
4474 EXECUTE 'SELECT EXISTS (
4476 FROM information_schema.columns
4477 WHERE table_schema = $1
4478 AND table_name = ''actor_usr_legacy''
4479 and column_name = $2
4480 )' INTO proceed USING table_schema, main_column_name;
4482 RAISE EXCEPTION 'No %.actor_usr_legacy with column %', table_schema, main_column_name;
4485 EXECUTE 'ALTER TABLE '
4486 || quote_ident(table_name)
4487 || ' DROP COLUMN IF EXISTS x_user';
4488 EXECUTE 'ALTER TABLE '
4489 || quote_ident(table_name)
4490 || ' ADD COLUMN x_user INTEGER';
4492 IF btrim_desired THEN
4493 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
4494 || ' SET x_user = b.id FROM actor_usr_legacy b'
4495 || ' WHERE BTRIM(a.' || quote_ident(foreign_column_name)
4496 || ') = BTRIM(b.' || quote_ident(main_column_name) || ')';
4498 EXECUTE 'UPDATE ' || quote_ident(table_name) || ' a'
4499 || ' SET x_user = b.id FROM actor_usr_legacy b'
4500 || ' WHERE a.' || quote_ident(foreign_column_name)
4501 || ' = b.' || quote_ident(main_column_name);
4504 --EXECUTE 'SELECT migration_tools.assert(
4505 -- NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE ' || quote_ident(foreign_column_name) || ' <> '''' AND x_user IS NULL),
4506 -- ''Cannot link every barcode'',
4507 -- ''Every barcode linked''
4511 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4513 -- convenience function for linking two tables
4514 -- e.g. select migration_tools.handle_link(:'migschema','asset_copy','barcode','test_foo','l_barcode','x_acp_id',false);
4515 CREATE OR REPLACE FUNCTION migration_tools.handle_link (TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,BOOLEAN) RETURNS VOID AS $$
4517 table_schema ALIAS FOR $1;
4518 table_a ALIAS FOR $2;
4519 column_a ALIAS FOR $3;
4520 table_b ALIAS FOR $4;
4521 column_b ALIAS FOR $5;
4522 column_x ALIAS FOR $6;
4523 btrim_desired ALIAS FOR $7;
4526 EXECUTE 'SELECT EXISTS (
4528 FROM information_schema.columns
4529 WHERE table_schema = $1
4531 and column_name = $3
4532 )' INTO proceed USING table_schema, table_a, column_a;
4534 RAISE EXCEPTION '%.% missing column %', table_schema, table_a, column_a;
4537 EXECUTE 'SELECT EXISTS (
4539 FROM information_schema.columns
4540 WHERE table_schema = $1
4542 and column_name = $3
4543 )' INTO proceed USING table_schema, table_b, column_b;
4545 RAISE EXCEPTION '%.% missing column %', table_schema, table_b, column_b;
4548 EXECUTE 'ALTER TABLE '
4549 || quote_ident(table_b)
4550 || ' DROP COLUMN IF EXISTS ' || quote_ident(column_x);
4551 EXECUTE 'ALTER TABLE '
4552 || quote_ident(table_b)
4553 || ' ADD COLUMN ' || quote_ident(column_x) || ' BIGINT';
4555 IF btrim_desired THEN
4556 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4557 || ' SET ' || quote_ident(column_x) || ' = a.id FROM ' || quote_ident(table_a) || ' a'
4558 || ' WHERE BTRIM(a.' || quote_ident(column_a)
4559 || ') = BTRIM(b.' || quote_ident(column_b) || ')';
4561 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4562 || ' SET ' || quote_ident(column_x) || ' = a.id FROM ' || quote_ident(table_a) || ' a'
4563 || ' WHERE a.' || quote_ident(column_a)
4564 || ' = b.' || quote_ident(column_b);
4568 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4570 -- convenience function for linking two tables, but copying column w into column x instead of "id"
4571 -- e.g. select migration_tools.handle_link2(:'migschema','asset_copy','barcode','test_foo','l_barcode','id','x_acp_id',false);
4572 CREATE OR REPLACE FUNCTION migration_tools.handle_link2 (TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,BOOLEAN) RETURNS VOID AS $$
4574 table_schema ALIAS FOR $1;
4575 table_a ALIAS FOR $2;
4576 column_a ALIAS FOR $3;
4577 table_b ALIAS FOR $4;
4578 column_b ALIAS FOR $5;
4579 column_w ALIAS FOR $6;
4580 column_x ALIAS FOR $7;
4581 btrim_desired ALIAS FOR $8;
4584 EXECUTE 'SELECT EXISTS (
4586 FROM information_schema.columns
4587 WHERE table_schema = $1
4589 and column_name = $3
4590 )' INTO proceed USING table_schema, table_a, column_a;
4592 RAISE EXCEPTION '%.% missing column %', table_schema, table_a, column_a;
4595 EXECUTE 'SELECT EXISTS (
4597 FROM information_schema.columns
4598 WHERE table_schema = $1
4600 and column_name = $3
4601 )' INTO proceed USING table_schema, table_b, column_b;
4603 RAISE EXCEPTION '%.% missing column %', table_schema, table_b, column_b;
4606 EXECUTE 'ALTER TABLE '
4607 || quote_ident(table_b)
4608 || ' DROP COLUMN IF EXISTS ' || quote_ident(column_x);
4609 EXECUTE 'ALTER TABLE '
4610 || quote_ident(table_b)
4611 || ' ADD COLUMN ' || quote_ident(column_x) || ' TEXT';
4613 IF btrim_desired THEN
4614 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4615 || ' SET ' || quote_ident(column_x) || ' = a.' || quote_ident(column_w) || ' FROM ' || quote_ident(table_a) || ' a'
4616 || ' WHERE BTRIM(a.' || quote_ident(column_a)
4617 || ') = BTRIM(b.' || quote_ident(column_b) || ')';
4619 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4620 || ' SET ' || quote_ident(column_x) || ' = a.' || quote_ident(column_w) || ' FROM ' || quote_ident(table_a) || ' a'
4621 || ' WHERE a.' || quote_ident(column_a)
4622 || ' = b.' || quote_ident(column_b);
4626 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4628 -- convenience function for linking two tables, but copying column w into column x instead of "id". Unlike handle_link2, this one won't drop the target column, and it also doesn't have a final boolean argument for btrim
4629 -- e.g. select migration_tools.handle_link3(:'migschema','asset_copy','barcode','test_foo','l_barcode','id','x_acp_id');
4630 CREATE OR REPLACE FUNCTION migration_tools.handle_link3 (TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4632 table_schema ALIAS FOR $1;
4633 table_a ALIAS FOR $2;
4634 column_a ALIAS FOR $3;
4635 table_b ALIAS FOR $4;
4636 column_b ALIAS FOR $5;
4637 column_w ALIAS FOR $6;
4638 column_x ALIAS FOR $7;
4641 EXECUTE 'SELECT EXISTS (
4643 FROM information_schema.columns
4644 WHERE table_schema = $1
4646 and column_name = $3
4647 )' INTO proceed USING table_schema, table_a, column_a;
4649 RAISE EXCEPTION '%.% missing column %', table_schema, table_a, column_a;
4652 EXECUTE 'SELECT EXISTS (
4654 FROM information_schema.columns
4655 WHERE table_schema = $1
4657 and column_name = $3
4658 )' INTO proceed USING table_schema, table_b, column_b;
4660 RAISE EXCEPTION '%.% missing column %', table_schema, table_b, column_b;
4663 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4664 || ' SET ' || quote_ident(column_x) || ' = a.' || quote_ident(column_w) || ' FROM ' || quote_ident(table_a) || ' a'
4665 || ' WHERE a.' || quote_ident(column_a)
4666 || ' = b.' || quote_ident(column_b);
4669 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4671 CREATE OR REPLACE FUNCTION migration_tools.handle_link3_skip_null_or_empty_string (TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4673 table_schema ALIAS FOR $1;
4674 table_a ALIAS FOR $2;
4675 column_a ALIAS FOR $3;
4676 table_b ALIAS FOR $4;
4677 column_b ALIAS FOR $5;
4678 column_w ALIAS FOR $6;
4679 column_x ALIAS FOR $7;
4682 EXECUTE 'SELECT EXISTS (
4684 FROM information_schema.columns
4685 WHERE table_schema = $1
4687 and column_name = $3
4688 )' INTO proceed USING table_schema, table_a, column_a;
4690 RAISE EXCEPTION '%.% missing column %', table_schema, table_a, column_a;
4693 EXECUTE 'SELECT EXISTS (
4695 FROM information_schema.columns
4696 WHERE table_schema = $1
4698 and column_name = $3
4699 )' INTO proceed USING table_schema, table_b, column_b;
4701 RAISE EXCEPTION '%.% missing column %', table_schema, table_b, column_b;
4704 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4705 || ' SET ' || quote_ident(column_x) || ' = a.' || quote_ident(column_w) || ' FROM ' || quote_ident(table_a) || ' a'
4706 || ' WHERE a.' || quote_ident(column_a)
4707 || ' = b.' || quote_ident(column_b)
4708 || ' AND NULLIF(a.' || quote_ident(column_w) || ','''') IS NOT NULL';
4711 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4713 CREATE OR REPLACE FUNCTION migration_tools.handle_link3_skip_null (TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4715 table_schema ALIAS FOR $1;
4716 table_a ALIAS FOR $2;
4717 column_a ALIAS FOR $3;
4718 table_b ALIAS FOR $4;
4719 column_b ALIAS FOR $5;
4720 column_w ALIAS FOR $6;
4721 column_x ALIAS FOR $7;
4724 EXECUTE 'SELECT EXISTS (
4726 FROM information_schema.columns
4727 WHERE table_schema = $1
4729 and column_name = $3
4730 )' INTO proceed USING table_schema, table_a, column_a;
4732 RAISE EXCEPTION '%.% missing column %', table_schema, table_a, column_a;
4735 EXECUTE 'SELECT EXISTS (
4737 FROM information_schema.columns
4738 WHERE table_schema = $1
4740 and column_name = $3
4741 )' INTO proceed USING table_schema, table_b, column_b;
4743 RAISE EXCEPTION '%.% missing column %', table_schema, table_b, column_b;
4746 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4747 || ' SET ' || quote_ident(column_x) || ' = a.' || quote_ident(column_w) || ' FROM ' || quote_ident(table_a) || ' a'
4748 || ' WHERE a.' || quote_ident(column_a)
4749 || ' = b.' || quote_ident(column_b)
4750 || ' AND a.' || quote_ident(column_w) || ' IS NOT NULL';
4753 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4755 CREATE OR REPLACE FUNCTION migration_tools.handle_link3_skip_true (TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4757 table_schema ALIAS FOR $1;
4758 table_a ALIAS FOR $2;
4759 column_a ALIAS FOR $3;
4760 table_b ALIAS FOR $4;
4761 column_b ALIAS FOR $5;
4762 column_w ALIAS FOR $6;
4763 column_x ALIAS FOR $7;
4766 EXECUTE 'SELECT EXISTS (
4768 FROM information_schema.columns
4769 WHERE table_schema = $1
4771 and column_name = $3
4772 )' INTO proceed USING table_schema, table_a, column_a;
4774 RAISE EXCEPTION '%.% missing column %', table_schema, table_a, column_a;
4777 EXECUTE 'SELECT EXISTS (
4779 FROM information_schema.columns
4780 WHERE table_schema = $1
4782 and column_name = $3
4783 )' INTO proceed USING table_schema, table_b, column_b;
4785 RAISE EXCEPTION '%.% missing column %', table_schema, table_b, column_b;
4788 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4789 || ' SET ' || quote_ident(column_x) || ' = a.' || quote_ident(column_w) || ' FROM ' || quote_ident(table_a) || ' a'
4790 || ' WHERE a.' || quote_ident(column_a)
4791 || ' = b.' || quote_ident(column_b)
4792 || ' AND a.' || quote_ident(column_w) || ' IS NOT TRUE';
4795 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4797 CREATE OR REPLACE FUNCTION migration_tools.handle_link3_skip_false (TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4799 table_schema ALIAS FOR $1;
4800 table_a ALIAS FOR $2;
4801 column_a ALIAS FOR $3;
4802 table_b ALIAS FOR $4;
4803 column_b ALIAS FOR $5;
4804 column_w ALIAS FOR $6;
4805 column_x ALIAS FOR $7;
4808 EXECUTE 'SELECT EXISTS (
4810 FROM information_schema.columns
4811 WHERE table_schema = $1
4813 and column_name = $3
4814 )' INTO proceed USING table_schema, table_a, column_a;
4816 RAISE EXCEPTION '%.% missing column %', table_schema, table_a, column_a;
4819 EXECUTE 'SELECT EXISTS (
4821 FROM information_schema.columns
4822 WHERE table_schema = $1
4824 and column_name = $3
4825 )' INTO proceed USING table_schema, table_b, column_b;
4827 RAISE EXCEPTION '%.% missing column %', table_schema, table_b, column_b;
4830 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4831 || ' SET ' || quote_ident(column_x) || ' = a.' || quote_ident(column_w) || ' FROM ' || quote_ident(table_a) || ' a'
4832 || ' WHERE a.' || quote_ident(column_a)
4833 || ' = b.' || quote_ident(column_b)
4834 || ' AND a.' || quote_ident(column_w) || ' IS NOT FALSE';
4837 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4839 CREATE OR REPLACE FUNCTION migration_tools.handle_link3_concat_skip_null (TEXT,TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4841 table_schema ALIAS FOR $1;
4842 table_a ALIAS FOR $2;
4843 column_a ALIAS FOR $3;
4844 table_b ALIAS FOR $4;
4845 column_b ALIAS FOR $5;
4846 column_w ALIAS FOR $6;
4847 column_x ALIAS FOR $7;
4850 EXECUTE 'SELECT EXISTS (
4852 FROM information_schema.columns
4853 WHERE table_schema = $1
4855 and column_name = $3
4856 )' INTO proceed USING table_schema, table_a, column_a;
4858 RAISE EXCEPTION '%.% missing column %', table_schema, table_a, column_a;
4861 EXECUTE 'SELECT EXISTS (
4863 FROM information_schema.columns
4864 WHERE table_schema = $1
4866 and column_name = $3
4867 )' INTO proceed USING table_schema, table_b, column_b;
4869 RAISE EXCEPTION '%.% missing column %', table_schema, table_b, column_b;
4872 EXECUTE 'UPDATE ' || quote_ident(table_b) || ' b'
4873 || ' SET ' || quote_ident(column_x) || ' = CONCAT_WS('' ; '',b.' || quote_ident(column_x) || ',a.' || quote_ident(column_w) || ') FROM ' || quote_ident(table_a) || ' a'
4874 || ' WHERE a.' || quote_ident(column_a)
4875 || ' = b.' || quote_ident(column_b)
4876 || ' AND NULLIF(a.' || quote_ident(column_w) || ','''') IS NOT NULL';
4879 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4881 -- convenience function for handling desired asset stat cats
4883 CREATE OR REPLACE FUNCTION migration_tools.vivicate_asset_sc_and_sce (TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4885 table_schema ALIAS FOR $1;
4886 table_name ALIAS FOR $2;
4887 field_suffix ALIAS FOR $3; -- for distinguishing between desired_sce1, desired_sce2, etc.
4888 org_shortname ALIAS FOR $4;
4896 SELECT 'desired_sc' || field_suffix INTO sc;
4897 SELECT 'desired_sce' || field_suffix INTO sce;
4899 EXECUTE 'SELECT EXISTS (
4901 FROM information_schema.columns
4902 WHERE table_schema = $1
4904 and column_name = $3
4905 )' INTO proceed USING table_schema, table_name, sc;
4907 RAISE EXCEPTION 'Missing column %', sc;
4909 EXECUTE 'SELECT EXISTS (
4911 FROM information_schema.columns
4912 WHERE table_schema = $1
4914 and column_name = $3
4915 )' INTO proceed USING table_schema, table_name, sce;
4917 RAISE EXCEPTION 'Missing column %', sce;
4920 SELECT id INTO org FROM actor.org_unit WHERE shortname = org_shortname;
4922 RAISE EXCEPTION 'Cannot find org by shortname';
4924 SELECT INTO org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( org );
4926 -- caller responsible for their own truncates though we try to prevent duplicates
4927 EXECUTE 'INSERT INTO asset_stat_cat (owner, name)
4932 ' || quote_ident(table_name) || '
4934 NULLIF(BTRIM('||sc||'),'''') IS NOT NULL
4938 WHERE owner = ANY ($2)
4939 AND name = BTRIM('||sc||')
4944 WHERE owner = ANY ($2)
4945 AND name = BTRIM('||sc||')
4948 USING org, org_list;
4950 EXECUTE 'INSERT INTO asset_stat_cat_entry (stat_cat, owner, value)
4955 WHERE owner = ANY ($2)
4956 AND BTRIM('||sc||') = BTRIM(name))
4959 WHERE owner = ANY ($2)
4960 AND BTRIM('||sc||') = BTRIM(name))
4965 ' || quote_ident(table_name) || '
4967 NULLIF(BTRIM('||sc||'),'''') IS NOT NULL
4968 AND NULLIF(BTRIM('||sce||'),'''') IS NOT NULL
4971 FROM asset.stat_cat_entry
4975 WHERE owner = ANY ($2)
4976 AND BTRIM('||sc||') = BTRIM(name)
4977 ) AND value = BTRIM('||sce||')
4978 AND owner = ANY ($2)
4982 FROM asset_stat_cat_entry
4986 WHERE owner = ANY ($2)
4987 AND BTRIM('||sc||') = BTRIM(name)
4988 ) AND value = BTRIM('||sce||')
4989 AND owner = ANY ($2)
4992 USING org, org_list;
4994 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
4996 CREATE OR REPLACE FUNCTION migration_tools.handle_asset_sc_and_sce (TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
4998 table_schema ALIAS FOR $1;
4999 table_name ALIAS FOR $2;
5000 field_suffix ALIAS FOR $3; -- for distinguishing between desired_sce1, desired_sce2, etc.
5001 org_shortname ALIAS FOR $4;
5009 SELECT 'desired_sc' || field_suffix INTO sc;
5010 SELECT 'desired_sce' || field_suffix INTO sce;
5011 EXECUTE 'SELECT EXISTS (
5013 FROM information_schema.columns
5014 WHERE table_schema = $1
5016 and column_name = $3
5017 )' INTO proceed USING table_schema, table_name, sc;
5019 RAISE EXCEPTION 'Missing column %', sc;
5021 EXECUTE 'SELECT EXISTS (
5023 FROM information_schema.columns
5024 WHERE table_schema = $1
5026 and column_name = $3
5027 )' INTO proceed USING table_schema, table_name, sce;
5029 RAISE EXCEPTION 'Missing column %', sce;
5032 SELECT id INTO org FROM actor.org_unit WHERE shortname = org_shortname;
5034 RAISE EXCEPTION 'Cannot find org by shortname';
5037 SELECT INTO org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( org );
5039 EXECUTE 'ALTER TABLE '
5040 || quote_ident(table_name)
5041 || ' DROP COLUMN IF EXISTS x_sc' || field_suffix;
5042 EXECUTE 'ALTER TABLE '
5043 || quote_ident(table_name)
5044 || ' ADD COLUMN x_sc' || field_suffix || ' INTEGER';
5045 EXECUTE 'ALTER TABLE '
5046 || quote_ident(table_name)
5047 || ' DROP COLUMN IF EXISTS x_sce' || field_suffix;
5048 EXECUTE 'ALTER TABLE '
5049 || quote_ident(table_name)
5050 || ' ADD COLUMN x_sce' || field_suffix || ' INTEGER';
5053 EXECUTE 'UPDATE ' || quote_ident(table_name) || '
5055 x_sc' || field_suffix || ' = id
5057 (SELECT id, name, owner FROM asset_stat_cat
5058 UNION SELECT id, name, owner FROM asset.stat_cat) u
5060 BTRIM(UPPER(u.name)) = BTRIM(UPPER(' || sc || '))
5061 AND u.owner = ANY ($1);'
5064 EXECUTE 'UPDATE ' || quote_ident(table_name) || '
5066 x_sce' || field_suffix || ' = id
5068 (SELECT id, stat_cat, owner, value FROM asset_stat_cat_entry
5069 UNION SELECT id, stat_cat, owner, value FROM asset.stat_cat_entry) u
5071 u.stat_cat = x_sc' || field_suffix || '
5072 AND BTRIM(UPPER(u.value)) = BTRIM(UPPER(' || sce || '))
5073 AND u.owner = ANY ($1);'
5076 EXECUTE 'SELECT migration_tools.assert(
5077 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_sc' || field_suffix || ' <> '''' AND x_sc' || field_suffix || ' IS NULL),
5078 ''Cannot find a desired stat cat'',
5079 ''Found all desired stat cats''
5082 EXECUTE 'SELECT migration_tools.assert(
5083 NOT EXISTS (SELECT 1 FROM ' || quote_ident(table_name) || ' WHERE desired_sce' || field_suffix || ' <> '''' AND x_sce' || field_suffix || ' IS NULL),
5084 ''Cannot find a desired stat cat entry'',
5085 ''Found all desired stat cat entries''
5089 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
5091 DROP FUNCTION IF EXISTS migration_tools.btrim_lcolumns(TEXT,TEXT);
5092 CREATE OR REPLACE FUNCTION migration_tools.btrim_lcolumns(s_name TEXT, t_name TEXT) RETURNS BOOLEAN
5099 FOR c_name IN SELECT column_name FROM information_schema.columns WHERE
5101 AND table_schema = s_name
5102 AND (data_type='text' OR data_type='character varying')
5103 AND column_name like 'l_%'
5105 EXECUTE FORMAT('UPDATE ' || s_name || '.' || t_name || ' SET ' || c_name || ' = BTRIM(' || c_name || ')');
5112 DROP FUNCTION IF EXISTS migration_tools.btrim_columns(TEXT,TEXT);
5113 CREATE OR REPLACE FUNCTION migration_tools.btrim_columns(s_name TEXT, t_name TEXT) RETURNS BOOLEAN
5120 FOR c_name IN SELECT column_name FROM information_schema.columns WHERE
5122 AND table_schema = s_name
5123 AND (data_type='text' OR data_type='character varying')
5125 EXECUTE FORMAT('UPDATE ' || s_name || '.' || t_name || ' SET ' || c_name || ' = BTRIM(' || c_name || ')');
5132 DROP FUNCTION IF EXISTS migration_tools.null_empty_lcolumns(TEXT,TEXT);
5133 CREATE OR REPLACE FUNCTION migration_tools.null_empty_lcolumns(s_name TEXT, t_name TEXT) RETURNS BOOLEAN
5140 FOR c_name IN SELECT column_name FROM information_schema.columns WHERE
5142 AND table_schema = s_name
5143 AND (data_type='text' OR data_type='character varying')
5144 AND column_name like 'l_%'
5146 EXECUTE FORMAT('UPDATE ' || s_name || '.' || t_name || ' SET ' || c_name || ' = NULL WHERE ' || c_name || ' = '''' ');
5153 DROP FUNCTION IF EXISTS migration_tools.null_empty_columns(TEXT,TEXT);
5154 CREATE OR REPLACE FUNCTION migration_tools.null_empty_columns(s_name TEXT, t_name TEXT) RETURNS BOOLEAN
5161 FOR c_name IN SELECT column_name FROM information_schema.columns WHERE
5163 AND table_schema = s_name
5164 AND (data_type='text' OR data_type='character varying')
5166 EXECUTE FORMAT('UPDATE ' || s_name || '.' || t_name || ' SET ' || c_name || ' = NULL WHERE ' || c_name || ' = '''' ');