Self-modifying SQL — это техника, при которой SQL-запросы не просто выполняют фиксированную операцию, а генерируют, изменяют и выполняют другие SQL-запросы во время работы приложения. Эта концепция может показаться экзотической и даже спорной, но в определённых сценариях она позволяет создать гибкие, адаптивные решения для динамического управления базой данных.

Эта статья предназначена для разработчиков всех уровней: от начинающих, которые хотят понять основы динамического SQL, до продвинутых специалистов, интересующихся нетривиальными приёмами и автоматизацией управления данными.

1. Введение

1.1 Что такое Self-modifying SQL?

Self-modifying SQL (самомодифицирующийся SQL) — это подход, когда SQL-запросы создают или изменяют другие SQL-запросы в ходе своей работы. Другими словами, вместо того чтобы работать с фиксированным набором инструкций, система генерирует новые запросы на основе текущего состояния базы данных, условий или логики приложения.

1.2 Зачем это нужно?

  • Динамическая адаптация: в условиях постоянно меняющихся данных можно автоматически корректировать структуру базы, создавать или обновлять индексы, триггеры и представления без ручного вмешательства.

  • Автоматизация администрирования: Self-modifying SQL позволяет автоматизировать рутинные задачи (например, создание временных таблиц, пересоздание индексов) и реагировать на изменения в данных.

  • Оптимизация производительности: при анализе статистики запросов можно генерировать SQL-код, который улучшает скорость выполнения запросов.

  • Эксперименты и исследовательская работа: тема интересна с точки зрения метапрограммирования в SQL, позволяя исследовать границы возможностей языка.

2. Основы динамического SQL

2.1 Динамический SQL vs. Self-modifying SQL

  • Динамический SQL - это механизм формирования SQL-запросов в коде во время выполнения, например, с помощью EXECUTE или PREPARE. Он позволяет изменять параметры запроса, но не меняет саму структуру базы.

  • Self-modifying SQL не просто создаёт запросы динамически, а изменяет саму структуру БД или генерирует новые SQL-запросы, влияющие на последующие операции.

2.2 Примеры динамического SQL

1. Простой пример динамического SQL в PostgreSQL:

-- Пример использования EXECUTE в PL/pgSQL для динамического выполнения 
DO $$ 
DECLARE 
    table_name TEXT := 'users';
    user_count INTEGER;
BEGIN 
    EXECUTE 'SELECT count(*) FROM ' || quote_ident(table_name)
    INTO user_count;
    RAISE NOTICE 'Number of users: %', user_count;
END $$; 

В этом примере динамически генерируется запрос для подсчета строк в таблице users. Это классический пример динамического SQL, он не включает самоизменение запроса.

2. Динамическое изменение структуры таблицы:

-- Сначала создадим тестовую таблицу dynamic_data
CREATE TABLE dynamic_data (
    id SERIAL PRIMARY KEY,
    data TEXT
);
-- Теперь запускаем код, который автоматически добавит колонку new_column, если её ещё нет
DO $$ 
DECLARE v_column_name TEXT := 'new_column';
BEGIN
    -- Проверяем, есть ли колонка
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.columns 
        WHERE table_name = 'dynamic_data' 
        AND column_name = v_column_name
    ) THEN
        -- Добавляем колонку динамически
	-- Используем %I в format() для безопасного экранирования имён колонок
        EXECUTE format('ALTER TABLE dynamic_data ADD COLUMN %I TEXT;', v_column_name);
        RAISE NOTICE 'Колонка % добавлена!', v_column_name;
    ELSE
        RAISE NOTICE 'Колонка % уже существует!', v_column_name;
    END IF;
END $$;
-- Теперь посмотрим, добавилась ли колонка
SELECT column_name FROM information_schema.columns WHERE table_name = 'dynamic_data';

В этом примере добавляется новый столбец, если он отсутствует.

3. Self-modifying SQL: концепция и пример

3.1 Генерация SQL-запросов на лету

Одним из примеров self-modifying SQL является генерация новых запросов на основе данных, полученных из текущего запроса. Рассмотрим пример, когда требуется создать индексы для всех таблиц в схеме, если они отсутствуют.

Пример: Автоматическое создание индексов

DO $$
DECLARE
    tbl RECORD;
BEGIN
    FOR tbl IN
        SELECT table_name 
        FROM information_schema.tables 
        WHERE table_schema = 'public'
    LOOP
        -- Генерируем и выполняем запрос на создание индекса, если он не существует
        -- Проверяем, содержит ли таблица поле id перед созданием индекса
EXECUTE format(
    'DO $$ BEGIN 
        IF EXISTS (SELECT 1 FROM information_schema.columns 
                   WHERE table_name = %L AND column_name = ''id'') 
        THEN CREATE INDEX IF NOT EXISTS idx_%I_id ON %I (id); 
        END IF; END $$;', 
    tbl.table_name, tbl.table_name, tbl.table_name
);
    END LOOP;
END $$;

В этом примере мы перебираем все таблицы в публичной схеме и для каждой таблицы динамически генерируем SQL-запрос, который создаёт индекс по полю id, если индекс ещё не существует.

3.2 Автоматическое создание триггеров

Еще один пример self-modifying SQL — автоматическая генерация триггеров для всех таблиц, чтобы логировать изменения.

Пример: Генерация триггеров для логирования изменений

DO $$
DECLARE
    tbl RECORD;
BEGIN
    FOR tbl IN
        SELECT table_name 
        FROM information_schema.tables 
        WHERE table_schema = 'public'
    LOOP
        EXECUTE '
            CREATE OR REPLACE FUNCTION log_changes_' || tbl.table_name || '() 
            RETURNS trigger AS $$
            BEGIN
                INSERT INTO change_log(table_name, operation, changed_at)
                VALUES (''' || tbl.table_name || ''', TG_OP, NOW());
                RETURN NEW;
            END;
            $$ LANGUAGE plpgsql;
        ';

        EXECUTE '
            DROP TRIGGER IF EXISTS trg_' || tbl.table_name || '_log ON ' || tbl.table_name || ';
            CREATE TRIGGER trg_' || tbl.table_name || '_log
            AFTER INSERT OR UPDATE OR DELETE ON ' || tbl.table_name || '
            FOR EACH ROW EXECUTE FUNCTION log_changes_' || tbl.table_name || '();
        ';
    END LOOP;
END $$;

В этом примере для каждой таблицы создаётся функция логирования и триггер, который будет вызываться при изменении данных в таблице. Это демонстрирует, как SQL может изменять структуру базы данных на лету.

3.3 Динамическое изменение триггеров

-- Создадим таблицу users и вспомогательную users_log для логирования
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT
);

CREATE TABLE users_log (
    log_id SERIAL PRIMARY KEY,
    user_id INT,
    action TEXT,
    created_at TIMESTAMP DEFAULT now()
);
-- Создаём функцию для логирования
CREATE OR REPLACE FUNCTION log_table_changes() RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO users_log (user_id, action) 
    VALUES (NEW.id, 'INSERTED OR UPDATED');
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Запускаем код, который создаёт триггер
DO $$ 
DECLARE trigger_name TEXT := 'users_log_trigger';
BEGIN
    -- Удаляем старый триггер
    EXECUTE format('DROP TRIGGER IF EXISTS %I ON users;', trigger_name);

    -- Создаём новый триггер
    EXECUTE format('
        CREATE TRIGGER %I 
        AFTER INSERT OR UPDATE 
        ON users 
        FOR EACH ROW 
        EXECUTE FUNCTION log_table_changes();
    ', trigger_name);
    
    RAISE NOTICE 'Триггер % пересоздан!', trigger_name;
END $$;
-- Проверяем - вставляем пользователя
INSERT INTO users (name) VALUES ('PPR');

-- Смотрим логи
SELECT * FROM users_log;

4. Преимущества и риски self-modifying SQL

4.1 Преимущества

  • Гибкость: возможность адаптировать схему базы данных в зависимости от изменения требований.

  • Автоматизация: сокращение ручного труда для администратора БД.

  • Адаптивность: система может реагировать на изменения данных, автоматически оптимизируя запросы и структуру.

4.2 Риски и предостережения

  • Безопасность: динамическое выполнение SQL повышает риск SQL-инъекций, если динамически генерируемые строки не экранируются корректно. Например:

EXECUTE 'SELECT * FROM users WHERE name = ''' || user_input || '''';

Если user_input = 'John' OR 1=1 --, то запрос вернёт всех пользователей.
Используйте quote_literal() и quote_ident() для защиты.

  • Отладка: самомодифицирующийся код сложнее тестировать и отлаживать, поскольку он меняется во время выполнения.

  • Производительность: частое выполнение динамических запросов может добавить накладные расходы, особенно если запросы компилируются заново.

5. Лучшие практики для реализации self-modifying SQL

  • Валидация и экранирование: всегда проверяйте и экранируйте переменные, используемые для генерации SQL-запросов (например, с помощью quote_ident в PostgreSQL).

  • Логирование изменений: записывайте, какие запросы были сгенерированы и выполнены, чтобы в случае ошибок можно было восстановить исходное состояние.

  • Ограничение области применения: используйте self-modifying SQL только для задач, которые действительно требуют динамической адаптации. Для рутинных операций лучше применять стандартные миграции или автоматизированные инструменты администрирования.

  • Тестирование: обязательно тестируйте сценарии использования на тестовых базах данных, прежде чем применять в продакшене.

  • Документация: поскольку самокорректирующийся SQL-код сложен для понимания, документируйте его логику, чтобы другие разработчики могли разобраться в механизмах работы.

6. Заключение

Самомодифицирующийся SQL помогает автоматизировать администрирование, изменять структуру БД и оптимизировать запросы.

Но он усложняет отладку и может создавать уязвимости, если не учитывать безопасность.

Где использовать: в автоматизации, администрировании, исследовательских задачах.
Где не стоит: в критически важных системах без строгой валидации данных

Комментарии (0)


  1. vlad4kr7
    17.09.2025 15:09

    self-modifying SQL

    Этот термин вы сами придумали?

    • Гибкость: возможность адаптировать схему базы данных в зависимости от изменения требований.

    • Автоматизация: сокращение ручного труда для администратора БД.

    • Адаптивность: система может реагировать на изменения данных, автоматически оптимизируя запросы и структуру.

    1. есть решения динамической генерации и приведения схемы базы по YAML описанию - все будет еще проще

    2. сомнительное утверждение, а отлаживать вот это все кто будет?

    3. все тоже

    Где не стоит: в критически важных системах без строгой валидации данных

    ну то-есть, где используется постгрес


  1. Kealkat
    17.09.2025 15:09

    Честно говоря, всё еще не ясна разница между этими терминами. В одном случае использовались динамические запросы для DML, во втором - DDL.


  1. Akina
    17.09.2025 15:09

    Self-modifying SQL (самомодифицирующийся SQL)

    И термин, и его перевод однозначно и безальтернативно утверждают, что речь идёт о запросе, который модифицирует сам себя. Тогда как пояснения автора этому противоречат.

    По дальнейшему тексту - самый обычный динамический SQL, с динамическим построением текста slave-запроса и последующим выполнением.

    Вся показанная разница заключается только в том, что показанная техника выполняет не однократную операцию, а в цикле обрабатывает записи, отданные запросом источником, строя динамически запрос или несколько последовательно выполняемых динамических запросов для каждой записи этого источника, возможно, пропуская некоторые записи по неким условиям. Но использование динамического SQL в итеративной процедуре в принципе не делает его чем-то иным.

    С моей точки зрения, на основании изложенного, между динамическим SQL и заявленным самомодифицирующимся SQL РАЗНИЦЫ НЕТ. Это абсолютно одно и то же.


  1. fiego
    17.09.2025 15:09

    Думал про вирус нас SQL прочитаю... А прочитал про странный алиас к динамическому SQL.