From c990cb2246974800f42801ff46a28d7dc6e2b58a Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Wed, 23 Jul 2025 01:28:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-979=20-=20=D0=91=D0=B0?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BD=D1=8B=D0=B9=20API,=20=D1=82=D0=B0=D0=B1=D0=BB?= =?UTF-8?q?=D0=B8=D1=86=D0=B0,=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=20-?= =?UTF-8?q?=20=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20(=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=B4=D0=B6=D0=B5=D1=80=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=BE=D0=B2,=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B4=D0=B8=D0=B0=D0=B3=D1=80=D0=B0=D0=BC=D0=BC=D0=B0?= =?UTF-8?q?,=20IUD=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D0=B9=20=D0=B8=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B5=D0=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/panels/query_editor/common.js | 23 + .../components/attribute/attribute.js | 104 ++ .../query_editor/components/entity/entity.css | 33 + .../query_editor/components/entity/entity.js | 50 + .../entity_add_dialog/entity_add_dialog.js | 43 + .../queries_manager/queries_list.js | 142 ++ .../queries_manager/queries_manager.js | 105 ++ .../queries_manager/query_iu_dialog.js | 54 + .../query_diagram/query_diagram.css | 9 + .../components/query_diagram/query_diagram.js | 160 ++ app/panels/query_editor/hooks.js | 290 +++ app/panels/query_editor/index.js | 16 + app/panels/query_editor/query_editor.js | 137 ++ db/P8PNL_QE_QUERY.sql | 25 + db/PKG_P8PANELS_QE.pck | 342 ++++ db/PKG_P8PANELS_QE_BASE.pck | 1573 +++++++++++++++++ db/grants.sql | 4 +- 17 files changed, 3109 insertions(+), 1 deletion(-) create mode 100644 app/panels/query_editor/common.js create mode 100644 app/panels/query_editor/components/attribute/attribute.js create mode 100644 app/panels/query_editor/components/entity/entity.css create mode 100644 app/panels/query_editor/components/entity/entity.js create mode 100644 app/panels/query_editor/components/entity_add_dialog/entity_add_dialog.js create mode 100644 app/panels/query_editor/components/queries_manager/queries_list.js create mode 100644 app/panels/query_editor/components/queries_manager/queries_manager.js create mode 100644 app/panels/query_editor/components/queries_manager/query_iu_dialog.js create mode 100644 app/panels/query_editor/components/query_diagram/query_diagram.css create mode 100644 app/panels/query_editor/components/query_diagram/query_diagram.js create mode 100644 app/panels/query_editor/hooks.js create mode 100644 app/panels/query_editor/index.js create mode 100644 app/panels/query_editor/query_editor.js create mode 100644 db/P8PNL_QE_QUERY.sql create mode 100644 db/PKG_P8PANELS_QE.pck create mode 100644 db/PKG_P8PANELS_QE_BASE.pck diff --git a/app/panels/query_editor/common.js b/app/panels/query_editor/common.js new file mode 100644 index 0000000..fc243db --- /dev/null +++ b/app/panels/query_editor/common.js @@ -0,0 +1,23 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Обще ресурсы и константы +*/ + +//--------- +//Константы +//--------- + +//Типы данных +const DATA_TYPE = { STR: 0, NUMB: 1, DATE: 2 }; + +//Типы элементов диаграммы +const NODE_TYPE = { + ENTITY: "entity", + ATTRIBUTE: "attribute" +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { DATA_TYPE, NODE_TYPE }; diff --git a/app/panels/query_editor/components/attribute/attribute.js b/app/panels/query_editor/components/attribute/attribute.js new file mode 100644 index 0000000..f1d36ef --- /dev/null +++ b/app/panels/query_editor/components/attribute/attribute.js @@ -0,0 +1,104 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Компоненты: Атрибут сущности +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Handle, Position, useStore } from "reactflow"; //Библиотека редактора диаграмм +import { Box, Stack, Icon, Typography } from "@mui/material"; //Компоненты UI +import { DATA_TYPE } from "../../common"; //Общие ресурсы и константы редактора + +//--------- +//Константы +//--------- + +//Типовые цвета точек привязки +const HANDLE_BORDER_COLOR = "#69db7c"; +const HANDLE_BORDER_COLOR_DISABLED = "#adb5bd"; + +//Стили +const STYLES = { + CONTAINER: { display: "flex", width: "100%", height: "100%" }, + HANDLE_SOURCE: isConnecting => ({ + width: 14, + height: 14, + right: -10, + border: `2px solid ${isConnecting ? HANDLE_BORDER_COLOR_DISABLED : HANDLE_BORDER_COLOR}`, + borderRadius: 7, + background: "white" + }), + HANDLE_TARGET: isConnecting => ({ + width: isConnecting ? 14 : 0, + height: 14, + left: isConnecting ? -7 : 0, + border: `2px solid ${HANDLE_BORDER_COLOR}`, + borderRadius: 7, + background: "white", + visibility: isConnecting ? "visible" : "hidden" + }), + CONTENT_STACK: { width: "100%" }, + TITLE_NAME_STACK: { width: "100%", containerType: "inline-size" } +}; + +//Иконки +const ICONS = { + [DATA_TYPE.STR]: "format_align_left", + [DATA_TYPE.NUMB]: "pin", + [DATA_TYPE.DATE]: "calendar_month", + DEFAULT: "category" +}; + +//Структура данных об атрибуте сущности +const ATTRIBUTE_DATA_SHAPE = PropTypes.shape({ + name: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + dataType: PropTypes.number.isRequired +}); + +//----------- +//Тело модуля +//----------- + +//Атрибут сущности +const Attribute = ({ data }) => { + //Поиск идентификатора соединяемого элемента + const connectionNodeId = useStore(state => state.connectionNodeId); + + //Флаг выполнения соединения сущностей + const isConnecting = Boolean(connectionNodeId); + + //Формирование представления + return ( + + + + + {ICONS[data.dataType] || ICONS.DEFAULT} + + + {data.title} + + + {data.name} + + + + + ); +}; + +//Контроль свойств компонента - Атрибут сущности +Attribute.propTypes = { + data: ATTRIBUTE_DATA_SHAPE +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { Attribute }; diff --git a/app/panels/query_editor/components/entity/entity.css b/app/panels/query_editor/components/entity/entity.css new file mode 100644 index 0000000..1f64803 --- /dev/null +++ b/app/panels/query_editor/components/entity/entity.css @@ -0,0 +1,33 @@ +.entity__wrapper { + width: 100%; + height: 100%; + border: 1px solid var(--border-color-dark); + border-radius: 6px; + box-shadow: var(--shadow-entity); + overflow: hidden; + background-color: white; +} + +.entity__wrapper[data-selected="true"] { + outline: 1px solid var(--outline-color); + border-color: var(--outline-color); +} + +.entity__title { + width: 100%; + height: 50px; + align-content: center; + border-bottom: 1px solid var(--border-color); + font-weight: 900; + text-align: center; + background-color: var(--entity-title-bg); + cursor: move; +} + +.entity__name { + width: 100%; + align-content: center; + text-align: center; + font-size: 0.8rem; + color: gray; +} diff --git a/app/panels/query_editor/components/entity/entity.js b/app/panels/query_editor/components/entity/entity.js new file mode 100644 index 0000000..89b9797 --- /dev/null +++ b/app/panels/query_editor/components/entity/entity.js @@ -0,0 +1,50 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Компоненты: Сущность запроса +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import "./entity.css"; //Стили компомнента + +//--------- +//Константы +//--------- + +//Структура данных о сущности запроса +const ENTITY_DATA_SHAPE = PropTypes.shape({ + name: PropTypes.string.isRequired, + title: PropTypes.string.isRequired +}); + +//----------- +//Тело модуля +//----------- + +//Сущность запроса +const Entity = ({ data, selected }) => { + return ( +
+
+ {data.title} +
{data.name}
+
+
+ ); +}; + +//Контроль свойств компонента - Сущность запроса +Entity.propTypes = { + data: ENTITY_DATA_SHAPE, + selected: PropTypes.bool.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { Entity }; diff --git a/app/panels/query_editor/components/entity_add_dialog/entity_add_dialog.js b/app/panels/query_editor/components/entity_add_dialog/entity_add_dialog.js new file mode 100644 index 0000000..b67a589 --- /dev/null +++ b/app/panels/query_editor/components/entity_add_dialog/entity_add_dialog.js @@ -0,0 +1,43 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Компонент: Диалог добавления сущности запроса +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог +import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения + +//----------- +//Тело модуля +//----------- + +//Диалог добавления сущности запроса +const EntityAddDialog = ({ onOk, onCancel }) => { + //Нажатие на кнопку "Ok" + const handleOk = values => onOk && onOk({ ...values }); + + //Нажатие на кнопку "Отмена" + const handleCancel = () => onCancel && onCancel(); + + //Генерация содержимого + return ( + + ); +}; + +//Контроль свойств - Диалог добавления сущности запроса +EntityAddDialog.propTypes = { + onOk: PropTypes.func, + onCancel: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { EntityAddDialog }; diff --git a/app/panels/query_editor/components/queries_manager/queries_list.js b/app/panels/query_editor/components/queries_manager/queries_list.js new file mode 100644 index 0000000..f3347e9 --- /dev/null +++ b/app/panels/query_editor/components/queries_manager/queries_list.js @@ -0,0 +1,142 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Компонент: Список запросов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные компоненты MUI +import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + SMALL_TOOL_ICON: { + fontSize: 20 + } +}; + +//--------- +//Константы +//--------- + +//Структура данных о сущности запроса +const QUERIES_LIST_ITEM_SHAPE = PropTypes.shape({ + rn: PropTypes.number.isRequired, + code: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + author: PropTypes.string.isRequired, + chDate: PropTypes.string.isRequired, + modify: PropTypes.oneOf([0, 1]).isRequired, + pbl: PropTypes.oneOf([0, 1]).isRequired, + ready: PropTypes.oneOf([0, 1]).isRequired +}); + +//----------- +//Тело модуля +//----------- + +//Диалог открытия запроса +const QueriesList = ({ queries = [], current = null, onSelect = null, onPbl = null, onReady = null, onEdit = null, onDelete = null } = {}) => { + //При выборе элемента списка + const handleSelectClick = query => { + onSelect && onSelect(query); + }; + + //При нажатии на общедоступность + const handlePblClick = (e, query) => { + e.stopPropagation(); + onPbl && onPbl(query); + }; + + //При нажатии на готовность + const handleReadyClick = (e, query) => { + e.stopPropagation(); + onReady && onReady(query); + }; + + //При нажатии на исправлении + const handleEditClick = (e, query) => { + e.stopPropagation(); + onEdit && onEdit(query); + }; + + //При нажатии на удаление + const handleDeleteClick = (e, query) => { + e.stopPropagation(); + onDelete && onDelete(query); + }; + + //Формирование представления + return ( + + {queries.map((query, i) => { + const selected = query.rn === current; + const disabled = !query.modify; + const pblTitle = `${query.pbl === 1 ? "Общедоступный" : "Приватный"}${!disabled ? " - нажмите, чтобы изменить" : ""}`; + const pblIcon = query.pbl === 1 ? "groups" : "lock_person"; + const readyTitle = `${query.ready === 1 ? "Готов" : "Не готов"}${!disabled ? " - нажмите, чтобы изменить" : ""}`; + const readyIcon = query.ready === 1 ? "touch_app" : "do_not_touch"; + return ( + + handleSelectClick(query)} selected={selected}> + + {`${query.code}, ${query.author}, ${query.chDate}`} + +
+ handlePblClick(e, query)}> + {pblIcon} + +
+
+ handleReadyClick(e, query)}> + {readyIcon} + +
+
+ + } + /> + + handleEditClick(e, query)} disabled={disabled} title={BUTTONS.UPDATE}> + edit + + handleDeleteClick(e, query)} disabled={disabled} title={BUTTONS.DELETE}> + delete + + +
+
+ ); + })} +
+ ); +}; + +//Контроль свойств компонента - Список запросов +QueriesList.propTypes = { + queries: PropTypes.arrayOf(QUERIES_LIST_ITEM_SHAPE), + current: PropTypes.number, + onSelect: PropTypes.func, + onPbl: PropTypes.func, + onReady: PropTypes.func, + onEdit: PropTypes.func, + onDelete: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { QueriesList }; diff --git a/app/panels/query_editor/components/queries_manager/queries_manager.js b/app/panels/query_editor/components/queries_manager/queries_manager.js new file mode 100644 index 0000000..fe31615 --- /dev/null +++ b/app/panels/query_editor/components/queries_manager/queries_manager.js @@ -0,0 +1,105 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Компонент: Менеджер запросов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useContext } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI +import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений +import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения +import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Типовой диалог настройки +import { useQuery } from "../../hooks"; //Пользовательские хуки +import { QueriesList } from "./queries_list"; //Список запросов +import { QueryIUDialog } from "./query_iu_dialog"; //Диалог добавления/исправления запроса + +//----------- +//Тело модуля +//----------- + +//Менеджер запросов +const QueriesManager = ({ current = null, onQuerySelect = null, onCancel = null } = {}) => { + //Собственное состояние - изменяемый запрос + const [modQuery, setModQuery] = useState(null); + + //Работа со списком запросов + const [queries, insertQuery, updateQuery, deleteQuery, setQueryReady, setQueryPbl] = useQuery(); + + //Подключение к контексту сообщений + const { showMsgWarn } = useContext(MessagingСtx); + + //При добавлении запроса + const handleQueryAdd = () => setModQuery(true); + + //При выборе запроса + const handleQuerySelect = query => onQuerySelect && onQuerySelect(query.rn); + + //При установке признака публичности + const handleQueryPblSet = query => setQueryPbl(query.rn, query.pbl === 1 ? 0 : 1); + + //При установке признака готовности + const handleQueryReadySet = query => setQueryReady(query.rn, query.ready === 1 ? 0 : 1); + + //При исправлении запроса + const handleQueryEdit = query => setModQuery({ ...query }); + + //При удалении запроса + const handleQueryDelete = query => showMsgWarn("Удалить запрос?", () => deleteQuery(query.rn)); + + //При закрытии диалога добавления/исправления по "ОК" + const handleIUDialogOk = async values => { + if (modQuery === true) await insertQuery(values.code, values.name); + else await updateQuery(modQuery.rn, values.code, values.name); + setModQuery(null); + }; + + //При закрытии диалога добавления/исправления по "Отмена" + const handleIUDialogCancel = () => setModQuery(null); + + //При закртии менеджера отменой + const handleCancel = () => onCancel && onCancel(); + + //Формирование представления + return ( + + {modQuery && ( + + )} + + + + ); +}; + +//Контроль свойств компонента - Менеджер запросов +QueriesManager.propTypes = { + current: PropTypes.number, + onQuerySelect: PropTypes.func, + onCancel: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { QueriesManager }; diff --git a/app/panels/query_editor/components/queries_manager/query_iu_dialog.js b/app/panels/query_editor/components/queries_manager/query_iu_dialog.js new file mode 100644 index 0000000..6053f07 --- /dev/null +++ b/app/panels/query_editor/components/queries_manager/query_iu_dialog.js @@ -0,0 +1,54 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Компонент: Диалог добавления/исправления запроса +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог +import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения + +//----------- +//Тело модуля +//----------- + +//Диалог добавления/исправления запроса +const QueryIUDialog = ({ code = "", name = "", insert = true, onOk, onCancel }) => { + //Нажатие на кнопку "Ok" + const handleOk = values => onOk && onOk({ ...values }); + + //Нажатие на кнопку "Отмена" + const handleCancel = () => onCancel && onCancel(); + + //Генерация содержимого + return ( + + ); +}; + +//Контроль свойств - Диалог добавления/исправления запроса +QueryIUDialog.propTypes = { + code: PropTypes.string, + name: PropTypes.string, + insert: PropTypes.bool, + onOk: PropTypes.func, + onCancel: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { QueryIUDialog }; diff --git a/app/panels/query_editor/components/query_diagram/query_diagram.css b/app/panels/query_editor/components/query_diagram/query_diagram.css new file mode 100644 index 0000000..535e228 --- /dev/null +++ b/app/panels/query_editor/components/query_diagram/query_diagram.css @@ -0,0 +1,9 @@ +.query_diagram { + --border-color: #dee2e6; + --border-color-dark: #adb5bd; + --outline-color: #74c0fc; + --entity-title-bg: #f1f3f5; + --shadow-entity: 0 -2px 5px 0 hsl(220 3% 15% / calc(1% + 2%)), 0 1px 1px -2px hsl(220 3% 15% / calc(1% + 3%)), + 0 2px 2px -2px hsl(220 3% 15% / calc(1% + 3%)), 0 5px 5px -2px hsl(220 3% 15% / calc(1% + 4%)), 0 9px 9px -2px hsl(220 3% 15% / calc(1% + 5%)), + 0 16px 16px -2px hsl(220 3% 15% / calc(1% + 6%)); +} diff --git a/app/panels/query_editor/components/query_diagram/query_diagram.js b/app/panels/query_editor/components/query_diagram/query_diagram.js new file mode 100644 index 0000000..2715653 --- /dev/null +++ b/app/panels/query_editor/components/query_diagram/query_diagram.js @@ -0,0 +1,160 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Диаграмма запроса +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useCallback, useEffect } from "react"; //Классы React +import ReactFlow, { addEdge, Controls, getOutgoers, applyNodeChanges, applyEdgeChanges } from "reactflow"; //Библиотека редактора диаграмм +import { NODE_TYPE } from "../../common"; //Общие ресурсы и константы редактора +import { Entity } from "../entity/entity"; //Сущность запроса +import { Attribute } from "../attribute/attribute"; //Атрибут сущности +import "reactflow/dist/style.css"; //Типовые стили библиотеки редактора диаграмм +import "./query_diagram.css"; //Стили компонента + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + CONNECTION_LINE: { + strokeWidth: 2, + stroke: "gray" + }, + EDGE: { + strokeWidth: 2 + } +}; + +//Привязка компонтов диаграммы к типам +const NODE_TYPES_COMPONENTS = { + [NODE_TYPE.ENTITY]: Entity, + [NODE_TYPE.ATTRIBUTE]: Attribute +}; + +//------------------------------------ +//Вспомогательные функции и компоненты +//------------------------------------ + +const hasCycle = (connection, target, nodes, edges, visited = new Set()) => { + if (visited.has(target.id)) { + return false; + } + + visited.add(target.id); + + for (const outgoer of getOutgoers(target, nodes, edges)) { + if (outgoer.id === connection.source || hasCycle(connection, outgoer, nodes, edges, visited)) { + return true; + } + } + + return false; +}; + +const isValidConnection = (connection, nodes, edges) => { + if (!connection.source || !connection.target) { + return false; + } + + const tableId = connection.source.split("-")[0]; + const isSameTable = connection.target.startsWith(tableId); + if (isSameTable) { + return false; + } + + const target = nodes.find(node => node.id === connection.target); + if (!target || target.id === connection.source) { + return false; + } + + return !hasCycle(connection, target, nodes, edges); +}; + +//----------- +//Тело модуля +//----------- + +//Диаграмма запроса +const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRemove, onRelationAdd, onRelationRemove }) => { + //Собственное состояние - элементы + const [nodes, setNodes] = useState(entities); + + //Собственное состояние - связи + const [edges, setEdges] = useState(relations); + + //Собственное состояние - перемещённый элемент + const [movedNode, setMovedNode] = useState(null); + + //При изменении элементов на диаграмме + const handleNodesChange = useCallback( + changes => { + setNodes(nodesSnapshot => applyNodeChanges(changes, nodesSnapshot)); + if (changes.length == 1 && changes[0].type == "position" && changes[0].dragging) + setMovedNode({ id: changes[0].id, position: { ...changes[0].position } }); + if (changes.length == 1 && changes[0].type == "position" && !changes[0].dragging && movedNode) { + if (onEntityPositionChange) onEntityPositionChange(movedNode.id, movedNode.position); + setMovedNode(null); + } + if (changes[0].type == "remove" && entities.find(e => e.id == changes[0].id && e.type == NODE_TYPE.ENTITY) && onEntityRemove) + onEntityRemove(changes[0].id); + }, + [movedNode, entities, onEntityPositionChange, onEntityRemove] + ); + + //При связывании элементов на диаграмме + const handleConnect = connection => { + setEdges(state => addEdge({ ...connection, id: `${connection.source}-${connection.target}` }, state)); + onRelationAdd && onRelationAdd(connection.source, connection.target); + }; + + //При изменении связей на диаграмме + const handleEdgesChange = useCallback( + changes => { + setEdges(edgesSnapshot => applyEdgeChanges(changes, edgesSnapshot)); + if (changes.length == 1 && changes[0].type == "remove" && onRelationRemove) onRelationRemove(changes[0].id); + }, + [onRelationRemove] + ); + + const validateConnection = connection => { + return isValidConnection(connection, nodes, edges); + }; + + //При изменении состава сущностей + useEffect(() => setNodes(entities), [entities]); + + //При изменении состава связей + useEffect(() => setEdges(relations), [relations]); + + //Генерация содержимого + return ( + + + + ); +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { QueryDiagram }; diff --git a/app/panels/query_editor/hooks.js b/app/panels/query_editor/hooks.js new file mode 100644 index 0000000..eb6a9f9 --- /dev/null +++ b/app/panels/query_editor/hooks.js @@ -0,0 +1,290 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Пользовательские хуки +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useContext, useEffect, useCallback } from "react"; //Классы React +import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { NODE_TYPE } from "./common"; //Общие ресурсы и константы редактора + +//------------------------------------ +//Вспомогательные функции и компоненты +//------------------------------------ + +//Конвертация серверного описания сущностей запроса в элементы диаграммы +const serverEntity2QueryDiagramNodes = entity => { + const groupWidth = 250; + const nameColumnHeight = 50; + const columns = entity?.XATTRS?.XATTR || []; + const columnsCount = columns.length; + const groupHeight = nameColumnHeight + columnsCount * 50; + + const groupNode = { + id: entity.id, + type: NODE_TYPE.ENTITY, + data: { ...entity }, + position: { x: entity.x, y: entity.y }, + style: { + width: groupWidth, + height: groupHeight + }, + draggable: true + }; + + const columnNodes = columns.map((column, index, columns) => { + const x = 1; + const y = 50 * (index + 1); + const width = groupWidth - 2; + const height = 50; + + const isLast = index === columns.length - 1; + const defaultColumnStyles = { + borderBottom: "1px solid #dee2e6" + }; + const lastColumnStyles = { + borderBottom: "none" + }; + const otherStyles = isLast ? lastColumnStyles : defaultColumnStyles; + + return { + id: column.id, + type: NODE_TYPE.ATTRIBUTE, + data: { + ...column, + included: false, + parentEntity: entity.id + }, + position: { x, y }, + parentId: entity.id, + extent: "parent", + style: { + width, + height, + ...otherStyles + }, + draggable: false, + selectable: true + }; + }); + + return [groupNode, ...columnNodes]; +}; + +//Конвертация серверного описания запроса в данные для редактора диаграмм +const serverQueryData2QueryDiagram = (entities, relations) => { + const result = { entities: [], relations: [...relations] }; + entities.forEach(entity => { + const nodes = serverEntity2QueryDiagramNodes(entity); + result.entities = [...result.entities, ...nodes]; + }); + return result; +}; + +//----------- +//Тело модуля +//----------- + +//Работа с запросами +const useQuery = () => { + //Собственное состояние - флаг инициализированности + const [isInit, setInit] = useState(false); + + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - флаг необходимости обновления + const [refresh, setRefresh] = useState(true); + + //Собственное состояние - данные + const [data, setData] = useState(null); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //Обновление данных + const doRefresh = () => setRefresh(true); + + //Добавление запроса + const insertQuery = useCallback( + async (code, name) => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_INSERT", args: { SCODE: code, SNAME: name }, loader: false }); + setRefresh(true); + }, + [executeStored] + ); + + //Изменение запроса + const updateQuery = useCallback( + async (query, code, name) => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_UPDATE", args: { NRN: query, SCODE: code, SNAME: name }, loader: false }); + setRefresh(true); + }, + [executeStored] + ); + + //Удаление запроса + const deleteQuery = useCallback( + async query => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_DELETE", args: { NRN: query }, loader: false }); + setRefresh(true); + }, + [executeStored] + ); + + //Установка флага готовности запроса + const setQueryReady = useCallback( + async (query, ready) => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_READY_SET", args: { NRN: query, NREADY: ready }, loader: false }); + setRefresh(true); + }, + [executeStored] + ); + + //Установка флага публичности запроса + const setQueryPbl = useCallback( + async (query, pbl) => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_PBL_SET", args: { NRN: query, NPBL: pbl }, loader: false }); + setRefresh(true); + }, + [executeStored] + ); + + //При необходимости получить/обновить данные + useEffect(() => { + //Загрузка данных с сервера + const loadData = async () => { + try { + setLoading(true); + const data = await executeStored({ + stored: "PKG_P8PANELS_QE.QUERY_LIST", + respArg: "COUT", + isArray: name => ["XQUERY"].includes(name), + attributeValueProcessor: (name, val) => (["code", "name"].includes(name) ? undefined : val), + loader: true + }); + setData(data?.XQUERIES?.XQUERY || []); + setInit(true); + } finally { + setRefresh(false); + setLoading(false); + } + }; + //Если надо обновить + if (refresh) + //Получим данные + loadData(); + }, [refresh, executeStored]); + + //Возвращаем интерфейс хука + return [data, insertQuery, updateQuery, deleteQuery, setQueryReady, setQueryPbl, doRefresh, isLoading, isInit]; +}; + +//Работа с содержимым запроса +const useQueryDesc = query => { + //Собственное состояние - флаг инициализированности + const [isInit, setInit] = useState(false); + + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - флаг необходимости обновления + const [refresh, setRefresh] = useState(true); + + //Собственное состояние - данные + const [data, setData] = useState(null); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //Обновление данных + const doRefresh = () => setRefresh(true); + + //Добавление сущности в запрос + const addEnt = useCallback( + async (name, type) => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_ADD", args: { NRN: query, SNAME: name, STYPE: type }, loader: false }); + setRefresh(true); + }, + [query, executeStored] + ); + + //Удаление сущности из запроса + const removeEnt = useCallback( + async ent => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_REMOVE", args: { NRN: query, SID: ent }, loader: false }); + setRefresh(true); + }, + [query, executeStored] + ); + + //Сохранение координат сущности на диаграммем + const setEntPosition = useCallback( + async (ent, x, y) => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_POSITION_SET", args: { NRN: query, SID: ent, NX: x, NY: y }, loader: false }); + }, + [query, executeStored] + ); + + //Добавление отношения сущностей в запрос + const addRl = useCallback( + async (source, target) => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_RL_ADD", args: { NRN: query, SSOURCE: source, STARGET: target }, loader: false }); + setRefresh(true); + }, + [query, executeStored] + ); + + //Удаление отношения сущностей из запроса + const removeRl = useCallback( + async rl => { + await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_RL_REMOVE", args: { NRN: query, SID: rl }, loader: false }); + setRefresh(true); + }, + [query, executeStored] + ); + + //При необходимости получить/обновить данные + useEffect(() => { + //Загрузка данных с сервера + const loadData = async () => { + try { + setLoading(true); + const data = await executeStored({ + stored: "PKG_P8PANELS_QE.QUERY_DESC", + args: { NRN: query }, + respArg: "COUT", + isArray: name => ["XENT", "XATTR", "XRL"].includes(name), + loader: true + }); + setData(serverQueryData2QueryDiagram(data?.XENTS?.XENT || [], data?.XRLS?.XRL || [])); + setInit(true); + } finally { + setRefresh(false); + setLoading(false); + } + }; + //Если надо обновить + if (refresh) + if (query) + //Если есть для чего получать данные + loadData(); + //Нет идентификатора запроса - нет данных + else setData(null); + }, [refresh, query, executeStored]); + + //При изменении входных свойств - поднимаем флаг обновления + useEffect(() => setRefresh(true), [query]); + + //Возвращаем интерфейс хука + return [data, addEnt, removeEnt, setEntPosition, addRl, removeRl, doRefresh, isLoading, isInit]; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useQuery, useQueryDesc }; diff --git a/app/panels/query_editor/index.js b/app/panels/query_editor/index.js new file mode 100644 index 0000000..7675a82 --- /dev/null +++ b/app/panels/query_editor/index.js @@ -0,0 +1,16 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Редактор запросов: точка входа +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { QueryEditor } from "./query_editor"; //Корневая панель редактора + +//---------------- +//Интерфейс модуля +//---------------- + +export const RootClass = QueryEditor; diff --git a/app/panels/query_editor/query_editor.js b/app/panels/query_editor/query_editor.js new file mode 100644 index 0000000..22e4069 --- /dev/null +++ b/app/panels/query_editor/query_editor.js @@ -0,0 +1,137 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Корневой компонент +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import { Box, Grid, Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI +import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Компоненты рабочего стола +import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора +import { BUTTONS } from "../../../app.text"; //Общие текстовые ресурсы приложения +import { QueryDiagram } from "./components/query_diagram/query_diagram"; //Диаграмма запроса +import { QueriesManager } from "./components/queries_manager/queries_manager"; //Менеджер запросов +import { EntityAddDialog } from "./components/entity_add_dialog/entity_add_dialog"; //Диалог добавления сущности +import { P8PEditorBox } from "../../components/editors/p8p_editor_box"; //Контейнер параметров редактора +import { P8PEditorSubHeader } from "../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора +import { useQueryDesc } from "./hooks"; //Пользовательские хуки + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + CONTAINER: { display: "flex" }, + GRID_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})` }, + GRID_ITEM_INSPECTOR: { backgroundColor: "#e9ecef" } +}; + +//----------- +//Тело модуля +//----------- + +//Корневой компонент редактора запросов +const QueryEditor = () => { + //Текущий запрос + const [query, setQuery] = useState(null); + + //Отображения менеджера запросов + const [openQueriesManager, setOpenQueriesManager] = useState(true); + + //Отображение диалога добавления сущности + const [openEntityAddDialog, setOpenEntityAddDialog] = useState(false); + + //Получение данных запроса + const [queryDiagram, addEnt, removeEnt, setEntPosition, addRl, removeRl] = useQueryDesc(query); + + //Обработка изменения положения сущности на диаграмме + const handleEntityPositionChange = (ent, position) => setEntPosition(ent, position.x, position.y); + + //Обработка удаления сущности из запроса + const handleEntityRemove = ent => removeEnt(ent); + + //Обработка добавления отношения cущностей + const handleRelationAdd = (source, target) => addRl(source, target); + + //Обработка удаления отношения cущностей + const handleRelationRemove = rl => removeRl(rl); + + //Открытие менеджера запросов + const handleOpenQueriesManager = () => setOpenQueriesManager(true); + + //Закрытие менеджера запросов + const handleCancelQueriesManager = () => setOpenQueriesManager(false); + + //Закрытие запроса + const handleQueryClose = () => setQuery(null); + + //При выборе запроса + const handleQuerySelect = query => { + setQuery(query); + setOpenQueriesManager(false); + }; + + //При добавлении сущности в запрос + const handleEntityAdd = () => setOpenEntityAddDialog(true); + + //Закрытие диалога добавления сущности по "ОК" + const handleEntityAddDialogOk = async values => { + await addEnt(values.name, "VIEW"); + setOpenEntityAddDialog(false); + }; + + //Закрытие диалога добавления сущности по "ОК" + const handleEntityAddDialogCancel = () => setOpenEntityAddDialog(false); + + //Панель инструмментов + const toolBar = ( + + ); + + //Генерация содержимого + return ( + + {openQueriesManager && } + {openEntityAddDialog && } + + + {queryDiagram && ( + + )} + + + {toolBar} + {query && ( + + + + + )} + + + + ); +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { QueryEditor }; diff --git a/db/P8PNL_QE_QUERY.sql b/db/P8PNL_QE_QUERY.sql new file mode 100644 index 0000000..318bc7d --- /dev/null +++ b/db/P8PNL_QE_QUERY.sql @@ -0,0 +1,25 @@ +/* + Парус 8 - Панели мониторинга - Общие - Редактор запросов + Запросы +*/ +create table P8PNL_QE_QUERY +( + RN number(17) not null, -- Рег. номер записи + CODE varchar2(100) not null, -- Мнемокод + NAME varchar2(2000) not null, -- Наименование + AUTHOR varchar2(30) not null, -- Автор + CH_DATE date not null, -- Дата последнего изменения + READY number(1) default 0 not null, -- Флаг готовности к использованию (0 - нет, 1 - да) + PBL number(1) default 0 not null, -- Флаг публичности (0 - нет, 1 - да) + OPTS clob, -- Параметры запроса + ENTS clob, -- Сущности запроса + RLS clob, -- Отношения сущностей запроса + QRY clob, -- Запрос + constraint C_P8PNL_QE_QUERY_RN_PK primary key (RN), + constraint C_P8PNL_QE_QUERY_CODE_NB check (rtrim(CODE) is not null), + constraint C_P8PNL_QE_QUERY_NAME_NB check (rtrim(NAME) is not null), + constraint C_P8PNL_QE_QUERY_AUTHOR_FK foreign key (AUTHOR) references USERLIST (AUTHID) on delete cascade, + constraint C_P8PNL_QE_QUERY_READY_VAL check (READY in (0, 1)), + constraint C_P8PNL_QE_QUERY_PBL_VAL check (PBL in (0, 1)), + constraint C_P8PNL_QE_QUERY_UN unique (CODE) +); diff --git a/db/PKG_P8PANELS_QE.pck b/db/PKG_P8PANELS_QE.pck new file mode 100644 index 0000000..a0f68c6 --- /dev/null +++ b/db/PKG_P8PANELS_QE.pck @@ -0,0 +1,342 @@ +create or replace package PKG_P8PANELS_QE as + + /* Получение списка запросов */ + procedure QUERY_LIST + ( + COUT out clob -- Сериализованный список запросов + ); + + /* Добавление запроса */ + procedure QUERY_INSERT + ( + SCODE in varchar2, -- Мнемокод + SNAME in varchar2, -- Наименование + NRN out number -- Рег. номер добавленного запроса + ); + + /* Исправление запроса */ + procedure QUERY_UPDATE + ( + NRN in number, -- Рег. номер запроса + SCODE in varchar2, -- Мнемокод + SNAME in varchar2 -- Наименование + ); + + /* Удаление запроса */ + procedure QUERY_DELETE + ( + NRN in number -- Рег. номер запроса + ); + + /* Получение данных о запросе */ + procedure QUERY_DESC + ( + NRN in number, -- Рег. номер запроса + COUT out clob -- Сериализованное описание запроса + ); + + /* Добавление сущности в запрос */ + procedure QUERY_ENT_ADD + ( + NRN in number, -- Рег. номер запроса + SNAME in varchar2, -- Имя + STYPE in varchar2 -- Тип (см. константы SENT_TYPE_*) + ); + + /* Удаление сущности из запроса */ + procedure QUERY_ENT_REMOVE + ( + NRN in number, -- Рег. номер запроса + SID in varchar2 -- Идентификатор сущности + ); + + /* Установка координат сущности в редакторе диаграммы запроса */ + procedure QUERY_ENT_POSITION_SET + ( + NRN in number, -- Рег. номер запроса + SID in varchar2, -- Идентификатор сущности + NX in number, -- Координата по оси абсцисс + NY in number -- Координата по оси ординат + ); + + /* Добавление связи в запрос */ + procedure QUERY_RL_ADD + ( + NRN in number, -- Рег. номер запроса + SSOURCE in varchar2, -- Идентификатор атрибута-источника + STARGET in varchar2 -- Идентификатор атрибута-приёмника + ); + + /* Удаление связи из запроса */ + procedure QUERY_RL_REMOVE + ( + NRN in number, -- Рег. номер запроса + SID in varchar2 -- Идентификатор связи + ); + + /* Установка признака "готовности" запроса */ + procedure QUERY_READY_SET + ( + NRN in number, -- Рег. номер запроса + NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да) + ); + + /* Установка признака "публичности" запроса */ + procedure QUERY_PBL_SET + ( + NRN in number, -- Рег. номер запроса + NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный) + ); + +end PKG_P8PANELS_QE; +/ +create or replace package body PKG_P8PANELS_QE as + + /* Получение списка запросов */ + procedure QUERY_LIST + ( + COUT out clob -- Сериализованный список запросов + ) + is + begin + /* Базово сформируем список */ + COUT := PKG_P8PANELS_QE_BASE.QUERY_LIST_GET(SUSER => UTILIZER()); + end QUERY_LIST; + + /* Добавление запроса */ + procedure QUERY_INSERT + ( + SCODE in varchar2, -- Мнемокод + SNAME in varchar2, -- Наименование + NRN out number -- Рег. номер добавленного запроса + ) + is + begin + /* Базовое добавление */ + PKG_P8PANELS_QE_BASE.QUERY_INSERT(SCODE => SCODE, SNAME => SNAME, NRN => NRN); + end QUERY_INSERT; + + /* Исправление запроса */ + procedure QUERY_UPDATE + ( + NRN in number, -- Рег. номер запроса + SCODE in varchar2, -- Мнемокод + SNAME in varchar2 -- Наименование + ) + is + begin + /* Провим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER()); + /* Базовое исправление */ + PKG_P8PANELS_QE_BASE.QUERY_UPDATE(NRN => NRN, SCODE => SCODE, SNAME => SNAME); + end QUERY_UPDATE; + + /* Удаление запроса */ + procedure QUERY_DELETE + ( + NRN in number -- Рег. номер запроса + ) + is + begin + /* Провим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER()); + /* Базовое удаление */ + PKG_P8PANELS_QE_BASE.QUERY_DELETE(NRN => NRN); + end QUERY_DELETE; + + /* Получение описания запроса */ + procedure QUERY_DESC + ( + NRN in number, -- Рег. номер запроса + COUT out clob -- Сериализованное описание запроса + ) + is + begin + /* Проверим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER()); + /* Получим описание запроса */ + COUT := PKG_P8PANELS_QE_BASE.QUERY_DESC_GET(NRN => NRN); + end QUERY_DESC; + + /* Добавление сущности в запрос */ + procedure QUERY_ENT_ADD + ( + NRN in number, -- Рег. номер запроса + SNAME in varchar2, -- Имя + STYPE in varchar2 -- Тип (см. константы SENT_TYPE_*) + ) + is + RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса + RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей + begin + /* Провим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER()); + /* Читаем запись запроса */ + RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN); + /* Читаем существующие сущности */ + RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS); + /* Формируем описание новой сущности и добавляем её в коллекцию */ + PKG_P8PANELS_QE_BASE.TENTS_APPEND(RENTS => RENTS, SNAME => SNAME, STYPE => STYPE); + /* Сохраняем обновленный набор сущностей */ + PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS); + end QUERY_ENT_ADD; + + /* Удаление сущности из запроса */ + procedure QUERY_ENT_REMOVE + ( + NRN in number, -- Рег. номер запроса + SID in varchar2 -- Идентификатор сущности + ) + is + RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса + RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей + RENT PKG_P8PANELS_QE_BASE.TENT; -- Удаляемая сущность + RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей + RRLS_TMP PKG_P8PANELS_QE_BASE.TRLS; -- Буфер для коллекции удаляемых связей + begin + /* Провим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER()); + /* Читаем запись запроса */ + RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN); + /* Читаем существующие сущности */ + RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS); + /* Читаем свзяи */ + RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS); + /* Находим удаляемую сущность */ + begin + RENT := RENTS(PKG_P8PANELS_QE_BASE.TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID)); + exception + when VALUE_ERROR then + P_EXCEPTION(0, + 'Сущность с идентификатором "%s" в запросе "%s" не определена.', + COALESCE(SID, '<НЕ УКАЗАН>'), + TO_CHAR(NRN)); + end; + /* Удаляем сущность из коллекции */ + PKG_P8PANELS_QE_BASE.TENTS_REMOVE(RENTS => RENTS, SID => SID); + /* Обходим атрибуты сущности */ + if ((RENT.RATTRS is not null) and (RENT.RATTRS.COUNT > 0)) then + for I in RENT.RATTRS.FIRST .. RENT.RATTRS.LAST + loop + /* Если атрибут есть в связях (как источник или как приёмник) */ + for J in 0 .. 1 + loop + RRLS_TMP := PKG_P8PANELS_QE_BASE.TRLS_LIST_BY_ST(RRLS => RRLS, + SSOURCE_TARGET => RENT.RATTRS(I).SID, + NLIST_TYPE => J); + /* То связь должна быть удалена */ + if ((RRLS_TMP is not null) and (RRLS_TMP.COUNT > 0)) then + for K in RRLS_TMP.FIRST .. RRLS_TMP.LAST + loop + PKG_P8PANELS_QE_BASE.TRLS_REMOVE(RRLS => RRLS, SID => RRLS_TMP(K).SID); + end loop; + end if; + end loop; + end loop; + end if; + /* Сохраняем обновленный набор сущностей */ + PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS); + /* Сохраняем обновленный набор связей */ + PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS); + end QUERY_ENT_REMOVE; + + /* Установка координат сущности в редакторе диаграммы запроса */ + procedure QUERY_ENT_POSITION_SET + ( + NRN in number, -- Рег. номер запроса + SID in varchar2, -- Идентификатор сущности + NX in number, -- Координата по оси абсцисс + NY in number -- Координата по оси ординат + ) + is + RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса + RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей + begin + /* Читаем запись запроса */ + RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN); + /* Сохранять расположение будем только если это автор - так остальные могут перемещать на экране диаграмму, но не запоминать расположение сущностей */ + if (RQ.AUTHOR = UTILIZER()) then + /* Читаем существующие сущности */ + RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS); + /* Меняем координаты сущности в коллекции */ + PKG_P8PANELS_QE_BASE.TENTS_POSITION_SET(RENTS => RENTS, SID => SID, NX => NX, NY => NY); + /* Сохраняем обновленный набор сущностей */ + PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS); + end if; + end QUERY_ENT_POSITION_SET; + + /* Добавление связи в запрос */ + procedure QUERY_RL_ADD + ( + NRN in number, -- Рег. номер запроса + SSOURCE in varchar2, -- Идентификатор атрибута-источника + STARGET in varchar2 -- Идентификатор атрибута-приёмника + ) + is + RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса + RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей + begin + /* Провим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER()); + /* Читаем запись запроса */ + RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN); + /* Читаем существующие связи */ + RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS); + /* Формируем описание новой связи и добавляем её в коллекцию */ + PKG_P8PANELS_QE_BASE.TRLS_APPEND(RRLS => RRLS, SSOURCE => SSOURCE, STARGET => STARGET); + /* Сохраняем обновленный набор связей */ + PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS); + end QUERY_RL_ADD; + + /* Удаление связи из запроса */ + procedure QUERY_RL_REMOVE + ( + NRN in number, -- Рег. номер запроса + SID in varchar2 -- Идентификатор связи + ) + is + RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса + RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей + begin + /* Провим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER()); + /* Читаем запись запроса */ + RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN); + /* Читаем существующие связи */ + RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS); + /* Удаляем связи из коллекции */ + PKG_P8PANELS_QE_BASE.TRLS_REMOVE(RRLS => RRLS, SID => SID); + /* Сохраняем обновленный набор связей */ + PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS); + end QUERY_RL_REMOVE; + + /* Установка признака "готовности" запроса */ + procedure QUERY_READY_SET + ( + NRN in number, -- Рег. номер запроса + NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да) + ) + is + begin + /* Провим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER()); + /* Базовая установка признака готовности */ + PKG_P8PANELS_QE_BASE.QUERY_READY_SET(NRN => NRN, NREADY => NREADY); + end QUERY_READY_SET; + + /* Установка признака "публичности" запроса */ + procedure QUERY_PBL_SET + ( + NRN in number, -- Рег. номер запроса + NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный) + ) + is + begin + /* Провим права доступа */ + PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER()); + /* Базовая установка признака публичности */ + PKG_P8PANELS_QE_BASE.QUERY_PBL_SET(NRN => NRN, NPBL => NPBL); + end QUERY_PBL_SET; + +end PKG_P8PANELS_QE; +/ diff --git a/db/PKG_P8PANELS_QE_BASE.pck b/db/PKG_P8PANELS_QE_BASE.pck new file mode 100644 index 0000000..2e30f80 --- /dev/null +++ b/db/PKG_P8PANELS_QE_BASE.pck @@ -0,0 +1,1573 @@ +create or replace package PKG_P8PANELS_QE_BASE as + + /* Константы - Типы сущностей */ + SENT_TYPE_TABLE constant PKG_STD.TSTRING := 'TABLE'; -- Таблица + SENT_TYPE_VIEW constant PKG_STD.TSTRING := 'VIEW'; -- Представление + + /* Типы данных - Атрибут сущности */ + type TATTR is record + ( + SID PKG_STD.TSTRING, -- Уникальный идентификатор в запросе + SNAME PKG_STD.TSTRING, -- Имя + STITLE PKG_STD.TSTRING, -- Заголовок + NDATA_TYPE PKG_STD.TNUMBER -- Тип данных (см. константы PKG_STD.DATA_TYPE_*) + ); + + /* Типы данных - Коллекция атрибутов сущности */ + type TATTRS is table of TATTR; + + /* Типы данных - Сущность */ + type TENT is record + ( + SID PKG_STD.TSTRING, -- Уникальный идентификатор в запросе + SNAME PKG_STD.TSTRING, -- Имя + STITLE PKG_STD.TSTRING, -- Заголовок + STYPE PKG_STD.TSTRING, -- Тип (см. константы SENT_TYPE_*) + NX PKG_STD.TNUMBER := 0, -- Координата по оси абсцисс + NY PKG_STD.TNUMBER := 0, -- Координата по оси ординат + RATTRS TATTRS -- Атрибуты + ); + + /* Типы данных - Коллекция сущностей */ + type TENTS is table of TENT; + + /* Типы данных - Отношение */ + type TRL is record + ( + SID PKG_STD.TSTRING, -- Уникальный идентификатор в запросе + SSOURCE PKG_STD.TSTRING, -- Идентификатор атрибута-источника + STARGET PKG_STD.TSTRING -- Идентификатор атрибута-приёмника + ); + + /* Типы данных - Коллекция отношений */ + type TRLS is table of TRL; + + /* Поиск индекса сущности по идентификатору */ + function TENTS_INDEX_BY_ID + ( + RENTS in TENTS, -- Коллекция сущностей + SID in varchar2 -- Искомый идентификатор + ) return number; -- Индекс найденной сущности (null - если не найдено) + + /* Добавление сущности в коллекцию */ + procedure TENTS_APPEND + ( + RENTS in out nocopy TENTS, -- Изменяемая коллекция + SNAME in varchar2, -- Имя + STYPE in varchar2 -- Тип (см. константы SENT_TYPE_*) + ); + + /* Удаление сущности из коллекции */ + procedure TENTS_REMOVE + ( + RENTS in out nocopy TENTS, -- Изменяемая коллекция + SID in varchar2 -- Идентификатор удялемой сущности + ); + + /* Установка координат сущности в коллекции */ + procedure TENTS_POSITION_SET + ( + RENTS in out nocopy TENTS, -- Изменяемая коллекция + SID in varchar2, -- Идентификатор сущности + NX in number, -- Координата по оси абсцисс + NY in number -- Координата по оси ординат + ); + + /* Формирование коллекции связей по источнику/приёмнику */ + function TRLS_LIST_BY_ST + ( + RRLS in TRLS, -- Коллекция связей + SSOURCE_TARGET in varchar2, -- Идентификатор источника/приёмкника + NLIST_TYPE in number -- Тип формирования коллекции (0 - по источнику, 1 - по приёмнику + ) return TRLS; -- Сформированная коллекция + + /* Добавление связи в коллекцию */ + procedure TRLS_APPEND + ( + RRLS in out nocopy TRLS, -- Изменяемая коллекция + SSOURCE in varchar2, -- Источник + STARGET in varchar2 -- Приёмник + ); + + /* Удаление связи из коллекции */ + procedure TRLS_REMOVE + ( + RRLS in out nocopy TRLS, -- Изменяемая коллекция + SID in varchar2 -- Идентификатор удялемой связи + ); + + /* Считывание записи запроса */ + function QUERY_GET + ( + NRN in number -- Рег. номер запроса + ) return P8PNL_QE_QUERY%rowtype; -- Запись запроса + + /* Получение признака возможности изменения запроса */ + function QUERY_ACCESS_SIGN_MODIFY + ( + NRN in number, -- Рег. номер запроса + SUSER in varchar2 -- Имя пользователя + ) return number; -- Признак возможности изменения запроса (0 - нет, 1 - да) + + /* Проверка возможности изменения запроса */ + procedure QUERY_ACCESS_MODIFY + ( + NRN in number, -- Рег. номер запроса + SUSER in varchar2 -- Имя пользователя + ); + + /* Получение признака возможности просмотра запроса */ + function QUERY_ACCESS_SIGN_VIEW + ( + NRN in number, -- Рег. номер запроса + SUSER in varchar2 -- Имя пользователя + ) return number; -- Признак возможности просмотра запроса (0 - нет, 1 - да) + + /* Проверка возможности просмотра запроса */ + procedure QUERY_ACCESS_VIEW + ( + NRN in number, -- Рег. номер запроса + SUSER in varchar2 -- Имя пользователя + ); + + /* Добавление запроса */ + procedure QUERY_INSERT + ( + SCODE in varchar2, -- Мнемокод + SNAME in varchar2, -- Наименование + NRN out number -- Рег. номер добавленного запроса + ); + + /* Исправление запроса */ + procedure QUERY_UPDATE + ( + NRN in number, -- Рег. номер запроса + SCODE in varchar2, -- Мнемокод + SNAME in varchar2 -- Наименование + ); + + /* Удаление запроса */ + procedure QUERY_DELETE + ( + NRN in number -- Рег. номер запроса + ); + + /* Формирование списка запросов */ + function QUERY_LIST_GET + ( + SUSER in varchar2 -- Имя пользователя + ) return clob; -- Список запросов + + /* Получение описания запроса */ + function QUERY_DESC_GET + ( + NRN in number -- Рег. номер запроса + ) return clob; -- XML-описание + + /* Чтение сущностей запроса */ + function QUERY_ENTS_GET + ( + CENTS in clob -- Сериализованное описание сущностей + ) return TENTS; -- Коллекция сущностей + + /* Запись сущностей запроса */ + procedure QUERY_ENTS_SET + ( + NRN in number, -- Рег. номер запроса + RENTS in TENTS -- Коллекция сущностей + ); + + /* Чтение связей запроса */ + function QUERY_RLS_GET + ( + CRLS in clob -- Сериализованное описание связей + ) return TRLS; -- Коллекция связей + + /* Запись связей запроса */ + procedure QUERY_RLS_SET + ( + NRN in number, -- Рег. номер запроса + RRLS in TRLS -- Коллекция связей + ); + + /* Установка признака "готовности" запроса */ + procedure QUERY_READY_SET + ( + NRN in number, -- Рег. номер запроса + NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да) + ); + + /* Установка признака "публичности" запроса */ + procedure QUERY_PBL_SET + ( + NRN in number, -- Рег. номер запроса + NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный) + ); + +end PKG_P8PANELS_QE_BASE; +/ +create or replace package body PKG_P8PANELS_QE_BASE as + + /* Константы - Теги для сериализации */ + STAG_DATA constant PKG_STD.TSTRING := 'XDATA'; -- Данные + STAG_QUERIES constant PKG_STD.TSTRING := 'XQUERIES'; -- Запросы + STAG_QUERY constant PKG_STD.TSTRING := 'XQUERY'; -- Запрос + STAG_ATTRS constant PKG_STD.TSTRING := 'XATTRS'; -- Атрибуты сущности + STAG_ATTR constant PKG_STD.TSTRING := 'XATTR'; -- Атрибут сущности + STAG_ENTS constant PKG_STD.TSTRING := 'XENTS'; -- Сущности + STAG_ENT constant PKG_STD.TSTRING := 'XENT'; -- Сущность + STAG_RLS constant PKG_STD.TSTRING := 'XRLS'; -- Связи + STAG_RL constant PKG_STD.TSTRING := 'XRL'; -- Связь + STAG_OPTS constant PKG_STD.TSTRING := 'XOPTS'; -- Параметры + STAG_OPT constant PKG_STD.TSTRING := 'XOPT'; -- Параметр + + /* Константы - Атрибуты для сериализации */ + SATTR_ID constant PKG_STD.TSTRING := 'id'; -- Идентификатор + SATTR_RN constant PKG_STD.TSTRING := 'rn'; -- Регистрационный номер + SATTR_CODE constant PKG_STD.TSTRING := 'code'; -- Код + SATTR_NAME constant PKG_STD.TSTRING := 'name'; -- Имя + SATTR_AUTHOR constant PKG_STD.TSTRING := 'author'; -- Автор + SATTR_CH_DATE constant PKG_STD.TSTRING := 'chDate'; -- Дата изменения + SATTR_READY constant PKG_STD.TSTRING := 'ready'; -- Готовность к использованию + SATTR_PBL constant PKG_STD.TSTRING := 'pbl'; -- Публичность + SATTR_MODIFY constant PKG_STD.TSTRING := 'modify'; -- Изменяемость + SATTR_TITLE constant PKG_STD.TSTRING := 'title'; -- Заголовок + SATTR_TYPE constant PKG_STD.TSTRING := 'type'; -- Тип + SATTR_DATA_TYPE constant PKG_STD.TSTRING := 'dataType'; -- Тип данных + SATTR_X constant PKG_STD.TSTRING := 'x'; -- Координата по X + SATTR_Y constant PKG_STD.TSTRING := 'y'; -- Координата по Y + SATTR_SOURCE constant PKG_STD.TSTRING := 'source'; -- Источник + SATTR_TARGET constant PKG_STD.TSTRING := 'target'; -- Приёмник + + /* Получение заголовка представления из метаданных */ + function DMSCLVIEWS_TITLE_GET + ( + SVIEW_NAME in varchar2 -- Имя представления + ) return varchar2 -- Заголовок представления из метаданных + is + begin + /* Обратимся к метаописанию представления */ + for V in (select T.VIEW_NOTE, + UL.UNITNAME + from DMSCLVIEWS T, + UNITLIST UL + where T.VIEW_NAME = SVIEW_NAME + and T.CUSTOM_QUERY = 0 + and T.PRN = UL.RN) + loop + if (V.VIEW_NOTE = SVIEW_NAME) then + return V.UNITNAME; + else + return V.VIEW_NOTE; + end if; + end loop; + /* Ничего не нашли - вернём обычное имя */ + return SVIEW_NAME; + end DMSCLVIEWS_TITLE_GET; + + /* Получение заголовка атрибута представления из метаданных */ + function DMSCLVIEWSATTRS_TITLE_GET + ( + SVIEW_NAME in varchar2, -- Имя представления + SATTR_NAME in varchar2 -- Имя атрибута + ) return varchar2 -- Заголовок атрибута представления из метаданных + is + begin + /* Обратимся к метаописанию представления */ + for V in (select T.RN + from DMSCLVIEWS T + where T.VIEW_NAME = SVIEW_NAME + and T.CUSTOM_QUERY = 0) + loop + /* Обходим поля найденного представления */ + for F in (select A.CAPTION + from DMSCLVIEWSATTRS T, + DMSCLATTRS A + where T.PRN = V.RN + and T.COLUMN_NAME = SATTR_NAME + and T.ATTR = A.RN) + loop + return F.CAPTION; + end loop; + end loop; + /* Ничего не нашли - вернём обычное имя */ + return SATTR_NAME; + end DMSCLVIEWSATTRS_TITLE_GET; + + /* Формирование идентификатора атрибута сущности */ + function TATTR_ID_MAKE + ( + SENT_ID in varchar2, -- Уникальный идентификатор родительской сущности + SNAME in varchar2 -- Имя атрибута + ) return varchar2 -- Сформированный идентификатор + is + begin + /* Проверим параметры */ + if (SNAME is null) then + P_EXCEPTION(0, 'Не указано имя атрибута сущности.'); + end if; + if (SENT_ID is null) then + P_EXCEPTION(0, + 'Не указан идентификатор родительской сущности атрибута.'); + end if; + /* Соберем идентификатор */ + return SENT_ID || '.' || SNAME; + end TATTR_ID_MAKE; + + /* Сериализация атрибута сущности */ + procedure TATTR_TO_XML + ( + RATTR in TATTR -- Атрибут сущности + ) + is + begin + /* Открываем описание атрибута сущности */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_ATTR); + /* Атрибут */ + PKG_XFAST.ATTR(SNAME => SATTR_ID, SVALUE => RATTR.SID); + PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RATTR.SNAME); + PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RATTR.STITLE); + PKG_XFAST.ATTR(SNAME => SATTR_DATA_TYPE, NVALUE => RATTR.NDATA_TYPE); + /* Закрываем описание атрибута сущности */ + PKG_XFAST.UP(); + end TATTR_TO_XML; + + /* Десериализация атрибута сущности */ + function TATTR_FROM_XML + ( + CXML in clob -- XML-описание атрибута сущности + ) return TATTR -- Атрибут сущности + is + RRES TATTR; -- Буфер для результата + XDOC PKG_XPATH.TDOCUMENT; -- Документ XML + XROOT PKG_XPATH.TNODE; -- Корень документа XML + XNODE PKG_XPATH.TNODE; -- Буфер узла документа + begin + /* Если данные есть */ + if (CXML is not null) then + begin + /* Разбираем XML */ + XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML); + /* Считываем корневой узел */ + XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); + XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ATTR); + /* Получаем значения */ + RRES.SID := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ID); + RRES.SNAME := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME); + RRES.STITLE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE); + RRES.NDATA_TYPE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_DATA_TYPE); + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + exception + when others then + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end; + end if; + /* Вернём результат */ + return RRES; + end TATTR_FROM_XML; + + /* Сериализация коллекции атрибутов сущности */ + procedure TATTRS_TO_XML + ( + RATTRS in TATTRS -- Коллекция атрибутов сущности + ) + is + begin + /* Открываем описание атрибутов сущности */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_ATTRS); + /* Обходим атрибуты из коллекции */ + if ((RATTRS is not null) and (RATTRS.COUNT > 0)) then + for I in RATTRS.FIRST .. RATTRS.LAST + loop + /* Добавляем описание атрибута сущности */ + TATTR_TO_XML(RATTR => RATTRS(I)); + end loop; + end if; + /* Закрываем описание атрибутов сущности */ + PKG_XFAST.UP(); + end TATTRS_TO_XML; + + /* Десериализация коллекции атрибутов сущности */ + function TATTRS_FROM_XML + ( + CXML in clob -- XML-описание коллекции атрибутов сущности + ) return TATTRS -- Коллекция атрибутов сущности + is + RRES TATTRS; -- Буфер для результата + XDOC PKG_XPATH.TDOCUMENT; -- Документ XML + XROOT PKG_XPATH.TNODE; -- Корень документа XML + XNODE PKG_XPATH.TNODE; -- Буфер узла документа + XNODES PKG_XPATH.TNODES; -- Буфер коллекции узлов документа + begin + /* Инициализируем результат */ + RRES := TATTRS(); + /* Если данные есть */ + if (CXML is not null) then + begin + /* Разбираем XML */ + XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML); + /* Считываем корневой узел */ + XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); + /* Считывание списка атрибутов */ + XNODES := PKG_XPATH.LIST_NODES(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ATTRS || '/' || STAG_ATTR); + /* Цикл по списку атрибутов */ + for I in 1 .. PKG_XPATH.COUNT_NODES(RNODES => XNODES) + loop + /* Считаем элемент по его номеру */ + XNODE := PKG_XPATH.ITEM_NODE(RNODES => XNODES, INUMBER => I); + /* Сериализуем и добавим его в коллекцию */ + RRES.EXTEND(); + RRES(RRES.LAST) := TATTR_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE)); + end loop; + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + exception + when others then + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end; + end if; + /* Вернём результат */ + return RRES; + end TATTRS_FROM_XML; + + /* Формирование идентификатора сущности */ + function TENT_ID_MAKE + ( + SNAME in varchar2, -- Имя сущности + NNUMB in number -- Номер сущности в запросе + ) return varchar2 -- Сформированный идентификатор + is + begin + /* Проверим параметры */ + if (SNAME is null) then + P_EXCEPTION(0, 'Не указано имя сущности.'); + end if; + /* Соберем идентификатор */ + if (NNUMB is null) then + return SNAME; + else + return SNAME || TO_CHAR(NNUMB); + end if; + end TENT_ID_MAKE; + + /* Формирование описания сущности */ + function TENT_MAKE + ( + SNAME in varchar2, -- Имя + STYPE in varchar2, -- Тип (см. константы SENT_TYPE_*) + NNUMB in number -- Номер сущности + ) return TENT -- Описание сущности + is + RENT TENT; -- Буфер для результата + RVIEW PKG_OBJECT_DESC.TVIEW; -- Описание представления + RVIEW_FIELDS PKG_OBJECT_DESC.TCOLUMNS; -- Коллекция описаний полей представления + RVIEW_FIELD PKG_OBJECT_DESC.TCOLUMN; -- Описание поля представления + begin + /* Проверим корректность типа сущности */ + if (STYPE not in (SENT_TYPE_TABLE, SENT_TYPE_VIEW)) then + P_EXCEPTION(0, + 'Сущности типа "%s" не поддерживаются.', + COALESCE(STYPE, '<НЕ УКАЗАН>')); + end if; + /* Если сущность это представление */ + if (STYPE = SENT_TYPE_VIEW) then + /* Получим описание представления */ + RVIEW := PKG_OBJECT_DESC.DESC_VIEW(SVIEW_NAME => SNAME, BRAISE_ERROR => true); + /* Получим список полей представления */ + RVIEW_FIELDS := PKG_OBJECT_DESC.DESC_SEL_COLUMNS(SSELECT_NAME => SNAME, BRAISE_ERROR => true); + /* Собираем заголовок сущности */ + RENT.SID := TENT_ID_MAKE(SNAME => RVIEW.VIEW_NAME, NNUMB => NNUMB); + RENT.SNAME := RVIEW.VIEW_NAME; + RENT.STITLE := DMSCLVIEWS_TITLE_GET(SVIEW_NAME => RENT.SNAME); + RENT.STYPE := SENT_TYPE_VIEW; + RENT.RATTRS := TATTRS(); + /* Собираем атрибуты в ответ */ + for I in 1 .. PKG_OBJECT_DESC.COUNT_COLUMNS(RCOLUMNS => RVIEW_FIELDS) + loop + /* По умолчанию - первые 10 */ + exit when I > 10; + /* Считываем очередное поле из коллекции описания полей представления */ + RVIEW_FIELD := PKG_OBJECT_DESC.FETCH_COLUMN(RCOLUMNS => RVIEW_FIELDS, IINDEX => I); + /* Формируем описание поля и добавляем в коллекцию атрибутов сущности */ + RENT.RATTRS.EXTEND(); + RENT.RATTRS(RENT.RATTRS.LAST).SID := TATTR_ID_MAKE(SENT_ID => RENT.SID, SNAME => RVIEW_FIELD.COLUMN_NAME); + RENT.RATTRS(RENT.RATTRS.LAST).SNAME := RVIEW_FIELD.COLUMN_NAME; + RENT.RATTRS(RENT.RATTRS.LAST).STITLE := DMSCLVIEWSATTRS_TITLE_GET(SVIEW_NAME => RENT.SNAME, + SATTR_NAME => RENT.RATTRS(RENT.RATTRS.LAST) + .SNAME); + RENT.RATTRS(RENT.RATTRS.LAST).NDATA_TYPE := RVIEW_FIELD.DATA_TYPE; + end loop; + end if; + /* Если сущность это таблица */ + if (STYPE = SENT_TYPE_TABLE) then + P_EXCEPTION(0, + 'Поддержка сущностей типа "Таблица" ещё не реализована.'); + end if; + /* Вернем полученное */ + return RENT; + end TENT_MAKE; + + /* Сериализация сущности */ + procedure TENT_TO_XML + ( + RENT in TENT -- Сущность + ) + is + begin + /* Открываем описание сущности */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_ENT); + /* Cущность */ + PKG_XFAST.ATTR(SNAME => SATTR_ID, SVALUE => RENT.SID); + PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RENT.SNAME); + PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RENT.STITLE); + PKG_XFAST.ATTR(SNAME => SATTR_TYPE, SVALUE => RENT.STYPE); + PKG_XFAST.ATTR(SNAME => SATTR_X, NVALUE => RENT.NX); + PKG_XFAST.ATTR(SNAME => SATTR_Y, NVALUE => RENT.NY); + /* Атрибуты */ + TATTRS_TO_XML(RATTRS => RENT.RATTRS); + /* Закрываем описание сущности */ + PKG_XFAST.UP(); + end TENT_TO_XML; + + /* Десериализация сущности */ + function TENT_FROM_XML + ( + CXML in clob -- XML-описание сущности + ) return TENT -- Сущность + is + RRES TENT; -- Буфер для результата + XDOC PKG_XPATH.TDOCUMENT; -- Документ XML + XROOT PKG_XPATH.TNODE; -- Корень документа XML + XNODE PKG_XPATH.TNODE; -- Буфер узла документа + begin + /* Инициализируем сущность */ + RRES.RATTRS := TATTRS(); + /* Если данные есть */ + if (CXML is not null) then + begin + /* Разбираем XML */ + XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML); + /* Считываем корневой узел */ + XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); + /* Считаваем узел сущности */ + XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ENT); + /* Получаем значения */ + RRES.SID := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ID); + RRES.SNAME := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME); + RRES.STITLE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE); + RRES.STYPE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TYPE); + RRES.NX := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_X); + RRES.NY := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_Y); + RRES.RATTRS := TATTRS_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XNODE, + SPATTERN => STAG_ATTRS))); + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + exception + when others then + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end; + end if; + /* Вернём результат */ + return RRES; + end TENT_FROM_XML; + + /* Поиск индекса сущности по идентификатору */ + function TENTS_INDEX_BY_ID + ( + RENTS in TENTS, -- Коллекция сущностей + SID in varchar2 -- Искомый идентификатор + ) return number -- Индекс найденной сущности (null - если не найдено) + is + begin + /* Обходим коллекцию */ + if ((RENTS is not null) and (RENTS.COUNT > 0)) then + for I in RENTS.FIRST .. RENTS.LAST + loop + begin + /* Возвращаем найденный индекс */ + if (RENTS(I).SID = SID) then + return I; + end if; + exception + when NO_DATA_FOUND then + null; + end; + end loop; + end if; + /* Ничего не нашли */ + return null; + end TENTS_INDEX_BY_ID; + + /* Поиск номера сущности в коллекции */ + function TENTS_NEXT_NUMB + ( + RENTS in TENTS, -- Коллекция сущностей + SNAME in varchar2 -- Имя + ) return number -- Номер сущности в коллекции + is + NNUMB PKG_STD.TNUMBER := 0; -- Буфер для результата + begin + /* Подбираем первый свободный номер */ + while (TENTS_INDEX_BY_ID(RENTS => RENTS, SID => TENT_ID_MAKE(SNAME => SNAME, NNUMB => NNUMB)) is not null) + loop + NNUMB := NNUMB + 1; + end loop; + /* Возвращаем его */ + return NNUMB; + end TENTS_NEXT_NUMB; + + /* Добавление сущности в коллекцию */ + procedure TENTS_APPEND + ( + RENTS in out nocopy TENTS, -- Изменяемая коллекция + SNAME in varchar2, -- Имя + STYPE in varchar2 -- Тип (см. константы SENT_TYPE_*) + ) + is + RENT TENT; -- Добавляемая сущность + begin + /* Инициализируем коллекцию если необходимо */ + if (RENTS is null) then + RENTS := TENTS(); + end if; + /* Формируем пописание сущности */ + RENT := TENT_MAKE(SNAME => SNAME, STYPE => STYPE, NNUMB => TENTS_NEXT_NUMB(RENTS => RENTS, SNAME => SNAME)); + /* Добавляем её в коллекцию */ + RENTS.EXTEND(); + RENTS(RENTS.LAST) := RENT; + end TENTS_APPEND; + + /* Удаление сущности из коллекции */ + procedure TENTS_REMOVE + ( + RENTS in out nocopy TENTS, -- Изменяемая коллекция + SID in varchar2 -- Идентификатор удялемой сущности + ) + is + NIND PKG_STD.TNUMBER; -- Индекс сущности в коллекции + begin + NIND := TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID); + if (NIND is not null) then + RENTS.DELETE(NIND); + end if; + end TENTS_REMOVE; + + /* Установка координат сущности в коллекции */ + procedure TENTS_POSITION_SET + ( + RENTS in out nocopy TENTS, -- Изменяемая коллекция + SID in varchar2, -- Идентификатор сущности + NX in number, -- Координата по оси абсцисс + NY in number -- Координата по оси ординат + ) + is + NIND PKG_STD.TNUMBER; -- Индекс сущности в коллекции + begin + NIND := TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID); + if (NIND is not null) then + RENTS(NIND).NX := NX; + RENTS(NIND).NY := NY; + end if; + end TENTS_POSITION_SET; + + /* Сериализация коллекции сущностей */ + procedure TENTS_TO_XML + ( + RENTS in TENTS -- Коллекция сущностей + ) + is + begin + /* Открываем описание сущностей */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_ENTS); + /* Обходим сущности из коллекции */ + if ((RENTS is not null) and (RENTS.COUNT > 0)) then + for I in RENTS.FIRST .. RENTS.LAST + loop + begin + /* Добавляем описание сущности */ + TENT_TO_XML(RENT => RENTS(I)); + exception + when NO_DATA_FOUND then + null; + end; + end loop; + end if; + /* Закрываем описание сущностей */ + PKG_XFAST.UP(); + end TENTS_TO_XML; + + /* Десериализация коллекции сущностей */ + function TENTS_FROM_XML + ( + CXML in clob -- XML-описание коллекции сущностей + ) return TENTS -- Коллекция сущностей + is + RRES TENTS; -- Буфер для результата + XDOC PKG_XPATH.TDOCUMENT; -- Документ XML + XROOT PKG_XPATH.TNODE; -- Корень документа XML + XNODE PKG_XPATH.TNODE; -- Буфер узла документа + XNODES PKG_XPATH.TNODES; -- Буфер коллекции узлов документа + begin + /* Инициализируем результат */ + RRES := TENTS(); + /* Если данные есть */ + if (CXML is not null) then + begin + /* Разбираем XML */ + XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML); + /* Считываем корневой узел */ + XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); + /* Считывание списка сущностей */ + XNODES := PKG_XPATH.LIST_NODES(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ENTS || '/' || STAG_ENT); + /* Цикл по списку сущностей */ + for I in 1 .. PKG_XPATH.COUNT_NODES(RNODES => XNODES) + loop + /* Считаем элемент по его номеру */ + XNODE := PKG_XPATH.ITEM_NODE(RNODES => XNODES, INUMBER => I); + /* Сериализуем и добавим его в коллекцию */ + RRES.EXTEND(); + RRES(RRES.LAST) := TENT_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE)); + end loop; + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + exception + when others then + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end; + end if; + /* Вернём результат */ + return RRES; + end TENTS_FROM_XML; + + /* Формирование идентификатора связи */ + function TRL_ID_MAKE + ( + SSOURCE in varchar2, -- Источник + STARGET in varchar2 -- Приёмник + ) return varchar2 -- Сформированный идентификатор + is + begin + /* Проверим параметры */ + if (SSOURCE is null) then + P_EXCEPTION(0, 'Не указан источник связи.'); + end if; + if (STARGET is null) then + P_EXCEPTION(0, 'Не указан приёмник связи.'); + end if; + /* Соберем идентификатор */ + return SSOURCE || '-' || STARGET; + end TRL_ID_MAKE; + + /* Формирование описания связи */ + function TRL_MAKE + ( + SID in varchar2 := null, -- Идентификатор (null - автоформирование) + SSOURCE in varchar2, -- Источник + STARGET in varchar2 -- Приёмник + ) return TRL -- Описание связи + is + RRL TRL; -- Буфер для результата + begin + /* Собираем описание связи */ + RRL.SID := COALESCE(SID, TRL_ID_MAKE(SSOURCE => SSOURCE, STARGET => STARGET)); + RRL.SSOURCE := SSOURCE; + RRL.STARGET := STARGET; + /* Вернем полученное */ + return RRL; + end TRL_MAKE; + + /* Сериализация связи */ + procedure TRL_TO_XML + ( + RRL in TRL -- Связь + ) + is + begin + /* Открываем описание связи */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_RL); + /* Связь */ + PKG_XFAST.ATTR(SNAME => SATTR_ID, SVALUE => RRL.SID); + PKG_XFAST.ATTR(SNAME => SATTR_SOURCE, SVALUE => RRL.SSOURCE); + PKG_XFAST.ATTR(SNAME => SATTR_TARGET, SVALUE => RRL.STARGET); + /* Закрываем описание связи */ + PKG_XFAST.UP(); + end TRL_TO_XML; + + /* Десериализация связи */ + function TRL_FROM_XML + ( + CXML in clob -- XML-описание связи + ) return TRL -- Связь + is + RRES TRL; -- Буфер для результата + XDOC PKG_XPATH.TDOCUMENT; -- Документ XML + XROOT PKG_XPATH.TNODE; -- Корень документа XML + XNODE PKG_XPATH.TNODE; -- Буфер узла документа + begin + /* Если данные есть */ + if (CXML is not null) then + begin + /* Разбираем XML */ + XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML); + /* Считываем корневой узел */ + XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); + /* Считаваем узел связи */ + XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_RL); + /* Получаем значения */ + RRES.SID := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ID); + RRES.SSOURCE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_SOURCE); + RRES.STARGET := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TARGET); + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + exception + when others then + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end; + end if; + /* Вернём результат */ + return RRES; + end TRL_FROM_XML; + + /* Поиск индекса связи по идентификатору */ + function TRLS_INDEX_BY_ID + ( + RRLS in TRLS, -- Коллекция связей + SID in varchar2 -- Искомый идентификатор + ) return number -- Индекс найденной связи (null - если не найдено) + is + begin + /* Обходим коллекцию */ + if ((RRLS is not null) and (RRLS.COUNT > 0)) then + for I in RRLS.FIRST .. RRLS.LAST + loop + begin + /* Возвращаем найденный индекс */ + if (RRLS(I).SID = SID) then + return I; + end if; + exception + when NO_DATA_FOUND then + null; + end; + end loop; + end if; + /* Ничего не нашли */ + return null; + end TRLS_INDEX_BY_ID; + + /* Формирование коллекции связей по источнику/приёмнику */ + function TRLS_LIST_BY_ST + ( + RRLS in TRLS, -- Коллекция связей + SSOURCE_TARGET in varchar2, -- Идентификатор источника/приёмкника + NLIST_TYPE in number -- Тип формирования коллекции (0 - по источнику, 1 - по приёмнику + ) return TRLS -- Сформированная коллекция + is + RRES TRLS; -- Буфер для результата + begin + /* Инициализируем результат */ + RRES := TRLS(); + /* Обходим входную коллекцию */ + if ((RRLS is not null) and (RRLS.COUNT > 0)) then + for I in RRLS.FIRST .. RRLS.LAST + loop + begin + /* Формируем выходную коллекцию */ + if (((NLIST_TYPE = 0) and (RRLS(I).SSOURCE = SSOURCE_TARGET)) or + ((NLIST_TYPE = 1) and (RRLS(I).STARGET = SSOURCE_TARGET))) then + RRES.EXTEND(); + RRES(RRES.LAST) := TRL_MAKE(SID => RRLS(I).SID, SSOURCE => RRLS(I).SSOURCE, STARGET => RRLS(I).STARGET); + end if; + exception + when NO_DATA_FOUND then + null; + end; + end loop; + end if; + /* Вернем результат */ + return RRES; + end TRLS_LIST_BY_ST; + + /* Добавление связи в коллекцию */ + procedure TRLS_APPEND + ( + RRLS in out nocopy TRLS, -- Изменяемая коллекция + SSOURCE in varchar2, -- Источник + STARGET in varchar2 -- Приёмник + ) + is + RRL TRL; -- Добавляемая связь + begin + /* Инициализируем коллекцию если необходимо */ + if (RRLS is null) then + RRLS := TRLS(); + end if; + /* Формируем пописание связи */ + RRL := TRL_MAKE(SSOURCE => SSOURCE, STARGET => STARGET); + /* Добавляем её в коллекцию */ + RRLS.EXTEND(); + RRLS(RRLS.LAST) := RRL; + end TRLS_APPEND; + + /* Удаление связи из коллекции */ + procedure TRLS_REMOVE + ( + RRLS in out nocopy TRLS, -- Изменяемая коллекция + SID in varchar2 -- Идентификатор удялемой связи + ) + is + NIND PKG_STD.TNUMBER; -- Индекс связи в коллекции + begin + NIND := TRLS_INDEX_BY_ID(RRLS => RRLS, SID => SID); + if (NIND is not null) then + RRLS.DELETE(NIND); + end if; + end TRLS_REMOVE; + + /* Сериализация коллекции связей */ + procedure TRLS_TO_XML + ( + RRLS in TRLS -- Коллекция связей + ) + is + begin + /* Открываем описание связей */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_RLS); + /* Обходим связи из коллекции */ + if ((RRLS is not null) and (RRLS.COUNT > 0)) then + for I in RRLS.FIRST .. RRLS.LAST + loop + begin + /* Добавляем описание связи */ + TRL_TO_XML(RRL => RRLS(I)); + exception + when NO_DATA_FOUND then + null; + end; + end loop; + end if; + /* Закрываем описание связей */ + PKG_XFAST.UP(); + end TRLS_TO_XML; + + /* Десериализация коллекции связей */ + function TRLS_FROM_XML + ( + CXML in clob -- XML-описание коллекции связей + ) return TRLS -- Коллекция связей + is + RRES TRLS; -- Буфер для результата + XDOC PKG_XPATH.TDOCUMENT; -- Документ XML + XROOT PKG_XPATH.TNODE; -- Корень документа XML + XNODE PKG_XPATH.TNODE; -- Буфер узла документа + XNODES PKG_XPATH.TNODES; -- Буфер коллекции узлов документа + begin + /* Инициализируем результат */ + RRES := TRLS(); + /* Если данные есть */ + if (CXML is not null) then + begin + /* Разбираем XML */ + XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML); + /* Считываем корневой узел */ + XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); + /* Считывание списка связей */ + XNODES := PKG_XPATH.LIST_NODES(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_RLS || '/' || STAG_RL); + /* Цикл по списку связей */ + for I in 1 .. PKG_XPATH.COUNT_NODES(RNODES => XNODES) + loop + /* Считаем элемент по его номеру */ + XNODE := PKG_XPATH.ITEM_NODE(RNODES => XNODES, INUMBER => I); + /* Сериализуем и добавим его в коллекцию */ + RRES.EXTEND(); + RRES(RRES.LAST) := TRL_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE)); + end loop; + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + exception + when others then + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end; + end if; + /* Вернём результат */ + return RRES; + end TRLS_FROM_XML; + + /* Считывание записи запроса */ + function QUERY_GET + ( + NRN in number -- Рег. номер запроса + ) return P8PNL_QE_QUERY%rowtype -- Запись запроса + is + RRES P8PNL_QE_QUERY%rowtype; -- Буфер для результата + begin + select T.* into RRES from P8PNL_QE_QUERY T where T.RN = NRN; + return RRES; + exception + when NO_DATA_FOUND then + PKG_MSG.RECORD_NOT_FOUND(NFLAG_SMART => 0, NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY'); + end QUERY_GET; + + /* Получение признака возможности изменения запроса */ + function QUERY_ACCESS_SIGN_MODIFY + ( + NRN in number, -- Рег. номер запроса + SUSER in varchar2 -- Имя пользователя + ) return number -- Признак возможности изменения запроса (0 - нет, 1 - да) + is + RQ P8PNL_QE_QUERY%rowtype; -- Проверяемая запись запроса + begin + /* Читаем запрос */ + RQ := QUERY_GET(NRN => NRN); + /* Менять можно только свой запрос */ + if (RQ.AUTHOR = SUSER) then + return 1; + end if; + /* Проверки не пройдены - менять нельзя */ + return 0; + end QUERY_ACCESS_SIGN_MODIFY; + + /* Проверка возможности изменения запроса */ + procedure QUERY_ACCESS_MODIFY + ( + NRN in number, -- Рег. номер запроса + SUSER in varchar2 -- Имя пользователя + ) + is + begin + /* Получим признак возможности измнения */ + if (QUERY_ACCESS_SIGN_MODIFY(NRN => NRN, SUSER => SUSER) = 0) then + /* Менять нельзя */ + P_EXCEPTION(0, 'У Вас нет прав доступа для измнения запроса.'); + end if; + end QUERY_ACCESS_MODIFY; + + /* Получение признака возможности просмотра запроса */ + function QUERY_ACCESS_SIGN_VIEW + ( + NRN in number, -- Рег. номер запроса + SUSER in varchar2 -- Имя пользователя + ) return number -- Признак возможности просмотра запроса (0 - нет, 1 - да) + is + RQ P8PNL_QE_QUERY%rowtype; -- Проверяемая запись запроса + begin + /* Читаем запрос */ + RQ := QUERY_GET(NRN => NRN); + /* Смотреть можно только свой или публичный запрос */ + if ((RQ.PBL = 1) or (RQ.AUTHOR = SUSER)) then + return 1; + end if; + /* Проверки не пройдены - нельзя смотреть */ + return 0; + end QUERY_ACCESS_SIGN_VIEW; + + /* Проверка возможности просмотра запроса */ + procedure QUERY_ACCESS_VIEW + ( + NRN in number, -- Рег. номер запроса + SUSER in varchar2 -- Имя пользователя + ) + is + begin + /* Получим признак возможности просмотра */ + if (QUERY_ACCESS_SIGN_VIEW(NRN => NRN, SUSER => SUSER) = 0) then + /* Смотреть нельзя */ + P_EXCEPTION(0, 'У Вас нет прав доступа для просмотра запроса.'); + end if; + end QUERY_ACCESS_VIEW; + + /* Проверка атрибутов запроса */ + procedure QUERY_CHECK + ( + SCODE in varchar2, -- Мнемокод + SNAME in varchar2 -- Наименование + ) + is + begin + /* Мнемокод должен быть задан */ + if (SCODE is null) then + P_EXCEPTION(0, 'Не задан мнемокод запроса.'); + end if; + /* Наименование должно быть задано */ + if (SNAME is null) then + P_EXCEPTION(0, 'Не задано наименование запроса.'); + end if; + end QUERY_CHECK; + + /* Синхронизация даты изменения запроса с текущим временем */ + procedure QUERY_CH_DATE_SYNC + ( + NRN in number -- Рег. номер запроса + ) + is + begin + /* Установим текущую дату изменения */ + update P8PNL_QE_QUERY T set T.CH_DATE = sysdate where T.RN = NRN; + /* Контроль изменения данных */ + if (sql%notfound) then + PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY'); + end if; + end QUERY_CH_DATE_SYNC; + + /* Добавление запроса */ + procedure QUERY_INSERT + ( + SCODE in varchar2, -- Мнемокод + SNAME in varchar2, -- Наименование + NRN out number -- Рег. номер добавленного запроса + ) + is + begin + /* Проверим параметры */ + QUERY_CHECK(SCODE => SCODE, SNAME => SNAME); + /* Формируем рег. номер */ + NRN := GEN_ID(); + /* Добавляем данные */ + insert into P8PNL_QE_QUERY + (RN, CODE, name, AUTHOR, CH_DATE, READY, PBL) + values + (NRN, SCODE, SNAME, UTILIZER(), sysdate, 0, 0); + end QUERY_INSERT; + + /* Исправление запроса */ + procedure QUERY_UPDATE + ( + NRN in number, -- Рег. номер запроса + SCODE in varchar2, -- Мнемокод + SNAME in varchar2 -- Наименование + ) + is + begin + /* Проверим параметры */ + QUERY_CHECK(SCODE => SCODE, SNAME => SNAME); + /* Изменяем данные */ + update P8PNL_QE_QUERY T + set T.CODE = SCODE, + T.NAME = SNAME + where T.RN = NRN; + /* Контроль изменения данных */ + if (sql%notfound) then + PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY'); + end if; + /* Обновим дату изменения запроса */ + QUERY_CH_DATE_SYNC(NRN => NRN); + end QUERY_UPDATE; + + /* Удаление запроса */ + procedure QUERY_DELETE + ( + NRN in number -- Рег. номер запроса + ) + is + begin + /* Удаляем запись */ + delete from P8PNL_QE_QUERY T where T.RN = NRN; + /* Контроль изменения данных */ + if (sql%notfound) then + PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY'); + end if; + end QUERY_DELETE; + + /* Сериализация сущностей запроса */ + function QUERY_ENTS_TO_XML + ( + RENTS in TENTS -- Коллекция сущностей + ) return clob -- XML-описание + is + CRES clob; -- Буфер для результата + begin + /* Если сущности есть */ + if ((RENTS is not null) and (RENTS.COUNT > 0)) then + /* Начинаем формирование XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + /* Добавляем сущности */ + TENTS_TO_XML(RENTS => RENTS); + /* Сериализуем */ + CRES := PKG_XFAST.SERIALIZE_TO_CLOB(); + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + else + CRES := null; + end if; + /* Возвращаем полученное */ + return CRES; + exception + when others then + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end QUERY_ENTS_TO_XML; + + /* Десериализация сущностей запроса */ + function QUERY_ENTS_FROM_XML + ( + CXML in clob -- XML-описание коллекции сущностей запроса + ) return TENTS -- Коллекция сущностей + is + RENTS TENTS; -- Буфер для результата + XDOC PKG_XPATH.TDOCUMENT; -- Документ XML + XROOT PKG_XPATH.TNODE; -- Корень документа XML + XNODE PKG_XPATH.TNODE; -- Буфер узла документа + begin + /* Инициализируем результат */ + RENTS := TENTS(); + /* Если данные есть */ + if (CXML is not null) then + begin + /* Разбираем XML */ + XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML); + /* Считываем корневой узел */ + XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); + /* Считываем узел сущностей */ + XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ENTS); + /* Десериализуем его */ + RENTS := TENTS_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE)); + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + exception + when others then + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end; + end if; + /* Вернём сформированное */ + return RENTS; + end QUERY_ENTS_FROM_XML; + + /* Сериализация связей запроса */ + function QUERY_RLS_TO_XML + ( + RRLS in TRLS -- Коллекция связей + ) return clob -- XML-описание + is + CRES clob; -- Буфер для результата + begin + /* Если связи есть */ + if ((RRLS is not null) and (RRLS.COUNT > 0)) then + /* Начинаем формирование XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_); + /* Добавляем связи */ + TRLS_TO_XML(RRLS => RRLS); + /* Сериализуем */ + CRES := PKG_XFAST.SERIALIZE_TO_CLOB(); + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + else + CRES := null; + end if; + /* Возвращаем полученное */ + return CRES; + exception + when others then + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end QUERY_RLS_TO_XML; + + /* Десериализация связей запроса */ + function QUERY_RLS_FROM_XML + ( + CXML in clob -- XML-описание коллекции связей запроса + ) return TRLS -- Коллекция связей + is + RRLS TRLS; -- Буфер для результата + XDOC PKG_XPATH.TDOCUMENT; -- Документ XML + XROOT PKG_XPATH.TNODE; -- Корень документа XML + XNODE PKG_XPATH.TNODE; -- Буфер узла документа + begin + /* Инициализируем результат */ + RRLS := TRLS(); + /* Если данные есть */ + if (CXML is not null) then + begin + /* Разбираем XML */ + XDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CXML); + /* Считываем корневой узел */ + XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); + /* Считываем узел связей */ + XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_RLS); + /* Десериализуем его */ + RRLS := TRLS_FROM_XML(CXML => PKG_XPATH.SERIALIZE_TO_CLOB(RNODE => XNODE)); + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + exception + when others then + /* Освободим документ */ + PKG_XPATH.FREE(RDOCUMENT => XDOC); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end; + end if; + /* Вернём сформированное */ + return RRLS; + end QUERY_RLS_FROM_XML; + + /* Сериализация запроса */ + function QUERY_TO_XML + ( + NRN in number -- Рег. номер запроса + ) return clob -- XML-описание + is + RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса + CRES clob; -- Буфер для сериализации + begin + /* Читаем запрос */ + RQ := QUERY_GET(NRN => NRN); + /* Начинаем формирование XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true); + /* Открываем корень */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA); + /* Параметры */ + if (RQ.OPTS is not null) then + null; + end if; + /* Сущности (можно использовать просто PKG_XFAST.VALUE_XML и сразу отдать готовый XML из RQ.ENTS, но это приводит к формированию некорректного документа с лишней ">" между группами) */ + if (RQ.ENTS is not null) then + TENTS_TO_XML(RENTS => QUERY_ENTS_FROM_XML(CXML => RQ.ENTS)); + end if; + /* Связи (можно использовать просто PKG_XFAST.VALUE_XML и сразу отдать готовый XML из RQ.RLS, но это приводит к формированию некорректного документа с лишней ">" между группами) */ + if (RQ.RLS is not null) then + TRLS_TO_XML(RRLS => QUERY_RLS_FROM_XML(CXML => RQ.RLS)); + end if; + /* Закрываем корень */ + PKG_XFAST.UP(); + /* Сериализуем */ + CRES := PKG_XFAST.SERIALIZE_TO_CLOB(); + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Возвращаем результат */ + return CRES; + exception + when others then + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end QUERY_TO_XML; + + /* Формирование списка запросов */ + function QUERY_LIST_GET + ( + SUSER in varchar2 -- Имя пользователя + ) return clob -- Список запросов + is + CRES clob; -- Буфер для сериализации + begin + /* Начинаем формирование XML */ + PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true); + /* Открываем корень */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA); + /* Открываем список запросов */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_QUERIES); + /* Обходим запросы - данного пользователя и публичные */ + for C in (select T.RN NRN, + T.CODE SCODE, + T.NAME SNAME, + UL.NAME SAUTHOR, + TO_CHAR(T.CH_DATE, 'dd.mm.yyyy hh24:mi:ss') SCH_DATE, + T.READY NREADY, + T.PBL NPBL, + QUERY_ACCESS_SIGN_MODIFY(T.RN, SUSER) NMODIFY + from P8PNL_QE_QUERY T, + USERLIST UL + where T.AUTHOR = UL.AUTHID + and QUERY_ACCESS_SIGN_VIEW(T.RN, SUSER) = 1 + order by T.CODE) + loop + /* Открываем описание запроса */ + PKG_XFAST.DOWN_NODE(SNAME => STAG_QUERY); + /* Запрос */ + PKG_XFAST.ATTR(SNAME => SATTR_RN, NVALUE => C.NRN); + PKG_XFAST.ATTR(SNAME => SATTR_CODE, SVALUE => C.SCODE); + PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => C.SNAME); + PKG_XFAST.ATTR(SNAME => SATTR_AUTHOR, SVALUE => C.SAUTHOR); + PKG_XFAST.ATTR(SNAME => SATTR_CH_DATE, SVALUE => C.SCH_DATE); + PKG_XFAST.ATTR(SNAME => SATTR_READY, NVALUE => C.NREADY); + PKG_XFAST.ATTR(SNAME => SATTR_PBL, NVALUE => C.NPBL); + PKG_XFAST.ATTR(SNAME => SATTR_MODIFY, NVALUE => C.NMODIFY); + /* Закрываем описание запроса */ + PKG_XFAST.UP(); + end loop; + /* Закрываем список запросов */ + PKG_XFAST.UP(); + /* Закрываем корень */ + PKG_XFAST.UP(); + /* Сериализуем */ + CRES := PKG_XFAST.SERIALIZE_TO_CLOB(); + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Возвращаем результат */ + return CRES; + exception + when others then + /* Завершаем формирование XML */ + PKG_XFAST.EPILOGUE(); + /* Вернем ошибку */ + PKG_STATE.DIAGNOSTICS_STACKED(); + P_EXCEPTION(0, PKG_STATE.SQL_ERRM()); + end QUERY_LIST_GET; + + /* Получение описания запроса */ + function QUERY_DESC_GET + ( + NRN in number -- Рег. номер запроса + ) return clob -- XML-описание + is + begin + /* Сериализуем запрос */ + return QUERY_TO_XML(NRN => NRN); + end QUERY_DESC_GET; + + /* Чтение сущностей запроса */ + function QUERY_ENTS_GET + ( + CENTS in clob -- Сериализованное описание сущностей + ) return TENTS -- Коллекция сущностей + is + begin + /* Десериализуем */ + return QUERY_ENTS_FROM_XML(CXML => CENTS); + end QUERY_ENTS_GET; + + /* Запись сущностей запроса */ + procedure QUERY_ENTS_SET + ( + NRN in number, -- Рег. номер запроса + RENTS in TENTS -- Коллекция сущностей + ) + is + CENTS clob; -- Буфер для сериализации + begin + /* Сериализуем полученную коллекцию сущностей */ + CENTS := QUERY_ENTS_TO_XML(RENTS => RENTS); + /* Сохраним её */ + update P8PNL_QE_QUERY T set T.ENTS = CENTS where T.RN = NRN; + /* Контроль изменения данных */ + if (sql%notfound) then + PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY'); + end if; + /* Обновим дату изменения запроса */ + QUERY_CH_DATE_SYNC(NRN => NRN); + end QUERY_ENTS_SET; + + /* Чтение связей запроса */ + function QUERY_RLS_GET + ( + CRLS in clob -- Сериализованное описание связей + ) return TRLS -- Коллекция связей + is + begin + /* Десериализуем */ + return QUERY_RLS_FROM_XML(CXML => CRLS); + end QUERY_RLS_GET; + + /* Запись связей запроса */ + procedure QUERY_RLS_SET + ( + NRN in number, -- Рег. номер запроса + RRLS in TRLS -- Коллекция связей + ) + is + CRLS clob; -- Буфер для сериализации + begin + /* Сериализуем полученную коллекцию связей */ + CRLS := QUERY_RLS_TO_XML(RRLS => RRLS); + /* Сохраним её */ + update P8PNL_QE_QUERY T set T.RLS = CRLS where T.RN = NRN; + /* Контроль изменения данных */ + if (sql%notfound) then + PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY'); + end if; + /* Обновим дату изменения запроса */ + QUERY_CH_DATE_SYNC(NRN => NRN); + end QUERY_RLS_SET; + + /* Установка признака "готовности" запроса */ + procedure QUERY_READY_SET + ( + NRN in number, -- Рег. номер запроса + NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да) + ) + is + begin + /* Проверим параметры */ + if (NREADY is null) then + P_EXCEPTION(0, + 'Не задано значение признака готовности запроса к использованию.'); + end if; + if (NREADY not in (0, 1)) then + P_EXCEPTION(0, + 'Значение признака готовности запроса к использованию задано некорректно (ожидалось 0 или 1).'); + end if; + /* Установим флаг готовности к использованию */ + update P8PNL_QE_QUERY T set T.READY = NREADY where T.RN = NRN; + /* Контроль изменения данных */ + if (sql%notfound) then + PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY'); + end if; + /* Обновим дату изменения запроса */ + QUERY_CH_DATE_SYNC(NRN => NRN); + end QUERY_READY_SET; + + /* Установка признака "публичности" запроса */ + procedure QUERY_PBL_SET + ( + NRN in number, -- Рег. номер запроса + NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный) + ) + is + begin + /* Проверим параметры */ + if (NPBL is null) then + P_EXCEPTION(0, 'Не задано значение признака публичности запроса.'); + end if; + if (NPBL not in (0, 1)) then + P_EXCEPTION(0, + 'Значение признака публичноти запроса задано некорректно (ожидалось 0 или 1).'); + end if; + /* Установим флаг публичности */ + update P8PNL_QE_QUERY T set T.PBL = NPBL where T.RN = NRN; + /* Контроль изменения данных */ + if (sql%notfound) then + PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_QE_QUERY'); + end if; + /* Обновим дату изменения запроса */ + QUERY_CH_DATE_SYNC(NRN => NRN); + end QUERY_PBL_SET; + +end PKG_P8PANELS_QE_BASE; +/ diff --git a/db/grants.sql b/db/grants.sql index c12f082..34d7065 100644 --- a/db/grants.sql +++ b/db/grants.sql @@ -2,4 +2,6 @@ grant execute on PKG_P8PANELS to public; grant execute on PKG_P8PANELS_PROJECTS to public; grant execute on PKG_P8PANELS_SAMPLES to public; grant execute on PKG_P8PANELS_EQUIPSRV to public; -grant execute on PKG_P8PANELS_RRPCONFED to public; \ No newline at end of file +grant execute on PKG_P8PANELS_RRPCONFED to public; +grant execute on PKG_P8PANELS_PE to public; +grant execute on PKG_P8PANELS_QE to public;