Кулинарная книга SQL-запросов для DuckDB CPG

Практическая подборка SQL-запросов для типовых задач анализа кода на DuckDB CPG.

Содержание

  1. Запросы к методам
  2. Анализ вызовов
  3. Управление потоком выполнения
  4. Поток данных
  5. Анализ файлов
  6. Шаблоны безопасности
  7. Качество кода
  8. Статистика
  9. Предвычисленные метрики (v6.0)
  10. Расширенные шаблоны
  11. Анализ ООП
  12. Ссылки и вложенность
  13. Результаты статического анализа
  14. Анализ импортов
  15. Соединения по идентификаторам
  16. Предварительные требования для PGQ
  17. Графовые запросы на SQL/PGQ
  18. Советы по использованию
  19. Заметки о производительности
  20. Дальнейшие действия
  21. Смотрите также

Запросы к методам

Найти метод по точному имени

SELECT id, name, full_name, filename, line_number, signature
FROM nodes_method
WHERE name = 'authenticate'
LIMIT 10;

Найти методы по шаблону

SELECT id, name, full_name, filename, line_number
FROM nodes_method
WHERE name LIKE '%process%'
ORDER BY name
LIMIT 50;

Методы по шаблону сигнатуры

SELECT name, full_name, signature, filename
FROM nodes_method
WHERE signature LIKE '%char*%'  -- Методы, принимающие параметр char*
ORDER BY name
LIMIT 100;

Внешние и внутренние методы

-- Внешние методы (из библиотек)
SELECT name, full_name, COUNT(*) as count
FROM nodes_method
WHERE is_external = true
GROUP BY name, full_name
ORDER BY count DESC
LIMIT 20;

-- Внутренние методы (ваш код)
SELECT filename, COUNT(*) as method_count
FROM nodes_method
WHERE is_external = false
GROUP BY filename
ORDER BY method_count DESC
LIMIT 20;

Методы по количеству строк

SELECT
    name,
    filename,
    line_number,
    line_number_end,
    (line_number_end - line_number) as lines_of_code
FROM nodes_method
WHERE line_number_end IS NOT NULL
  AND (line_number_end - line_number) > 50  -- Длинные методы
ORDER BY lines_of_code DESC
LIMIT 20;

Анализ вызовов

Прямые вызываемые функции (Какие функции вызывает X?)

SELECT DISTINCT
    callee.name,
    callee.full_name,
    callee.filename,
    callee.line_number
FROM edges_call ec
JOIN nodes_call c ON ec.src = c.id
JOIN nodes_method caller ON c.containing_method_id = caller.id
JOIN nodes_method callee ON ec.dst = callee.id
WHERE caller.name = 'main'
ORDER BY callee.name
LIMIT 100;

Прямые вызывающие функции (Кто вызывает X?)

SELECT DISTINCT
    caller.name,
    caller.full_name,
    caller.filename,
    caller.line_number
FROM edges_call ec
JOIN nodes_call c ON ec.src = c.id
JOIN nodes_method callee ON ec.dst = callee.id
JOIN nodes_method caller ON c.containing_method_id = caller.id
WHERE callee.name = 'malloc'
ORDER BY caller.name
LIMIT 100;

Цепочка вызовов (транзитивные вызываемые функции)

WITH RECURSIVE call_chain AS (
    -- Базовый случай: прямые вызовы из начальной функции
    SELECT
        ec.dst as method_id,
        1 as depth,
        m_start.name as start_method
    FROM edges_call ec
    JOIN nodes_call c ON ec.src = c.id
    JOIN nodes_method m_start ON c.containing_method_id = m_start.id
    WHERE m_start.name = 'main'

    UNION ALL

    -- Рекурсивный случай: продолжаем цепочку
    SELECT
        ec2.dst,
        cc.depth + 1,
        cc.start_method
    FROM edges_call ec2
    JOIN nodes_call c2 ON ec2.src = c2.id
    JOIN nodes_method m2 ON c2.containing_method_id = m2.id
    JOIN call_chain cc ON m2.id = cc.method_id
    WHERE cc.depth < 5
)
SELECT DISTINCT
    m.name,
    m.full_name,
    MIN(cc.depth) as min_depth
FROM call_chain cc
JOIN nodes_method m ON cc.method_id = m.id
GROUP BY m.id, m.name, m.full_name
ORDER BY min_depth, m.name
LIMIT 100;

Наиболее активные вызывающие функции (функции, совершающие наибольшее число вызовов)

SELECT
    m.name,
    m.full_name,
    m.filename,
    COUNT(DISTINCT c.id) as outgoing_calls
FROM nodes_method m
LEFT JOIN nodes_call c ON c.containing_method_id = m.id
GROUP BY m.id, m.name, m.full_name, m.filename
ORDER BY outgoing_calls DESC
LIMIT 20;

Наиболее часто вызываемые функции

SELECT
    m.name,
    m.full_name,
    m.filename,
    COUNT(ec.src) as incoming_calls
FROM nodes_method m
LEFT JOIN edges_call ec ON m.id = ec.dst
GROUP BY m.id, m.name, m.full_name, m.filename
ORDER BY incoming_calls DESC
LIMIT 20;

Пары вызовов (частые шаблоны «вызывающий — вызываемый»)

SELECT
    caller.name as caller,
    callee.name as callee,
    COUNT(*) as frequency
FROM edges_call ec
JOIN nodes_call c ON ec.src = c.id
JOIN nodes_method caller ON c.containing_method_id = caller.id
JOIN nodes_method callee ON ec.dst = callee.id
GROUP BY caller.name, callee.name
ORDER BY frequency DESC
LIMIT 50;

Листовые функции (функции без исходящих вызовов)

SELECT
    m.name,
    m.full_name,
    m.filename
FROM nodes_method m
WHERE NOT EXISTS (
    SELECT 1
    FROM nodes_call c
    WHERE c.containing_method_id = m.id
)
AND m.is_external = false
ORDER BY m.name
LIMIT 100;

Корневые функции (функции, которые никто не вызывает)

SELECT
    m.name,
    m.full_name,
    m.filename,
    m.line_number
FROM nodes_method m
WHERE NOT EXISTS (
    SELECT 1
    FROM edges_call ec
    WHERE ec.dst = m.id
)
AND m.is_external = false
ORDER BY m.filename, m.line_number
LIMIT 100;

Управление потоком выполнения

Методы с ветвлениями

-- Найти методы, содержащие структуры IF/SWITCH, через AST
SELECT DISTINCT
    m.name,
    m.full_name,
    m.filename,
    COUNT(DISTINCT cs.id) as branch_count
FROM nodes_method m
JOIN edges_ast ast ON m.id = ast.src
JOIN nodes_control_structure cs ON ast.dst = cs.id
WHERE cs.control_structure_type IN ('IF', 'SWITCH')
GROUP BY m.id, m.name, m.full_name, m.filename
ORDER BY branch_count DESC
LIMIT 20;

Методы с циклами

-- Найти методы, содержащие структуры циклов, через AST
SELECT DISTINCT
    m.name,
    m.full_name,
    m.filename,
    COUNT(DISTINCT cs.id) as loop_count
FROM nodes_method m
JOIN edges_ast ast ON m.id = ast.src
JOIN nodes_control_structure cs ON ast.dst = cs.id
WHERE cs.control_structure_type IN ('WHILE', 'FOR', 'DO')
GROUP BY m.id, m.name, m.full_name, m.filename
ORDER BY loop_count DESC
LIMIT 20;

Приближённая оценка цикломатической сложности

-- Оценка сложности по количеству управляющих структур
SELECT
    m.name,
    m.filename,
    1 + COUNT(DISTINCT cs.id) as approx_complexity
FROM nodes_method m
LEFT JOIN edges_ast ast ON m.id = ast.src
LEFT JOIN nodes_control_structure cs ON ast.dst = cs.id
WHERE m.is_external = false
GROUP BY m.id, m.name, m.filename
ORDER BY approx_complexity DESC
LIMIT 30;

Обход графа потока управления (SQL/PGQ)

-- Следование по рёбрам CFG с использованием графа свойств
FROM GRAPH_TABLE(cpg
    MATCH (start:METHOD)-[:CFG*1..10]->(node:CPG_NODE)
    WHERE start.name = 'process_request'
    COLUMNS (
        start.name AS method_name,
        node.id AS node_id,
        node.node_type
    )
)
LIMIT 100;

Поток данных

Пути потока переменных

WITH RECURSIVE var_flow AS (
    -- Базовый случай: начальные определения
    SELECT src, dst, variable, 1 as depth
    FROM edges_reaching_def
    WHERE variable = 'password'

    UNION ALL

    -- Рекурсивный случай: продолжение потока
    SELECT erd.src, erd.dst, erd.variable, vf.depth + 1
    FROM edges_reaching_def erd
    JOIN var_flow vf ON erd.src = vf.dst
    WHERE vf.depth < 10
)
SELECT
    variable,
    COUNT(DISTINCT src) as definition_points,
    COUNT(DISTINCT dst) as use_points,
    MAX(depth) as max_depth
FROM var_flow
GROUP BY variable;

Загрязнённые параметры

-- Методы, получающие загрязнённые входные данные
SELECT DISTINCT
    m.name,
    m.full_name,
    p.name as parameter_name,
    p.type_full_name as parameter_type
FROM nodes_method m
JOIN nodes_param p ON p.method_id = m.id
WHERE p.name IN ('userInput', 'request', 'input', 'data')
ORDER BY m.name
LIMIT 50;

Анализ файлов

Методы по файлам

SELECT
    filename,
    COUNT(*) as method_count,
    SUM(CASE WHEN is_external THEN 1 ELSE 0 END) as external_methods,
    SUM(CASE WHEN is_external THEN 0 ELSE 1 END) as internal_methods
FROM nodes_method
GROUP BY filename
ORDER BY method_count DESC
LIMIT 30;

Файлы по активности вызовов

SELECT
    m.filename,
    COUNT(DISTINCT c.id) as total_calls,
    COUNT(DISTINCT c.name) as unique_calls
FROM nodes_method m
JOIN nodes_call c ON c.containing_method_id = m.id
GROUP BY m.filename
ORDER BY total_calls DESC
LIMIT 20;

Вызовы между файлами

SELECT
    caller.filename as from_file,
    callee.filename as to_file,
    COUNT(*) as call_count
FROM edges_call ec
JOIN nodes_call c ON ec.src = c.id
JOIN nodes_method caller ON c.containing_method_id = caller.id
JOIN nodes_method callee ON ec.dst = callee.id
WHERE caller.filename != callee.filename
GROUP BY caller.filename, callee.filename
ORDER BY call_count DESC
LIMIT 50;

Шаблоны безопасности

Проблемы управления памятью

-- Методы, которые вызывают malloc, но не вызывают free
SELECT DISTINCT m.name, m.full_name
FROM nodes_method m
WHERE EXISTS (
    SELECT 1 FROM nodes_call c
    WHERE c.containing_method_id = m.id
      AND c.name = 'malloc'
)
AND NOT EXISTS (
    SELECT 1 FROM nodes_call c
    WHERE c.containing_method_id = m.id
      AND c.name = 'free'
)
LIMIT 50;

Опасные вызовы функций

SELECT
    m.name as caller,
    m.filename,
    c.name as dangerous_function,
    m.line_number
FROM nodes_call c
JOIN nodes_method m ON c.containing_method_id = m.id
WHERE c.name IN ('strcpy', 'sprintf', 'gets', 'system', 'eval')
ORDER BY m.filename, m.line_number
LIMIT 100;

Кандидаты на SQL-инъекцию

SELECT DISTINCT
    m.name,
    m.full_name,
    m.filename
FROM nodes_method m
WHERE EXISTS (
    -- Есть вызовы, связанные с SQL
    SELECT 1 FROM nodes_call c
    WHERE c.containing_method_id = m.id
      AND (c.name LIKE '%query%' OR c.name LIKE '%execute%')
)
AND EXISTS (
    -- Получает пользовательский ввод
    SELECT 1 FROM nodes_param p
    WHERE p.method_id = m.id
      AND (p.name LIKE '%input%' OR p.name LIKE '%request%')
)
LIMIT 50;

Кандидаты на обход аутентификации

SELECT
    m.name,
    m.full_name,
    m.filename
FROM nodes_method m
WHERE m.name LIKE '%auth%'
  OR m.name LIKE '%login%'
  OR m.name LIKE '%verify%'
ORDER BY m.name
LIMIT 100;

Качество кода

Богатые методы (слишком длинные)

SELECT
    name,
    filename,
    line_number,
    line_number_end,
    (line_number_end - line_number) as loc
FROM nodes_method
WHERE line_number_end IS NOT NULL
  AND (line_number_end - line_number) > 100
ORDER BY loc DESC
LIMIT 20;

Высокая исходящая связность (слишком много вызовов)

SELECT
    m.name,
    m.filename,
    COUNT(DISTINCT c.id) as fan_out
FROM nodes_method m
JOIN nodes_call c ON c.containing_method_id = m.id
GROUP BY m.id, m.name, m.filename
HAVING COUNT(DISTINCT c.id) > 10
ORDER BY fan_out DESC
LIMIT 20;

Высокая входящая связность (слишком много вызывающих)

SELECT
    m.name,
    m.filename,
    COUNT(DISTINCT ec.src) as fan_in
FROM nodes_method m
JOIN edges_call ec ON m.id = ec.dst
GROUP BY m.id, m.name, m.filename
HAVING COUNT(DISTINCT ec.src) > 10
ORDER BY fan_in DESC
LIMIT 20;

Неиспользуемые методы (потенциальный мёртвый код)

SELECT
    m.name,
    m.full_name,
    m.filename
FROM nodes_method m
WHERE m.is_external = false
  AND NOT EXISTS (
      SELECT 1 FROM edges_call ec WHERE ec.dst = m.id
  )
  AND m.name != 'main'
  AND m.name NOT LIKE 'test%'  -- Исключаем точки входа
ORDER BY m.filename, m.name
LIMIT 100;

Статистика

Обзор CPG

SELECT
    (SELECT COUNT(*) FROM nodes_method) as total_methods,
    (SELECT COUNT(*) FROM nodes_method WHERE is_external = false) as internal_methods,
    (SELECT COUNT(*) FROM nodes_call) as total_calls,
    (SELECT COUNT(*) FROM edges_call) as call_edges,
    (SELECT COUNT(DISTINCT filename) FROM nodes_method) as file_count;

Распределение вызовов

SELECT
    call_count_bucket,
    COUNT(*) as methods_in_bucket
FROM (
    SELECT
        m.id,
        CASE
            WHEN call_count = 0 THEN '0 вызовов'
            WHEN call_count <= 5 THEN '1-5 вызовов'
            WHEN call_count <= 10 THEN '6-10 вызовов'
            WHEN call_count <= 20 THEN '11-20 вызовов'
            ELSE '20+ вызовов'
        END as call_count_bucket
    FROM nodes_method m
    LEFT JOIN (
        SELECT
            c.containing_method_id as method_id,
            COUNT(c.id) as call_count
        FROM nodes_call c
        GROUP BY c.containing_method_id
    ) counts ON m.id = counts.method_id
)
GROUP BY call_count_bucket
ORDER BY
    CASE call_count_bucket
        WHEN '0 вызовов' THEN 1
        WHEN '1-5 вызовов' THEN 2
        WHEN '6-10 вызовов' THEN 3
        WHEN '11-20 вызовов' THEN 4
        ELSE 5
    END;

Распределение языков (по расширению файла)

SELECT
    CASE
        WHEN filename LIKE '%.c' THEN 'C'
        WHEN filename LIKE '%.cpp' OR filename LIKE '%.cc' THEN 'C++'
        WHEN filename LIKE '%.java' THEN 'Java'
        WHEN filename LIKE '%.py' THEN 'Python'
        WHEN filename LIKE '%.js' THEN 'JavaScript'
        ELSE 'Другое'
    END as language,
    COUNT(*) as method_count
FROM nodes_method
WHERE is_external = false
GROUP BY language
ORDER BY method_count DESC;

Сложность сигнатур методов

SELECT
    m.name,
    m.signature,
    LENGTH(m.signature) as sig_length,
    (LENGTH(m.signature) - LENGTH(REPLACE(m.signature, ',', ''))) + 1 as param_count
FROM nodes_method m
WHERE m.signature IS NOT NULL
  AND m.is_external = false
ORDER BY param_count DESC, sig_length DESC
LIMIT 30;

Предвычисленные метрики (v6.0)

Начиная с версии 6.0, GoCPG вычисляет метрики на этапе генерации CPG и сохраняет их непосредственно в nodes_method. Это избавляет от необходимости приближённых вычислений через CTE.

Цикломатическая сложность (предвычисленная)

-- Методы с наивысшей цикломатической сложностью
SELECT
    name,
    full_name,
    filename,
    cyclomatic_complexity,
    line_number,
    line_number_end
FROM nodes_method
WHERE is_external = false
  AND cyclomatic_complexity > 10
ORDER BY cyclomatic_complexity DESC
LIMIT 30;

Связность входящая и исходящая (предвычисленная)

-- Методы с высокой входящей связностью (много вызывающих)
SELECT name, full_name, filename, fan_in
FROM nodes_method
WHERE fan_in > 10
ORDER BY fan_in DESC
LIMIT 20;

-- Методы с высокой исходящей связностью (много вызовов)
SELECT name, full_name, filename, fan_out
FROM nodes_method
WHERE fan_out > 10
ORDER BY fan_out DESC
LIMIT 20;

Точки входа и тестовые методы

-- Все точки входа (main, обработчики HTTP, CLI-команды и т.д.)
SELECT name, full_name, filename, line_number
FROM nodes_method
WHERE is_entry_point = true
ORDER BY filename, line_number;

-- Тестовые методы
SELECT name, full_name, filename
FROM nodes_method
WHERE is_test = true
ORDER BY filename, name;

-- Вложенные функции (замыкания, внутренние функции)
SELECT name, full_name, filename, line_number
FROM nodes_method
WHERE is_nested = true
ORDER BY filename, line_number;

Комбинированный анализ сложности

-- Методы с высокой сложностью И высокой связностью — кандидаты на рефакторинг
SELECT
    name,
    full_name,
    filename,
    cyclomatic_complexity,
    fan_in,
    fan_out,
    (line_number_end - line_number) as loc
FROM nodes_method
WHERE is_external = false
  AND cyclomatic_complexity > 5
  AND (fan_in > 5 OR fan_out > 5)
ORDER BY cyclomatic_complexity * (fan_in + fan_out) DESC
LIMIT 20;

Расширенные шаблоны

Поиск цепочек вызовов методов (A → B → C)

WITH chain AS (
    SELECT
        caller.name as method_a,
        intermediate.name as method_b,
        callee.name as method_c
    FROM edges_call ec1
    JOIN nodes_call c1 ON ec1.src = c1.id
    JOIN nodes_method caller ON c1.containing_method_id = caller.id
    JOIN nodes_method intermediate ON ec1.dst = intermediate.id
    JOIN nodes_call c2 ON c2.containing_method_id = intermediate.id
    JOIN edges_call ec2 ON c2.id = ec2.src
    JOIN nodes_method callee ON ec2.dst = callee.id
    WHERE caller.name = 'main'
)
SELECT DISTINCT method_a, method_b, method_c
FROM chain
LIMIT 100;

Связные компоненты (кластеры методов)

-- Найти методы в одном графе вызовов
WITH RECURSIVE component AS (
    SELECT id, name, id as root
    FROM nodes_method
    WHERE name = 'main'

    UNION

    SELECT m.id, m.name, c.root
    FROM nodes_method m
    JOIN edges_call ec ON m.id = ec.dst OR m.id IN (
        SELECT caller.id FROM nodes_method caller
        JOIN nodes_call nc ON nc.containing_method_id = caller.id
        WHERE nc.id = ec.src
    )
    JOIN component c ON ec.dst = c.id OR ec.src = c.id
)
SELECT
    root,
    COUNT(DISTINCT id) as component_size,
    STRING_AGG(DISTINCT name, ', ') as methods
FROM component
GROUP BY root
ORDER BY component_size DESC
LIMIT 10;

Анализ ООП

Объявления типов

-- Все пользовательские типы (классы, структуры, интерфейсы)
SELECT
    name,
    full_name,
    filename,
    line_number,
    inherits_from_type_full_name
FROM nodes_type_decl
WHERE is_external = false
ORDER BY filename, line_number;

Члены типов

-- Поля и свойства типов
SELECT
    td.name as type_name,
    mb.name as member_name,
    mb.type_full_name as member_type,
    mb.filename,
    mb.line_number
FROM nodes_type_decl td
JOIN edges_ast ea ON td.id = ea.src
JOIN nodes_member mb ON ea.dst = mb.id
ORDER BY td.name, mb.order_index;

Иерархия наследования

-- Прямое наследование через таблицу рёбер
SELECT
    child.name as derived_type,
    child.full_name as derived_full_name,
    parent_id.dst as base_type_id,
    child.filename
FROM nodes_type_decl child
JOIN edges_inherits_from parent_id ON child.id = parent_id.src
ORDER BY child.name;

Типы с наибольшим числом членов

SELECT
    td.name as type_name,
    td.full_name,
    td.filename,
    COUNT(mb.id) as member_count
FROM nodes_type_decl td
JOIN edges_ast ea ON td.id = ea.src
JOIN nodes_member mb ON ea.dst = mb.id
WHERE td.is_external = false
GROUP BY td.id, td.name, td.full_name, td.filename
ORDER BY member_count DESC
LIMIT 20;

Ссылки и вложенность

Ссылки между узлами

-- Все ссылки из данного метода
SELECT
    er.src as source_id,
    er.dst as target_id
FROM edges_ref er
JOIN nodes_method m ON er.src = m.id
WHERE m.name = 'process_request'
LIMIT 100;

Вложенность узлов

-- Узлы, вложенные в данный метод
SELECT
    ec.dst as child_id
FROM edges_contains ec
JOIN nodes_method m ON ec.src = m.id
WHERE m.name = 'authenticate'
LIMIT 100;

-- Файлы и содержащиеся в них методы
SELECT
    f.name as file_name,
    m.name as method_name,
    m.line_number
FROM edges_contains ec
JOIN nodes_file f ON ec.src = f.id
JOIN nodes_method m ON ec.dst = m.id
ORDER BY f.name, m.line_number
LIMIT 100;

Результаты статического анализа

Результаты проверок (findings)

-- Все результаты проверок, сгруппированные по серьёзности
SELECT
    severity,
    category,
    COUNT(*) as finding_count
FROM nodes_finding
GROUP BY severity, category
ORDER BY
    CASE severity
        WHEN 'error' THEN 1
        WHEN 'warning' THEN 2
        WHEN 'info' THEN 3
        WHEN 'hint' THEN 4
        ELSE 5
    END,
    finding_count DESC;

-- Подробный список результатов
SELECT
    title,
    description,
    severity,
    category,
    rule_id,
    confidence,
    status
FROM nodes_finding
WHERE status = 'open'
ORDER BY severity, confidence DESC
LIMIT 50;

Метки (tags)

-- Метки, назначенные узлам CPG
SELECT
    name,
    value,
    external_source,
    confidence,
    COUNT(*) as tag_count
FROM nodes_tag_v2
GROUP BY name, value, external_source, confidence
ORDER BY tag_count DESC
LIMIT 30;

Анализ импортов

Все импорты в проекте

SELECT
    filename,
    imported_entity,
    imported_as,
    is_wildcard,
    line_number
FROM nodes_import
ORDER BY filename, line_number
LIMIT 200;

Наиболее импортируемые модули

SELECT
    imported_entity,
    COUNT(*) as import_count,
    COUNT(DISTINCT filename) as file_count
FROM nodes_import
GROUP BY imported_entity
ORDER BY import_count DESC
LIMIT 30;

Файлы с наибольшим числом импортов

SELECT
    filename,
    COUNT(*) as import_count,
    SUM(CASE WHEN is_wildcard THEN 1 ELSE 0 END) as wildcard_imports
FROM nodes_import
GROUP BY filename
ORDER BY import_count DESC
LIMIT 20;

Неиспользуемые импорты (приближённый анализ)

-- Импорты, на которые нет ссылок в вызовах
SELECT
    i.filename,
    i.imported_entity,
    i.line_number
FROM nodes_import i
WHERE NOT EXISTS (
    SELECT 1 FROM nodes_call c
    WHERE c.filename = i.filename
      AND c.name = SPLIT_PART(i.imported_entity, '.', -1)
)
AND i.is_wildcard = false
ORDER BY i.filename, i.line_number
LIMIT 100;

Соединения по идентификаторам

Многие таблицы CPG содержат столбцы с идентификаторами для прямых соединений, что эффективнее нечётких сопоставлений по LIKE.

Основные столбцы-идентификаторы

Таблица Столбец Целевая таблица
nodes_call containing_method_id nodes_method.id
nodes_call callee_method_id nodes_method.id
nodes_param method_id nodes_method.id
nodes_method_return method_id nodes_method.id
nodes_method_parameter_out method_id nodes_method.id

Пример: параметры метода через идентификатор

-- Вместо нечёткого LIKE '%method_name%' используйте прямое соединение
SELECT
    m.name as method_name,
    p.name as param_name,
    p.type_full_name as param_type,
    p.index as param_index
FROM nodes_method m
JOIN nodes_param p ON p.method_id = m.id
WHERE m.name = 'authenticate'
ORDER BY p.index;

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

SELECT
    m.name as method_name,
    c.name as called_function,
    c.line_number
FROM nodes_method m
JOIN nodes_call c ON c.containing_method_id = m.id
WHERE m.name = 'main'
ORDER BY c.line_number;

Советы по использованию

  1. Всегда добавляйте LIMIT, чтобы избежать возврата миллионов строк
  2. Используйте конкретные столбцы вместо SELECT *
  3. Сначала тестируйте на небольших наборах данных
  4. Добавляйте условия WHERE, чтобы отфильтровать нерелевантные результаты
  5. Используйте EXPLAIN для проверки планов выполнения запросов
  6. Контролируйте время выполнения (для большинства запросов должно быть < 100 мс)
  7. Предпочитайте соединения по идентификаторам (containing_method_id, method_id) вместо LIKE

Заметки о производительности

  • Простые запросы: 1–3 мс
  • JOIN’ы (1–2 таблицы): 2–5 мс
  • Агрегации: 4–8 мс
  • Рекурсивные CTE: 10–50 мс (зависит от глубины)

Все запросы выполнялись на тестовой базе данных с 5 методами и 4 вызовами.

Производительность растёт сублинейно с увеличением объёма данных благодаря использованию индексов.

Предварительные требования для PGQ

Для выполнения графовых запросов SQL/PGQ необходима подготовка.

Материализация полиморфного представления cpg_nodes

PGQ использует cpg_nodes — объединение всех таблиц узлов — в качестве полиморфной вершины графа. Перед запросами необходимо материализовать это представление:

-- Создание материализованной таблицы cpg_nodes
-- (объединение всех таблиц узлов с общими столбцами)
CREATE TABLE cpg_nodes AS
SELECT id, 'METHOD' as node_type, name, full_name, filename, line_number FROM nodes_method
UNION ALL
SELECT id, 'CALL' as node_type, name, method_full_name as full_name, filename, line_number FROM nodes_call
UNION ALL
SELECT id, 'PARAM' as node_type, name, type_full_name as full_name, filename, line_number FROM nodes_param
UNION ALL
SELECT id, 'IDENTIFIER' as node_type, name, type_full_name as full_name, filename, line_number FROM nodes_identifier
UNION ALL
SELECT id, 'LITERAL' as node_type, name, type_full_name as full_name, filename, line_number FROM nodes_literal
UNION ALL
SELECT id, 'CONTROL_STRUCTURE' as node_type, name, NULL as full_name, filename, line_number FROM nodes_control_structure
UNION ALL
SELECT id, 'TYPE_DECL' as node_type, name, full_name, filename, line_number FROM nodes_type_decl
-- ... (и остальные таблицы узлов)
;

Создание графа свойств

CREATE PROPERTY GRAPH cpg
VERTEX TABLES (
    nodes_method    LABEL METHOD,
    nodes_call      LABEL CALL_NODE,
    nodes_type_decl LABEL TYPE_DECL,
    cpg_nodes       LABEL CPG_NODE
)
EDGE TABLES (
    edges_call           SOURCE KEY (src) REFERENCES nodes_call (id)
                         DESTINATION KEY (dst) REFERENCES nodes_method (id)
                         LABEL CALLS,
    edges_ast            SOURCE KEY (src) REFERENCES cpg_nodes (id)
                         DESTINATION KEY (dst) REFERENCES cpg_nodes (id)
                         LABEL AST,
    edges_cfg            SOURCE KEY (src) REFERENCES cpg_nodes (id)
                         DESTINATION KEY (dst) REFERENCES cpg_nodes (id)
                         LABEL CFG,
    edges_reaching_def   SOURCE KEY (src) REFERENCES cpg_nodes (id)
                         DESTINATION KEY (dst) REFERENCES cpg_nodes (id)
                         LABEL REACHING_DEF,
    edges_ref            SOURCE KEY (src) REFERENCES cpg_nodes (id)
                         DESTINATION KEY (dst) REFERENCES cpg_nodes (id)
                         LABEL REF,
    edges_inherits_from  SOURCE KEY (src) REFERENCES nodes_type_decl (id)
                         DESTINATION KEY (dst) REFERENCES cpg_nodes (id)
                         LABEL INHERITS_FROM
);

Замечание. GoCPG автоматически создаёт cpg_nodes и граф свойств при генерации CPG. Ручная настройка требуется только при работе с базой данных напрямую.

Графовые запросы на SQL/PGQ

Расширение SQL/PGQ в DuckDB позволяет выполнять интуитивные запросы для обхода графов.

Обход графа вызовов

-- Найти все методы, вызываемые из main (прямые и косвенные)
-- Метка CALLS связывает CALL_NODE → METHOD
FROM GRAPH_TABLE(cpg
    MATCH (caller:CALL_NODE)-[:CALLS*1..3]->(callee:METHOD)
    WHERE caller.name = 'main'
    COLUMNS (
        caller.name AS caller,
        callee.name AS callee,
        callee.filename
    )
)
LIMIT 100;

Анализ потока данных

-- Отслеживание потока данных через достижимые определения
-- REACHING_DEF связывает CPG_NODE → CPG_NODE (полиморфные узлы)
FROM GRAPH_TABLE(cpg
    MATCH (src:CPG_NODE)-[:REACHING_DEF*1..5]->(sink:CPG_NODE)
    WHERE src.name = 'user_input'
    COLUMNS (
        src.name AS source_var,
        sink.name AS sink_function,
        sink.line_number
    )
)
LIMIT 100;

Обход AST

-- Найти все узлы, находящиеся под методом в AST
FROM GRAPH_TABLE(cpg
    MATCH (method:METHOD)-[:AST*1..5]->(child:CPG_NODE)
    WHERE method.name = 'authenticate'
    COLUMNS (
        method.name AS method_name,
        child.id AS child_id,
        child.node_type
    )
)
LIMIT 100;

Иерархия типов

-- Найти отношения наследования
-- INHERITS_FROM связывает TYPE_DECL → CPG_NODE
FROM GRAPH_TABLE(cpg
    MATCH (derived:TYPE_DECL)-[:INHERITS_FROM]->(base:CPG_NODE)
    COLUMNS (
        derived.name AS derived_type,
        base.full_name AS base_type
    )
)

Дальнейшие действия

  • Попробуйте эти запросы в своей базе данных CPG
  • Измените параметры в соответствии со своими требованиями
  • Комбинируйте шаблоны для сложного анализа
  • Контролируйте производительность и оптимизируйте при необходимости
  • Добавьте свои собственные шаблоны в эту книгу рецептов!

Смотрите также