/* ------------------------------------------------------------------------ * * hash.sql * HASH partitioning functions * * Copyright (c) 2015-2016, Postgres Professional * * ------------------------------------------------------------------------ */ /* * Creates hash partitions for specified relation */ CREATE OR REPLACE FUNCTION @extschema@.create_hash_partitions( parent_relid REGCLASS, attribute TEXT, partitions_count INTEGER, partition_data BOOLEAN DEFAULT true) RETURNS INTEGER AS $$ DECLARE v_child_relname TEXT; v_type TEXT; v_plain_schema TEXT; v_plain_relname TEXT; v_hashfunc TEXT; BEGIN IF partition_data = true THEN /* Acquire data modification lock */ PERFORM @extschema@.prevent_relation_modification(parent_relid); ELSE /* Acquire lock on parent */ PERFORM @extschema@.lock_partitioned_relation(parent_relid); END IF; PERFORM @extschema@.validate_relname(parent_relid); attribute := lower(attribute); PERFORM @extschema@.common_relation_checks(parent_relid, attribute); v_type := @extschema@.get_attribute_type_name(parent_relid, attribute); SELECT * INTO v_plain_schema, v_plain_relname FROM @extschema@.get_plain_schema_and_relname(parent_relid); v_hashfunc := @extschema@.get_type_hash_func(v_type::regtype)::regproc; /* Insert new entry to pathman config */ INSERT INTO @extschema@.pathman_config (partrel, attname, parttype) VALUES (parent_relid, attribute, 1); /* Create partitions and update pg_pathman configuration */ FOR partnum IN 0..partitions_count-1 LOOP v_child_relname := format('%s.%s', quote_ident(v_plain_schema), quote_ident(v_plain_relname || '_' || partnum)); EXECUTE format('CREATE TABLE %1$s (LIKE %2$s INCLUDING ALL) INHERITS (%2$s)', v_child_relname, parent_relid::TEXT); EXECUTE format('ALTER TABLE %s ADD CONSTRAINT %s CHECK (@extschema@.get_hash_part_idx(%s(%s), %s) = %s)', v_child_relname, @extschema@.build_check_constraint_name(v_child_relname::regclass, attribute), v_hashfunc, attribute, partitions_count, partnum); END LOOP; /* Notify backend about changes */ PERFORM @extschema@.on_create_partitions(parent_relid); /* Copy data */ IF partition_data = true THEN PERFORM @extschema@.disable_parent(parent_relid); PERFORM @extschema@.partition_data(parent_relid); ELSE PERFORM @extschema@.enable_parent(parent_relid); END IF; RETURN partitions_count; END $$ LANGUAGE plpgsql SET client_min_messages = WARNING; /* * Creates an update trigger */ CREATE OR REPLACE FUNCTION @extschema@.create_hash_update_trigger( parent_relid REGCLASS) RETURNS TEXT AS $$ DECLARE func TEXT := 'CREATE OR REPLACE FUNCTION %1$s() RETURNS TRIGGER AS $body$ DECLARE old_idx INTEGER; /* partition indices */ new_idx INTEGER; BEGIN old_idx := @extschema@.get_hash_part_idx(%9$s(OLD.%2$s), %3$s); new_idx := @extschema@.get_hash_part_idx(%9$s(NEW.%2$s), %3$s); IF old_idx = new_idx THEN RETURN NEW; END IF; EXECUTE format(''DELETE FROM %8$s WHERE %4$s'', old_idx) USING %5$s; EXECUTE format(''INSERT INTO %8$s VALUES (%6$s)'', new_idx) USING %7$s; RETURN NULL; END $body$ LANGUAGE plpgsql'; trigger TEXT := 'CREATE TRIGGER %s BEFORE UPDATE ON %s FOR EACH ROW EXECUTE PROCEDURE %s()'; att_names TEXT; old_fields TEXT; new_fields TEXT; att_val_fmt TEXT; att_fmt TEXT; attr TEXT; plain_schema TEXT; plain_relname TEXT; child_relname_format TEXT; funcname TEXT; triggername TEXT; atttype TEXT; hashfunc TEXT; partitions_count INTEGER; BEGIN attr := attname FROM @extschema@.pathman_config WHERE partrel = parent_relid; IF attr IS NULL THEN RAISE EXCEPTION 'Table "%" is not partitioned', parent_relid::TEXT; END IF; SELECT string_agg(attname, ', '), string_agg('OLD.' || attname, ', '), string_agg('NEW.' || attname, ', '), string_agg('CASE WHEN NOT $' || attnum || ' IS NULL THEN ' || attname || ' = $' || attnum || ' ' || 'ELSE ' || attname || ' IS NULL END', ' AND '), string_agg('$' || attnum, ', ') FROM pg_catalog.pg_attribute WHERE attrelid = parent_relid AND attnum > 0 INTO att_names, old_fields, new_fields, att_val_fmt, att_fmt; partitions_count := COUNT(*) FROM pg_catalog.pg_inherits WHERE inhparent = parent_relid::oid; /* Build trigger & trigger function's names */ funcname := @extschema@.build_update_trigger_func_name(parent_relid); triggername := @extschema@.build_update_trigger_name(parent_relid); /* Build partition name template */ SELECT * INTO plain_schema, plain_relname FROM @extschema@.get_plain_schema_and_relname(parent_relid); child_relname_format := quote_ident(plain_schema) || '.' || quote_ident(plain_relname || '_%s'); /* Fetch base hash function for atttype */ atttype := @extschema@.get_attribute_type_name(parent_relid, attr); hashfunc := @extschema@.get_type_hash_func(atttype::regtype)::regproc; /* Format function definition and execute it */ func := format(func, funcname, attr, partitions_count, att_val_fmt, old_fields, att_fmt, new_fields, child_relname_format, hashfunc); EXECUTE func; /* Create trigger on every partition */ FOR num IN 0..partitions_count-1 LOOP EXECUTE format(trigger, triggername, format(child_relname_format, num), funcname); END LOOP; return funcname; END $$ LANGUAGE plpgsql; /* * Returns hash function OID for specified type */ CREATE OR REPLACE FUNCTION @extschema@.get_type_hash_func(REGTYPE) RETURNS OID AS 'pg_pathman', 'get_type_hash_func' LANGUAGE C STRICT; /* * Calculates hash for integer value */ CREATE OR REPLACE FUNCTION @extschema@.get_hash_part_idx(INTEGER, INTEGER) RETURNS INTEGER AS 'pg_pathman', 'get_hash_part_idx' LANGUAGE C STRICT;