Загрузить файлы в «/»
This commit is contained in:
+728
@@ -0,0 +1,728 @@
|
|||||||
|
-- Создание схемы для модуля вычисляемых полей
|
||||||
|
CREATE SCHEMA IF NOT EXISTS computed_fields_system;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 1: СЛУЖЕБНЫЕ ТАБЛИЦЫ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Таблица конфигурации вычисляемых полей (реестр правил)
|
||||||
|
CREATE TABLE IF NOT EXISTS computed_fields_system.computed_fields_config (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
table_schema VARCHAR(63) NOT NULL DEFAULT 'public',
|
||||||
|
table_name VARCHAR(63) NOT NULL,
|
||||||
|
column_name VARCHAR(63) NOT NULL,
|
||||||
|
column_type VARCHAR(50) NOT NULL,
|
||||||
|
expression TEXT NOT NULL,
|
||||||
|
implementation_type VARCHAR(10) NOT NULL DEFAULT 'AUTO'
|
||||||
|
CHECK (implementation_type IN ('AUTO', 'NATIVE', 'TRIGGER')),
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
description TEXT,
|
||||||
|
UNIQUE (table_schema, table_name, column_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE computed_fields_system.computed_fields_config IS
|
||||||
|
'Реестр конфигурации вычисляемых полей для автоматической генерации';
|
||||||
|
|
||||||
|
-- Таблица логов применения правил
|
||||||
|
CREATE TABLE IF NOT EXISTS computed_fields_system.application_log (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
config_id INTEGER REFERENCES computed_fields_system.computed_fields_config(id),
|
||||||
|
action VARCHAR(20) NOT NULL,
|
||||||
|
implementation_method VARCHAR(10) NOT NULL,
|
||||||
|
status VARCHAR(10) NOT NULL CHECK (status IN ('SUCCESS', 'ERROR')),
|
||||||
|
error_message TEXT,
|
||||||
|
executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица результатов валидации
|
||||||
|
CREATE TABLE IF NOT EXISTS computed_fields_system.validation_results (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
table_schema VARCHAR(63) NOT NULL,
|
||||||
|
table_name VARCHAR(63) NOT NULL,
|
||||||
|
column_name VARCHAR(63) NOT NULL,
|
||||||
|
validation_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
total_rows BIGINT NOT NULL,
|
||||||
|
invalid_rows BIGINT NOT NULL,
|
||||||
|
invalid_row_ids TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 2: ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Функция для определения версии PostgreSQL
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.get_pg_version_num()
|
||||||
|
RETURNS INTEGER
|
||||||
|
LANGUAGE plpgsql STABLE
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_version INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT current_setting('server_version_num')::INTEGER INTO v_version;
|
||||||
|
RETURN v_version;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Функция проверки поддержки GENERATED COLUMNS
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.supports_generated_columns()
|
||||||
|
RETURNS BOOLEAN
|
||||||
|
LANGUAGE plpgsql STABLE
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
-- PostgreSQL 12+ (версия >= 120000) поддерживает GENERATED COLUMNS
|
||||||
|
RETURN computed_fields_system.get_pg_version_num() >= 120000;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Функция проверки существования столбца
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.column_exists(
|
||||||
|
p_schema VARCHAR,
|
||||||
|
p_table VARCHAR,
|
||||||
|
p_column VARCHAR
|
||||||
|
)
|
||||||
|
RETURNS BOOLEAN
|
||||||
|
LANGUAGE plpgsql STABLE
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_exists BOOLEAN;
|
||||||
|
BEGIN
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = p_schema
|
||||||
|
AND table_name = p_table
|
||||||
|
AND column_name = p_column
|
||||||
|
) INTO v_exists;
|
||||||
|
|
||||||
|
RETURN v_exists;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 3: ОСНОВНЫЕ ПРОЦЕДУРЫ ГЕНЕРАЦИИ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Процедура создания вычисляемого поля через GENERATED COLUMN (нативный метод)
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.create_generated_column(
|
||||||
|
p_schema VARCHAR,
|
||||||
|
p_table VARCHAR,
|
||||||
|
p_column VARCHAR,
|
||||||
|
p_type VARCHAR,
|
||||||
|
p_expression TEXT
|
||||||
|
)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_sql TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- Формирование DDL-команды для GENERATED ALWAYS AS
|
||||||
|
v_sql := format(
|
||||||
|
'ALTER TABLE %I.%I ADD COLUMN %I %s GENERATED ALWAYS AS (%s) STORED',
|
||||||
|
p_schema, p_table, p_column, p_type, p_expression
|
||||||
|
);
|
||||||
|
|
||||||
|
RAISE NOTICE 'Executing: %', v_sql;
|
||||||
|
EXECUTE v_sql;
|
||||||
|
|
||||||
|
RAISE NOTICE 'Generated column %.%.% created successfully',
|
||||||
|
p_schema, p_table, p_column;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Процедура создания вычисляемого поля через TRIGGER (триггерный метод)
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.create_trigger_computed_field(
|
||||||
|
p_schema VARCHAR,
|
||||||
|
p_table VARCHAR,
|
||||||
|
p_column VARCHAR,
|
||||||
|
p_type VARCHAR,
|
||||||
|
p_expression TEXT
|
||||||
|
)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_function_name VARCHAR;
|
||||||
|
v_trigger_name VARCHAR;
|
||||||
|
v_sql TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- Генерация уникальных имен для функции и триггера
|
||||||
|
v_function_name := format('trg_fn_%s_%s_compute', p_table, p_column);
|
||||||
|
v_trigger_name := format('trg_%s_%s_compute', p_table, p_column);
|
||||||
|
|
||||||
|
-- 1. Добавляем столбец (если не существует)
|
||||||
|
IF NOT computed_fields_system.column_exists(p_schema, p_table, p_column) THEN
|
||||||
|
v_sql := format(
|
||||||
|
'ALTER TABLE %I.%I ADD COLUMN %I %s',
|
||||||
|
p_schema, p_table, p_column, p_type
|
||||||
|
);
|
||||||
|
EXECUTE v_sql;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 2. Создаем триггерную функцию
|
||||||
|
v_sql := format($FUNC$
|
||||||
|
CREATE OR REPLACE FUNCTION %I.%I()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $TRG$
|
||||||
|
BEGIN
|
||||||
|
-- Вычисление значения с обработкой NULL
|
||||||
|
BEGIN
|
||||||
|
NEW.%I := (%s)::%s;
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
-- Логирование ошибки, но не прерываем транзакцию
|
||||||
|
RAISE WARNING 'Error computing field %%.%%: %%',
|
||||||
|
TG_TABLE_SCHEMA, TG_TABLE_NAME, SQLERRM;
|
||||||
|
NEW.%I := NULL;
|
||||||
|
END;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$TRG$;
|
||||||
|
$FUNC$,
|
||||||
|
p_schema, v_function_name,
|
||||||
|
p_column, p_expression, p_type,
|
||||||
|
p_column
|
||||||
|
);
|
||||||
|
|
||||||
|
EXECUTE v_sql;
|
||||||
|
|
||||||
|
-- 3. Создаем триггер
|
||||||
|
v_sql := format($TRIG$
|
||||||
|
DROP TRIGGER IF EXISTS %I ON %I.%I;
|
||||||
|
CREATE TRIGGER %I
|
||||||
|
BEFORE INSERT OR UPDATE ON %I.%I
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION %I.%I();
|
||||||
|
$TRIG$,
|
||||||
|
v_trigger_name, p_schema, p_table,
|
||||||
|
v_trigger_name,
|
||||||
|
p_schema, p_table,
|
||||||
|
p_schema, v_function_name
|
||||||
|
);
|
||||||
|
|
||||||
|
EXECUTE v_sql;
|
||||||
|
|
||||||
|
RAISE NOTICE 'Trigger-based computed field %.%.% created successfully',
|
||||||
|
p_schema, p_table, p_column;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 4: ОРКЕСТРАТОР - ПРИМЕНЕНИЕ ПРАВИЛ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.apply_computed_fields_rules()
|
||||||
|
RETURNS TABLE (
|
||||||
|
config_id INTEGER,
|
||||||
|
table_full_name TEXT,
|
||||||
|
column_name TEXT,
|
||||||
|
method_used VARCHAR,
|
||||||
|
status TEXT
|
||||||
|
)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_config RECORD;
|
||||||
|
v_implementation_method VARCHAR(10);
|
||||||
|
v_supports_native BOOLEAN;
|
||||||
|
v_error_msg TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- Проверка поддержки нативных GENERATED COLUMNS
|
||||||
|
v_supports_native := computed_fields_system.supports_generated_columns();
|
||||||
|
|
||||||
|
RAISE NOTICE 'PostgreSQL version: %, Native GENERATED COLUMNS support: %',
|
||||||
|
computed_fields_system.get_pg_version_num(), v_supports_native;
|
||||||
|
|
||||||
|
-- Обработка каждого правила из конфигурации
|
||||||
|
FOR v_config IN
|
||||||
|
SELECT c.id, c.table_schema, c.table_name, c.column_name,
|
||||||
|
c.column_type, c.expression, c.implementation_type
|
||||||
|
FROM computed_fields_system.computed_fields_config c
|
||||||
|
WHERE c.is_active = TRUE
|
||||||
|
ORDER BY c.id
|
||||||
|
LOOP
|
||||||
|
BEGIN
|
||||||
|
-- Определение метода реализации
|
||||||
|
IF v_config.implementation_type = 'AUTO' THEN
|
||||||
|
v_implementation_method := CASE
|
||||||
|
WHEN v_supports_native THEN 'NATIVE'
|
||||||
|
ELSE 'TRIGGER'
|
||||||
|
END;
|
||||||
|
ELSE
|
||||||
|
v_implementation_method := v_config.implementation_type;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Проверка возможности использования выбранного метода
|
||||||
|
IF v_implementation_method = 'NATIVE' AND NOT v_supports_native THEN
|
||||||
|
RAISE EXCEPTION 'NATIVE method requested but PostgreSQL version < 12';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Проверка существования столбца
|
||||||
|
IF computed_fields_system.column_exists(
|
||||||
|
v_config.table_schema,
|
||||||
|
v_config.table_name,
|
||||||
|
v_config.column_name
|
||||||
|
) THEN
|
||||||
|
RAISE NOTICE 'Column %.%.% already exists, skipping',
|
||||||
|
v_config.table_schema, v_config.table_name, v_config.column_name;
|
||||||
|
CONTINUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Применение выбранного метода
|
||||||
|
IF v_implementation_method = 'NATIVE' THEN
|
||||||
|
PERFORM computed_fields_system.create_generated_column(
|
||||||
|
v_config.table_schema,
|
||||||
|
v_config.table_name,
|
||||||
|
v_config.column_name,
|
||||||
|
v_config.column_type,
|
||||||
|
v_config.expression
|
||||||
|
);
|
||||||
|
ELSE
|
||||||
|
PERFORM computed_fields_system.create_trigger_computed_field(
|
||||||
|
v_config.table_schema,
|
||||||
|
v_config.table_name,
|
||||||
|
v_config.column_name,
|
||||||
|
v_config.column_type,
|
||||||
|
v_config.expression
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Логирование успешного применения
|
||||||
|
INSERT INTO computed_fields_system.application_log
|
||||||
|
(config_id, action, implementation_method, status)
|
||||||
|
VALUES
|
||||||
|
(v_config.id, 'CREATE', v_implementation_method, 'SUCCESS');
|
||||||
|
|
||||||
|
-- Возврат результата
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
v_config.id,
|
||||||
|
v_config.table_schema || '.' || v_config.table_name,
|
||||||
|
v_config.column_name,
|
||||||
|
v_implementation_method,
|
||||||
|
'SUCCESS'::TEXT;
|
||||||
|
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
v_error_msg := SQLERRM;
|
||||||
|
|
||||||
|
-- Логирование ошибки
|
||||||
|
INSERT INTO computed_fields_system.application_log
|
||||||
|
(config_id, action, implementation_method, status, error_message)
|
||||||
|
VALUES
|
||||||
|
(v_config.id, 'CREATE', v_implementation_method, 'ERROR', v_error_msg);
|
||||||
|
|
||||||
|
RAISE WARNING 'Failed to create computed field %.%.%: %',
|
||||||
|
v_config.table_schema, v_config.table_name,
|
||||||
|
v_config.column_name, v_error_msg;
|
||||||
|
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
v_config.id,
|
||||||
|
v_config.table_schema || '.' || v_config.table_name,
|
||||||
|
v_config.column_name,
|
||||||
|
v_implementation_method,
|
||||||
|
'ERROR: ' || v_error_msg;
|
||||||
|
END;
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 5: ВАЛИДАЦИЯ ЦЕЛОСТНОСТИ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.validate_computed_fields(
|
||||||
|
p_table_schema VARCHAR DEFAULT 'public',
|
||||||
|
p_table_name VARCHAR DEFAULT NULL
|
||||||
|
)
|
||||||
|
RETURNS TABLE (
|
||||||
|
table_name TEXT,
|
||||||
|
column_name TEXT,
|
||||||
|
total_rows BIGINT,
|
||||||
|
invalid_rows BIGINT,
|
||||||
|
is_valid BOOLEAN
|
||||||
|
)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_config RECORD;
|
||||||
|
v_sql TEXT;
|
||||||
|
v_total_rows BIGINT;
|
||||||
|
v_invalid_rows BIGINT;
|
||||||
|
v_invalid_ids TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- Обработка каждого активного правила
|
||||||
|
FOR v_config IN
|
||||||
|
SELECT c.table_schema, c.table_name, c.column_name,
|
||||||
|
c.expression, c.column_type
|
||||||
|
FROM computed_fields_system.computed_fields_config c
|
||||||
|
WHERE c.is_active = TRUE
|
||||||
|
AND c.table_schema = p_table_schema
|
||||||
|
AND (p_table_name IS NULL OR c.table_name = p_table_name)
|
||||||
|
ORDER BY c.table_name, c.column_name
|
||||||
|
LOOP
|
||||||
|
-- Проверка существования столбца
|
||||||
|
IF NOT computed_fields_system.column_exists(
|
||||||
|
v_config.table_schema,
|
||||||
|
v_config.table_name,
|
||||||
|
v_config.column_name
|
||||||
|
) THEN
|
||||||
|
RAISE WARNING 'Column %.%.% does not exist, skipping validation',
|
||||||
|
v_config.table_schema, v_config.table_name, v_config.column_name;
|
||||||
|
CONTINUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Подсчет общего количества строк
|
||||||
|
v_sql := format(
|
||||||
|
'SELECT COUNT(*) FROM %I.%I',
|
||||||
|
v_config.table_schema, v_config.table_name
|
||||||
|
);
|
||||||
|
EXECUTE v_sql INTO v_total_rows;
|
||||||
|
|
||||||
|
-- Подсчет строк с расхождениями
|
||||||
|
v_sql := format($VAL$
|
||||||
|
SELECT COUNT(*),
|
||||||
|
STRING_AGG(id::TEXT, ', ')
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN pg_typeof(id) = 'integer'::regtype THEN id::INTEGER
|
||||||
|
WHEN pg_typeof(id) = 'bigint'::regtype THEN id::BIGINT
|
||||||
|
ELSE id::INTEGER
|
||||||
|
END as id
|
||||||
|
FROM %I.%I
|
||||||
|
WHERE %I IS DISTINCT FROM ((%s)::%s)
|
||||||
|
LIMIT 100
|
||||||
|
) subq
|
||||||
|
$VAL$,
|
||||||
|
v_config.table_schema, v_config.table_name,
|
||||||
|
v_config.column_name, v_config.expression, v_config.column_type
|
||||||
|
);
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
EXECUTE v_sql INTO v_invalid_rows, v_invalid_ids;
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
RAISE WARNING 'Validation error for %.%.%: %',
|
||||||
|
v_config.table_schema, v_config.table_name,
|
||||||
|
v_config.column_name, SQLERRM;
|
||||||
|
v_invalid_rows := -1;
|
||||||
|
v_invalid_ids := 'ERROR: ' || SQLERRM;
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- Сохранение результатов валидации
|
||||||
|
INSERT INTO computed_fields_system.validation_results
|
||||||
|
(table_schema, table_name, column_name, total_rows,
|
||||||
|
invalid_rows, invalid_row_ids)
|
||||||
|
VALUES
|
||||||
|
(v_config.table_schema, v_config.table_name, v_config.column_name,
|
||||||
|
v_total_rows, v_invalid_rows, v_invalid_ids);
|
||||||
|
|
||||||
|
-- Возврат результата
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
v_config.table_schema || '.' || v_config.table_name,
|
||||||
|
v_config.column_name,
|
||||||
|
v_total_rows,
|
||||||
|
v_invalid_rows,
|
||||||
|
(v_invalid_rows = 0);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Процедура принудительного пересчета значений
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.force_recompute(
|
||||||
|
p_table_schema VARCHAR,
|
||||||
|
p_table_name VARCHAR,
|
||||||
|
p_column_name VARCHAR
|
||||||
|
)
|
||||||
|
RETURNS BIGINT
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_config RECORD;
|
||||||
|
v_sql TEXT;
|
||||||
|
v_updated_rows BIGINT;
|
||||||
|
BEGIN
|
||||||
|
-- Получение конфигурации поля
|
||||||
|
SELECT c.expression, c.column_type
|
||||||
|
INTO v_config
|
||||||
|
FROM computed_fields_system.computed_fields_config c
|
||||||
|
WHERE c.table_schema = p_table_schema
|
||||||
|
AND c.table_name = p_table_name
|
||||||
|
AND c.column_name = p_column_name
|
||||||
|
AND c.is_active = TRUE;
|
||||||
|
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE EXCEPTION 'Configuration not found for %.%.%',
|
||||||
|
p_table_schema, p_table_name, p_column_name;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Массовое обновление
|
||||||
|
v_sql := format($UPD$
|
||||||
|
UPDATE %I.%I
|
||||||
|
SET %I = (%s)::%s
|
||||||
|
$UPD$,
|
||||||
|
p_table_schema, p_table_name,
|
||||||
|
p_column_name, v_config.expression, v_config.column_type
|
||||||
|
);
|
||||||
|
|
||||||
|
EXECUTE v_sql;
|
||||||
|
GET DIAGNOSTICS v_updated_rows = ROW_COUNT;
|
||||||
|
|
||||||
|
RAISE NOTICE 'Force recompute: % rows updated in %.%.%',
|
||||||
|
v_updated_rows, p_table_schema, p_table_name, p_column_name;
|
||||||
|
|
||||||
|
RETURN v_updated_rows;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 6: ДЕМОНСТРАЦИОННЫЕ ПРИМЕРЫ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Пример 1: Таблица заказов (order_items)
|
||||||
|
CREATE TABLE IF NOT EXISTS order_items (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
order_id INTEGER NOT NULL,
|
||||||
|
product_name VARCHAR(255) NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL CHECK (quantity > 0),
|
||||||
|
unit_price NUMERIC(10,2) NOT NULL CHECK (unit_price >= 0),
|
||||||
|
discount_percent NUMERIC(5,2) DEFAULT 0 CHECK (discount_percent BETWEEN 0 AND 100),
|
||||||
|
tax_rate NUMERIC(5,2) DEFAULT 0 CHECK (tax_rate >= 0),
|
||||||
|
order_date DATE DEFAULT CURRENT_DATE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Пример 2: Таблица комнат (rooms)
|
||||||
|
CREATE TABLE IF NOT EXISTS rooms (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
room_number VARCHAR(10) NOT NULL,
|
||||||
|
length NUMERIC(10,2) NOT NULL CHECK (length > 0),
|
||||||
|
width NUMERIC(10,2) NOT NULL CHECK (width > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Пример 3: Таблица пользователей (users)
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
first_name VARCHAR(100) NOT NULL,
|
||||||
|
last_name VARCHAR(100) NOT NULL,
|
||||||
|
middle_name VARCHAR(100),
|
||||||
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Пример 4: Таблица логов с JSON (users_log)
|
||||||
|
CREATE TABLE IF NOT EXISTS users_log (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
payload JSONB NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 7: КОНФИГУРАЦИЯ ВЫЧИСЛЯЕМЫХ ПОЛЕЙ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Добавление правил для вычисляемых полей
|
||||||
|
INSERT INTO computed_fields_system.computed_fields_config
|
||||||
|
(table_schema, table_name, column_name, column_type, expression,
|
||||||
|
implementation_type, description)
|
||||||
|
VALUES
|
||||||
|
-- Пример 1: Итоговая стоимость позиции заказа
|
||||||
|
('public', 'order_items', 'final_price', 'NUMERIC(10,2)',
|
||||||
|
'(quantity * unit_price) * (1 - discount_percent / 100.0) * (1 + tax_rate / 100.0)',
|
||||||
|
'AUTO',
|
||||||
|
'Финальная стоимость с учетом скидки и налога'),
|
||||||
|
|
||||||
|
-- Пример 2: Площадь комнаты
|
||||||
|
('public', 'rooms', 'total_area', 'NUMERIC(10,2)',
|
||||||
|
'length * width',
|
||||||
|
'AUTO',
|
||||||
|
'Площадь комнаты в квадратных метрах'),
|
||||||
|
|
||||||
|
-- Пример 3: Полное имя пользователя
|
||||||
|
('public', 'users', 'full_name', 'TEXT',
|
||||||
|
'CONCAT_WS('' '', last_name, first_name, middle_name)',
|
||||||
|
'AUTO',
|
||||||
|
'ФИО пользователя для поиска и отображения'),
|
||||||
|
|
||||||
|
-- Пример 4: Поисковая строка (нормализованная)
|
||||||
|
('public', 'users', 'search_string', 'TEXT',
|
||||||
|
'LOWER(CONCAT_WS('' '', last_name, first_name, middle_name, email))',
|
||||||
|
'AUTO',
|
||||||
|
'Нормализованная строка для полнотекстового поиска'),
|
||||||
|
|
||||||
|
-- Пример 5: Email из JSON
|
||||||
|
('public', 'users_log', 'email_extracted', 'TEXT',
|
||||||
|
'payload ->> ''email''',
|
||||||
|
'AUTO',
|
||||||
|
'Email извлеченный из JSON для быстрого доступа')
|
||||||
|
ON CONFLICT (table_schema, table_name, column_name) DO NOTHING;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 8: ТЕСТОВЫЕ ДАННЫЕ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Вставка тестовых данных в order_items
|
||||||
|
INSERT INTO order_items (order_id, product_name, quantity, unit_price, discount_percent, tax_rate)
|
||||||
|
VALUES
|
||||||
|
(1, 'Ноутбук Lenovo ThinkPad', 2, 75000.00, 5.00, 20.00),
|
||||||
|
(1, 'Мышь Logitech MX Master', 2, 7500.00, 10.00, 20.00),
|
||||||
|
(2, 'Монитор Dell UltraSharp', 1, 45000.00, 0.00, 20.00),
|
||||||
|
(3, 'Клавиатура Mechanical', 3, 12000.00, 15.00, 20.00);
|
||||||
|
|
||||||
|
-- Вставка тестовых данных в rooms
|
||||||
|
INSERT INTO rooms (room_number, length, width)
|
||||||
|
VALUES
|
||||||
|
('A101', 5.5, 4.2),
|
||||||
|
('A102', 6.0, 5.0),
|
||||||
|
('B201', 4.5, 3.8),
|
||||||
|
('B202', 7.2, 6.5);
|
||||||
|
|
||||||
|
-- Вставка тестовых данных в users
|
||||||
|
INSERT INTO users (first_name, last_name, middle_name, email)
|
||||||
|
VALUES
|
||||||
|
('Арина', 'Полякова', 'Витальевна', 'polyakova.av@example.com'),
|
||||||
|
('Иван', 'Иванов', 'Петрович', 'ivanov.ip@example.com'),
|
||||||
|
('Мария', 'Сидорова', NULL, 'sidorova.m@example.com'),
|
||||||
|
('Петр', 'Петров', 'Сергеевич', 'petrov.ps@example.com');
|
||||||
|
|
||||||
|
-- Вставка тестовых данных в users_log
|
||||||
|
INSERT INTO users_log (payload)
|
||||||
|
VALUES
|
||||||
|
('{"email": "test1@example.com", "action": "login", "timestamp": "2025-12-15T10:00:00Z"}'::jsonb),
|
||||||
|
('{"email": "test2@example.com", "action": "logout", "timestamp": "2025-12-15T11:00:00Z"}'::jsonb),
|
||||||
|
('{"email": "admin@example.com", "action": "update", "timestamp": "2025-12-15T12:00:00Z"}'::jsonb);
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 9: ПРИМЕНЕНИЕ ПРАВИЛ И ТЕСТИРОВАНИЕ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Применение всех правил
|
||||||
|
SELECT * FROM computed_fields_system.apply_computed_fields_rules();
|
||||||
|
|
||||||
|
-- Проверка созданных полей
|
||||||
|
SELECT
|
||||||
|
table_name,
|
||||||
|
column_name,
|
||||||
|
data_type,
|
||||||
|
is_generated,
|
||||||
|
generation_expression
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND column_name IN ('final_price', 'total_area', 'full_name',
|
||||||
|
'search_string', 'email_extracted')
|
||||||
|
ORDER BY table_name, ordinal_position;
|
||||||
|
|
||||||
|
-- Проверка триггеров
|
||||||
|
SELECT
|
||||||
|
trigger_schema,
|
||||||
|
trigger_name,
|
||||||
|
event_object_table,
|
||||||
|
action_statement
|
||||||
|
FROM information_schema.triggers
|
||||||
|
WHERE trigger_name LIKE 'trg_%_compute'
|
||||||
|
ORDER BY event_object_table;
|
||||||
|
|
||||||
|
-- Валидация целостности всех полей
|
||||||
|
SELECT * FROM computed_fields_system.validate_computed_fields();
|
||||||
|
|
||||||
|
-- Просмотр результатов в таблицах
|
||||||
|
SELECT id, product_name, quantity, unit_price,
|
||||||
|
discount_percent, tax_rate, final_price
|
||||||
|
FROM order_items;
|
||||||
|
|
||||||
|
SELECT id, room_number, length, width, total_area
|
||||||
|
FROM rooms;
|
||||||
|
|
||||||
|
SELECT id, last_name, first_name, middle_name,
|
||||||
|
full_name, search_string
|
||||||
|
FROM users;
|
||||||
|
|
||||||
|
SELECT id, payload, email_extracted
|
||||||
|
FROM users_log;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 10: УТИЛИТЫ ДЛЯ УПРАВЛЕНИЯ
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Просмотр логов применения правил
|
||||||
|
CREATE OR REPLACE VIEW computed_fields_system.v_application_log AS
|
||||||
|
SELECT
|
||||||
|
l.id,
|
||||||
|
l.executed_at,
|
||||||
|
c.table_schema || '.' || c.table_name as table_full_name,
|
||||||
|
c.column_name,
|
||||||
|
l.action,
|
||||||
|
l.implementation_method,
|
||||||
|
l.status,
|
||||||
|
l.error_message
|
||||||
|
FROM computed_fields_system.application_log l
|
||||||
|
JOIN computed_fields_system.computed_fields_config c ON c.id = l.config_id
|
||||||
|
ORDER BY l.executed_at DESC;
|
||||||
|
|
||||||
|
-- Просмотр последних результатов валидации
|
||||||
|
CREATE OR REPLACE VIEW computed_fields_system.v_latest_validation AS
|
||||||
|
SELECT DISTINCT ON (table_schema, table_name, column_name)
|
||||||
|
table_schema || '.' || table_name as table_full_name,
|
||||||
|
column_name,
|
||||||
|
validation_time,
|
||||||
|
total_rows,
|
||||||
|
invalid_rows,
|
||||||
|
CASE
|
||||||
|
WHEN invalid_rows = 0 THEN 'VALID'
|
||||||
|
WHEN invalid_rows > 0 THEN 'INVALID'
|
||||||
|
ELSE 'ERROR'
|
||||||
|
END as validation_status
|
||||||
|
FROM computed_fields_system.validation_results
|
||||||
|
ORDER BY table_schema, table_name, column_name, validation_time DESC;
|
||||||
|
|
||||||
|
-- Процедура деактивации правила
|
||||||
|
CREATE OR REPLACE FUNCTION computed_fields_system.deactivate_rule(
|
||||||
|
p_table_schema VARCHAR,
|
||||||
|
p_table_name VARCHAR,
|
||||||
|
p_column_name VARCHAR
|
||||||
|
)
|
||||||
|
RETURNS VOID
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
UPDATE computed_fields_system.computed_fields_config
|
||||||
|
SET is_active = FALSE,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE table_schema = p_table_schema
|
||||||
|
AND table_name = p_table_name
|
||||||
|
AND column_name = p_column_name;
|
||||||
|
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE EXCEPTION 'Rule not found for %.%.%',
|
||||||
|
p_table_schema, p_table_name, p_column_name;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RAISE NOTICE 'Rule deactivated for %.%.%',
|
||||||
|
p_table_schema, p_table_name, p_column_name;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- РАЗДЕЛ 11: ДОКУМЕНТАЦИЯ И СПРАВКА
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
COMMENT ON SCHEMA computed_fields_system IS
|
||||||
|
'Система автоматической генерации вычисляемых полей в PostgreSQL';
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION computed_fields_system.apply_computed_fields_rules() IS
|
||||||
|
'Оркестратор применения правил: сканирует конфигурацию и создает вычисляемые поля';
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION computed_fields_system.validate_computed_fields(VARCHAR, VARCHAR) IS
|
||||||
|
'Валидация целостности: проверяет соответствие сохраненных значений формулам';
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION computed_fields_system.force_recompute(VARCHAR, VARCHAR, VARCHAR) IS
|
||||||
|
'Принудительный пересчет всех значений вычисляемого поля';
|
||||||
|
|
||||||
|
-- Итоговый отчет
|
||||||
|
SELECT 'Модуль успешно установлен!' as status,
|
||||||
|
computed_fields_system.get_pg_version_num() as pg_version,
|
||||||
|
computed_fields_system.supports_generated_columns() as native_support,
|
||||||
|
(SELECT COUNT(*) FROM computed_fields_system.computed_fields_config WHERE is_active = TRUE) as active_rules;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- КОНЕЦ СКРИПТА
|
||||||
|
-- ============================================================================
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user