diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6ef3f56..3154434 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.0.1 +current_version = 1.0.2 commit = False tag = False serialize = diff --git a/.gitignore b/.gitignore index 982ad76..c9321ea 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ sqlcl/ **/node_modules/ **/dist/ example/compose-stack/transform.mjs +example/compose-stack/transform.mjs +example/compose-stack/dataset/*.sql example/**/package-lock.json docs/.vitepress/cache docs/api diff --git a/.version b/.version index 7f20734..e6d5cb8 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -1.0.1 \ No newline at end of file +1.0.2 \ No newline at end of file diff --git a/README.md b/README.md index d732caa..1b156cd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # `@linkurious/ogma-oracle-parser` -![logo](/logo.svg) +![@linkurious/ogma-oracle-parser Logo](https://raw.githubusercontent.com/Linkurious/ogma-oracle-parser/develop/logo.svg) -Seamless comunication between [Ogma](https://doc.linkurious.com/ogma/latest/) and [Oracle SQL graphs](https://docs.oracle.com/en//database/oracle/property-graph/23.3/spgdg/sql-property-graphs.html). +Seamless comunication between [Ogma](https://doc.linkurious.com/ogma/latest/) and [Oracle Database 23ai SQL Property Graphs](https://docs.oracle.com/database/oracle/property-graph/23.4/spgdg/sql-property-graphs.html). If you don't have an Ogma licence, [contact us](https://doc.linkurious.com/ogma/latest/contact.html). diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index ab36c6a..be1b71c 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,60 +1,64 @@ -import { DefaultTheme, defineConfig } from 'vitepress'; -import fs from 'fs/promises'; +import { DefaultTheme, defineConfig } from "vitepress"; +import fs from "fs/promises"; // recursively go into input dir and create a vitepress sidebar config function createSidebar(dir) { - return fs.readdir(dir, { withFileTypes: true }) - .then(files => { - const promises = files.map(file => { + return fs + .readdir(dir, { withFileTypes: true }) + .then((files) => { + const promises = files.map((file) => { if (file.isDirectory()) { return createSidebar(`${dir}/${file.name}`); - } else if (file.name.endsWith('.md')) { + } else if (file.name.endsWith(".md")) { return { - text: file.name.replace('.md', ''), - link: `${dir.replace('docs/', '')}/${file.name.replace('.md', '')}`, + text: file.name.replace(".md", ""), + link: `${dir.replace("docs/", "")}/${file.name.replace(".md", "")}`, }; } }); return Promise.all(promises); }) - .then(res => { - return res.filter(r => r); + .then((res) => { + return res.filter((r) => r); }); } -const classes = await createSidebar('docs/api/classes'); +const classes = await createSidebar("docs/api/classes"); // const interfaces = await createSidebar('docs/api/interfaces'); const sidebar: DefaultTheme.Sidebar = { - 'api/': [ + "api/": [ { - text: 'OgmaOracleParser', - link: 'api/classes/OgmaOracleParser' + text: "OgmaOracleParser", + link: "api/classes/OgmaOracleParser", }, { - text: 'Types', - link: 'api/modules' + text: "Types", + link: "api/modules", }, ], }; // https://vitepress.dev/reference/site-config export default defineConfig({ title: "ogma-oracle-parser", - description: "Seamless Ogma and Oracle Property Graph integration", - head: [['link', { rel: 'icon', href: '/ogma-oracle-parser/favicon.ico' }]], - base: '/ogma-oracle-parser/', + description: + "Seamless communication between Ogma and SQL Property Graphs in the Oracle Database 23ai", + head: [["link", { rel: "icon", href: "/ogma-oracle-parser/favicon.ico" }]], + base: "/ogma-oracle-parser/", themeConfig: { - logo: '/logo-small.svg', + logo: "/logo-small.svg", nav: [ - { text: 'Getting started', link: '/getting-started' }, - { text: 'Example', link: '/example' }, - { text: 'API', link: '/api/classes/OgmaOracleParser' }, - + { text: "Getting started", link: "/getting-started" }, + { text: "Example", link: "/example" }, + { text: "API", link: "/api/classes/OgmaOracleParser" }, ], sidebar, outline: { - level: [2, 3] + level: [2, 3], }, socialLinks: [ - { icon: 'github', link: 'https://github.com/Linkurious/ogma-oracle-graph-db' } - ] - } + { + icon: "github", + link: "https://github.com/Linkurious/ogma-oracle-graph-db", + }, + ], + }, }); diff --git a/docs/example.md b/docs/example.md index 9e875ec..84e8834 100644 --- a/docs/example.md +++ b/docs/example.md @@ -1,38 +1,118 @@ # Example -We provide a complete example on how to setup your graph database, connect to it, retrieve elements and display them in Ogma. And the best is that you can make it work in minutes. +We provide a complete example on how to setup your Oracle Database as a graph database, connect to it, retrieve elements and display them in Ogma. And the best is that you can make it work in minutes. Let's get started: ```sh -git clone https://github.com/Linkurious/ogma-oracle-graph-db -cd ogma-oracle-graph-db +git clone https://github.com/Linkurious/ogma-oracle-parser.git +tree ogma-oracle-parser ``` ## Setup the Database -The simplest way is to use the `docker-compose` file we provide, which will: +### Before you set up the database instance -- pull the Oracle SQL server image -- setup the users login/password -- load a database -- add the SQL utility functions required by the parser +The `database` subfolder contains a curated [OpenFlights](https://openflights.org/) dataset about airports and flights connecting airports. You need to `unzip` the dataset first. ```sh -cd example/compose-stack -docker compose up +cd ogma-oracle-parser/example/database +sh ./deflate-db.sh +ll dataset ``` -And you are done ! You know have a container exposing the port `1521` on which you can execute SQL requests. +### Create the database container using startup scripts -## Start the server +Now, you can use `Podman` to: + +- pull the Oracle Database Free 23ai **full** container image from the [Oracle Container Registry](https://container-registry.oracle.com/) +- setup the DB user login/password +- load a sample dataset +- create a property graph on top of the sample dataset + +```sh +# Make sure you are in the right directory +cd ~/ogma-oracle-parser/example/database + +# Clean up existing containers +podman rmi --force -a + +# Pull a new Oracle Database 23ai Free container image +podman run --privileged -d --name 23aifree \ + -p 1521:1521 \ + -e ORACLE_PWD=Welcome_1234# \ + -e ORACLE_PDB=freepdb1 \ + -e GRAPH_USER=graphuser \ + -e GRAPH_PWD=Welcome_1234# \ + -v oracle_data:/opt/oracle/oradata \ + -v ./startup:/opt/oracle/scripts/startup \ + -v ./dataset:/home/oracle/dataset:rw \ + -v ./scripts:/home/oracle/scripts:rw \ + container-registry.oracle.com/database/free:latest +``` + +Note: It takes about 3-4 minutes to have the container up and running. Make sure to replace the `ORACLE_PWD` and `GRAPH_PWD` passwords at a later stage. + +You can check the container using: + +```sh +podman ps +podman logs 23aifree +``` + +You now have a container running that exposes the standard Oracle Database port `1521` on which you can execute SQL requests. To test the connection to the database, do the following: + +```sh +podman exec -it 23aifree sqlplus pdbadmin/Welcome_1234#@freepdb1 +``` + +```sql +show user + +select 1; +``` + +Logout if everything looks fine. + +```sql +quit +``` + +As `GRAPH_USER` you can check that the property graph was created: + +```sh +podman exec -it 23aifree sqlplus graphuser/Welcome_1234#@freepdb1 +``` + +```sql +select * from graph_table ( + openflights_graph + match (a is airport)-[e]->(b is city) + columns (a.name as airport, a.iata as iata, b.city as city) +) +fetch first 10 rows only; +``` + +Logout if everything looks fine. + +```sql +quit +``` + +Congratulations! You have completed the first step. + +## Build the Frontend + +Open a new SSH connection to your compute instance. ```sh -cd example/server +# Make sure you are in the right directory +cd ~/ogma-oracle-parser/example/client ``` You will need to provide your Ogma API key to be able to install Ogma via npm install. Either by modifying the `package.json`, either by running: + ```sh npm install --save https://get.linkurio.us/api/get/npm/ogma//?secret= ``` @@ -41,30 +121,40 @@ Then: ```sh npm install -npm run start +npm run build ``` +That's it! You know have a `dist` folder containing the frontend app. +It displays the graph. You can check the properties of nodes and edges properties by clicking on the node or edge. A double-click on a node expands it with one hop. -You know have an express app that answers to a few routes by querying your SQL database: +Now we will need to start the server in order to access it. -- `[GET] /nodes/:type` Returns all nodes of a certain type. Type must match with the labels passed in your `CREATE PROPERTY GRAPH` call. -- `[GET] /edges/:types` Returns all edges of a certain type. -- `[GET] /node/:id` Returns the node corresponding to `id`. Id must be of the form: `LABEL-ID`. -- `[GET] /edge/:id` Returns the edge corresponding to `id` -- `[GET /expand/:id` Returns all the neighbors of the node refered by `id`. +## Start the Server -## Start the frontend +```sh +# Make sure you are in the right directory +cd ~/ogma-oracle-parser/example/server +``` +Same as for cient, you will need to install Ogma by providing your `API_KEY`. Then you can just proceed: ```sh -cd example/client +npm install --save https://get.linkurio.us/api/get/npm/ogma//?secret= ``` -Same as for server, you will need to install Ogma by providing your `API_KEY`. Then you can just proceed: +Then: ```sh npm install -npm run dev +npm run start ``` -You know have a frontend running on `http://localhost:5174/` which displays the graph, allows you to look into nodes/edges properties by clicking on it, and expand nodes by double clicking on it. +You now have an express app that answers to a few routes by querying your SQL database: + +- `[GET] /nodes/:type` Returns all nodes of a certain type. Type must match with the labels passed in your `CREATE PROPERTY GRAPH` call. +- `[GET] /edges/:types` Returns all edges of a certain type. +- `[GET] /node/:id` Returns the node corresponding to `id`. ID must be of the form: `LABEL-ID`. +- `[GET] /edge/:id` Returns the edge corresponding to `id` +- `[GET /expand/:id` Returns all the neighbors of the node referred by `id`. + +It also serves the client app on the root route. So you can navigate to `http://localhost:1337` and see the graph displayed. Or to your remote server IP address if you are running it on a remote server. Enjoy! diff --git a/docs/getting-started.md b/docs/getting-started.md index 72a4938..c8baed4 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,52 +1,34 @@ ---- -outline: deep ---- +# Getting Started -You have an Oracle SQL database and want to display it as a graph ? Great ! Let's see how to achieve that with [Ogma](https://doc.linkurious.com/ogma/latest/), a powerful and blazing fast graph visualization library. +You have an Oracle Database 23ai and want to display SQL Property Graphs? Great! Let's see how to achieve that with [Ogma](https://doc.linkurious.com/ogma/latest/), a powerful and blazing fast graph visualization library. -## Create your Graph Database +## Create your Oracle Database 23ai Free instance -You can have a look at our [example](./example), which allows you to visualize a sample SQL database in minutes with Docker compose. +You can have a look at our [example](./example), which allows you to visualize a sample property graph in minutes using Podman. -Oracle provides great tutorials/resources on how to create your graph database: +Oracle provides great tutorials/resources on how to create Property Graphs in your Oracle Database: - [Tutorial](https://oracle-base.com/articles/23c/sql-property-graphs-and-sql-pgq-23c) -- [Quick start guide](https://docs.oracle.com/en/database/oracle/property-graph/23.4/spgdg/sql-property-graph.html#GUID-70485837-3FFC-4B1E-AD3E-B9B61AC525A1) +- [Quick Start guide for working with SQL Property Graphs](https://docs.oracle.com/en/database/oracle/property-graph/23.4/spgdg/sql-property-graph.html) -## Add some functions to SQL server +## Functions in Oracle Database 23ai to return graph query results as JSON -Once you have your database is set, you will need to add [ORA_SQLGRAPH_TO_JSON](https://raw.githubusercontent.com/oracle/apex/23.2/plugins/region/graph-visualization/optional-23c-only/gvt_sqlgraph_to_json.sql) and [CUST_SQLGRAPH_JSON](https://docs.oracle.com/en//database/oracle/property-graph/23.3/spgdg/visualizing-sql-graph-queries-using-apex-graph-visualization-plug.html#GUID-A48C808E-52BD-4E6D-8AB9-4AF88811990D) functions to your SQL server. This will allow you to select vertices/edges in your database in JSON format. +OGMA accepts the result set from SQL graph query (returned nodes, edges, and their properties) in JSON format only. The transformation to JSON relies on the [DBMS_GVT PL/SQL package available on GitHub](https://github.com/oracle/apex/blob/23.2/plugins/region/graph-visualization/optional-23ai-only/gvt_sqlgraph_to_json.sql). The package and a PL/SQL helper function, `CUST_SQLGRAPH_JSON`, are created upon the creation of the Oracle Database container. (See the [scripts in this folder](./example/database/scripts)). -```sh -sqlplus -s USER/PASSWORD@localhost:1521/SESSION @/path/to/script/sqlgraph-to-json.sql -``` +`GVT` is the abbreviation for `Graph Visualization Toolkit`. Details are available in: -```sql -CREATE OR REPLACE FUNCTION CUST_SQLGRAPH_JSON ( - QUERY VARCHAR2 -) RETURN CLOB - AUTHID CURRENT_USER IS - INCUR SYS_REFCURSOR; - L_CUR NUMBER; - RETVALUE CLOB; -BEGIN - OPEN INCUR FOR QUERY; - L_CUR := DBMS_SQL.TO_CURSOR_NUMBER(INCUR); - RETVALUE := ORA_SQLGRAPH_TO_JSON(L_CUR); - DBMS_SQL.CLOSE_CURSOR(L_CUR); - RETURN RETVALUE; -END; -``` +- [Oracle DeveloperĀ“s Guide for Property Graph](https://docs.oracle.com/en/database/oracle/property-graph/24.3/spgdg/visualizing-sql-graph-queries-using-apex-graph-visualization-plug.html) +- [Oracle Graph JavaScript API Reference for Property Graph Visualization](https://docs.oracle.com/en/database/oracle/property-graph/23.4/pgjsd/index.html). -## Retrieve your nodes/edges from the Databse in NodeJS +## Retrieve your nodes/edges from the database in Node.js -First, install the ogma, the oracle connector and ogma-oracle-parser: +First, install the Ogma, the Oracle Database 23ai connector and ogma-oracle-parser: ```sh npm i oracledb @linkurious/ogma @linkurious/ogma-oracle-parser ``` -Create your connection: +Create your DB connection: ```ts const connectString = host + ":" + port + "/" + service; @@ -95,7 +77,7 @@ You can see that the result should look like } ``` -Now, we can use the `CUST_SQLGRAPH_JSON` to retrieve vertives/edges data from the ids we got from the previous request: +Now, we can use the `CUST_SQLGRAPH_JSON` to retrieve nodes/edges data from the IDs we got from the previous request: ```ts import { parseLob } from "@linkurious/ogma-oracle-parser"; @@ -150,7 +132,7 @@ Now, what you get is this: } ``` -Where `vlabel` and `elabel` are the labels you have passed to SQL in your `CREATE PROPERTY GRAPH` call. `-id` is the id of your element in the table. +Where `vlabel` and `elabel` are the labels you have passed to SQL in your `CREATE PROPERTY GRAPH` call. `-id` is the ID of your element in the table. And that's it ! You now have retrieved nodes and edges in the [Ogma format](https://doc.linkurious.com/ogma/latest/api.html#RawGraph) The plugin also provides a [getRawGraph](/api/classes/OgmaOracleParser.html#getrawgraph) function that does all the work for you. You can use it like this: @@ -199,7 +181,7 @@ axios.get("http://url-to-node-server:port/nodes/VLABEL").then(({ data }) => { And you are done ! -## Customize your nodes/edges ids +## Customize your node/edge IDs By default, the plugin transforms the `label:{"ID": id}` into `label-id`. You can customize this behaviour by creating an instance of the [OgmaOracleParser](/api/classes/OgmaOracleParser.html#constructors) class" @@ -216,7 +198,7 @@ const { parse, parseLob, getRawGraph } = new OgmaOracleParser({ }); ``` -## Node and Edge data types +## Node and edge data types You can type the data of your nodes and edges by passing [ND](/api/classes/OgmaOracleParser.html#type-parameters) and [ED](/api/classes/OgmaOracleParser.html#type-parameters) value in the `OgmaOracleParser` constructor: diff --git a/docs/index.md b/docs/index.md index 8c12b46..5a053a9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,5 +15,5 @@ hero: link: /example - theme: alt text: API - link: /api + link: /api/classes/OgmaOracleParser --- diff --git a/example/client/package.json b/example/client/package.json index b31bc01..c9ddf21 100644 --- a/example/client/package.json +++ b/example/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.0.0", + "version": "1.0.2", "type": "module", "scripts": { "dev": "vite", @@ -13,8 +13,8 @@ "vite": "^4.4.5" }, "dependencies": { - "@linkurious/ogma": "^4.5.0", + "@linkurious/ogma": "5.1.4", "@linkurious/ogma-oracle-parser": "^1.0.0", "axios": "^1.5.1" } -} \ No newline at end of file +} diff --git a/example/client/src/graph-fetch.ts b/example/client/src/graph-fetch.ts index d1cede6..9ace7d6 100644 --- a/example/client/src/graph-fetch.ts +++ b/example/client/src/graph-fetch.ts @@ -1,36 +1,35 @@ -import axios from 'axios'; -import { DBSchema } from './types'; -import { RawNode, RawEdge } from '@linkurious/ogma/umd'; +import { RawNode, RawEdge, RawGraph } from "@linkurious/ogma"; +import axios from "axios"; +import { DBSchema } from "./types"; -export class Connector{ +export class Connector { public schema: S; constructor(schema: S) { this.schema = schema; } fetchNodesByType< - TableName extends string & keyof S['nodes'], - NodeData extends S['nodes'][TableName]['properties'] - >(type: TableName,): Promise[]> { - return axios.get(`http://localhost:1337/nodes/${type}`) - .then(({ data }) => { - const { nodes } = data; - return nodes; - }); + TableName extends string & keyof S["nodes"], + NodeData extends S["nodes"][TableName]["properties"], + >(type: TableName): Promise[]> { + return axios + .get(`http://localhost:1337/nodes/${type}`) + .then(({ data }) => data.nodes); } fetchEdgesByType< - TableName extends string & keyof S['edges'], - EdgeData extends S['edges'][TableName]['properties'] - >(type: TableName,): Promise[]> { - return axios.get(`http://localhost:1337/edges/${type}/1/5000/5000`) + TableName extends string & keyof S["edges"], + EdgeData extends S["edges"][TableName]["properties"], + >(type: TableName): Promise[]> { + return axios + .get(`http://localhost:1337/edges/${type}/4000/1000`) + .then(({ data }) => data.edges); + } + expand(nodeId: string) { + return axios + .get(`http://localhost:1337/expand/${nodeId}`) .then(({ data }) => { - const { edges } = data; - return edges; + return data; }); - }; - expand(nodeId: string) { - return axios.get(`http://localhost:1337/expand/${nodeId}`) - .then(({ data }) => { return data; }); } -} \ No newline at end of file +} diff --git a/example/client/src/icons.ts b/example/client/src/icons.ts index 3c8e528..89adc16 100644 --- a/example/client/src/icons.ts +++ b/example/client/src/icons.ts @@ -1,16 +1,15 @@ // dummy icon element to retrieve the HEX code -const placeholder = document.getElementById('icon-placeholder'); -placeholder.style.visibility = 'hidden'; +const placeholder = document.getElementById("icon-placeholder")!; +placeholder.style.visibility = "hidden"; // helper routine to get the icon HEX code -const getIconCode = className => { +const getIconCode = (className: string) => { placeholder.className = className; - const code = getComputedStyle(placeholder, ':before').content; + const code = getComputedStyle(placeholder, ":before").content; return code; }; - export const icons = { - AIRPORTS: getIconCode('fa-solid fa-plane'), - CITIES: getIconCode('fa-solid fa-city'), -} + AIRPORTS: getIconCode("fa-solid fa-plane"), + CITIES: getIconCode("fa-solid fa-city"), +}; diff --git a/example/client/src/left-panel.ts b/example/client/src/left-panel.ts index d168dbf..812c391 100644 --- a/example/client/src/left-panel.ts +++ b/example/client/src/left-panel.ts @@ -1,5 +1,5 @@ -import { Node, Edge } from '@linkurious/ogma'; -import { rowId, labelFromId } from '@linkurious/ogma-oracle-parser'; +import { Node, Edge } from "@linkurious/ogma"; +import { rowId, labelFromId } from "@linkurious/ogma-oracle-parser"; export class LeftPanel { private rootElement: HTMLElement; constructor(rootElement: HTMLElement) { @@ -7,17 +7,20 @@ export class LeftPanel { } setGraphElement(element: Node | Edge) { - this.rootElement.classList.add('slide-in'); - this.rootElement.classList.remove('slide-out'); + this.rootElement.classList.add("slide-in"); + this.rootElement.classList.remove("slide-out"); - const eltType = element.isNode ? 'node' : 'edge'; - const tableName = labelFromId(element.getId()); - const id = rowId(element.getId()); + const eltType = element.isNode ? "node" : "edge"; + const eltId = element.getId() as string; + const tableName = labelFromId(eltId); + const id = rowId(eltId); const graphId = `${tableName}{"ID":${id}}`; - const properties = `
    ${Object.entries(element.getData()) - .reduce((acc, [key, value]) => { + const properties = `
      ${Object.entries(element.getData()).reduce( + (acc, [key, value]) => { return acc + `
    • ${key}: ${value}
    • `; - }, '')}
    `; + }, + "" + )}
`; const dbProperties = `
    @@ -34,9 +37,9 @@ export class LeftPanel { `; } clear() { - this.rootElement.classList.remove('slide-in'); - this.rootElement.classList.add('slide-out'); + this.rootElement.classList.remove("slide-in"); + this.rootElement.classList.add("slide-out"); - this.rootElement.innerHTML = ''; + this.rootElement.innerHTML = ""; } -} \ No newline at end of file +} diff --git a/example/client/src/ogma.ts b/example/client/src/ogma.ts index a1e5855..394632c 100644 --- a/example/client/src/ogma.ts +++ b/example/client/src/ogma.ts @@ -1,56 +1,56 @@ import Ogma from "@linkurious/ogma"; import { labelFromId } from "@linkurious/ogma-oracle-parser"; -import { showLoader, hideLoader } from './loader'; import { Connector } from "./graph-fetch"; +import { icons } from "./icons"; import { LeftPanel } from "./left-panel"; -import { icons } from './icons'; +import { showLoader, hideLoader } from "./loader"; -const leftPanelRoot = document.createElement('div'); -leftPanelRoot.classList.add('left-panel'); +const leftPanelRoot = document.createElement("div"); +leftPanelRoot.classList.add("left-panel"); const leftPanel = new LeftPanel(leftPanelRoot); -const fontName = 'Font Awesome 6 Free'; +const fontName = "Font Awesome 6 Free"; const schema = { nodes: { city: { - label: 'city', + label: "city", properties: { - CITY: 'string', - COUNTRY: 'string', + CITY: "string", + COUNTRY: "string", }, }, airport: { - label: 'located_in', + label: "located_in", properties: { - NAME: 'string', - IATA: 'string', - ICAO: 'string', - AIRPORT_TYPE: 'string', - LONGITUDE: 'number', - LATITUDE: 'number', - ALTITUDE: 'number', - TIMEZONE: 'string', - TZDBTIME: 'string', - DST: 'string', - } - } + NAME: "string", + IATA: "string", + ICAO: "string", + AIRPORT_TYPE: "string", + LONGITUDE: "number", + LATITUDE: "number", + ALTITUDE: "number", + TIMEZONE: "string", + TZDBTIME: "string", + DST: "string", + }, + }, }, edges: { route: { - label: 'route', + label: "route", properties: { - codeshare: 'string', - airline_id: 'string', - equipment: 'string', - stops: 'number', - distance_in_mi: 'number', - distance_in_km: 'number', - } + codeshare: "string", + airline_id: "string", + equipment: "string", + stops: "number", + distance_in_mi: "number", + distance_in_km: "number", + }, }, - airports_in_cities: { + located_in: { label: "located_in", - properties: {} - } - } + properties: {}, + }, + }, }; const connector = new Connector(schema); @@ -59,73 +59,78 @@ export function setupOgma(element: HTMLDivElement) { container: element, }); ogma.styles.addNodeRule({ - color: node => labelFromId(`${node.getId()}`) === 'CITIE' ? 'red' : 'blue', + color: (node) => + labelFromId(`${node.getId()}`) === "CITIE" ? "#dbd3ad" : "#d36060", icon: { font: fontName, - color: 'white', - content: n => icons[n.getData('type')], - scale: 0.75 + color: "white", + // @ts-expect-error any + content: (n) => icons[n.getData("type")], + scale: 0.75, }, text: { - content: n => n.getData('name'), + content: (n) => + labelFromId(`${n.getId()}`) === "CITIE" + ? n.getData("CITY") + : n.getData("IATA"), size: 15, padding: 10, minVisibleSize: 2, }, }); const highlighted = ogma.styles.createClass({ - name: 'highlighted', + name: "highlighted", nodeAttributes: { radius: 10, outerStroke: { - color: 'black', + color: "black", width: 2, }, }, edgeAttributes: { - color: 'black', + color: "black", width: 2, }, }); ogma.layers.addLayer(leftPanelRoot); - ogma.events.on('doubleclick', (evt) => { + ogma.events.on("doubleclick", (evt) => { if (!evt.target || !evt.target.isNode) return; - const nodeId = evt.target.getId(); + const nodeId = evt.target.getId() as string; highlighted.clearNodes(); highlighted.clearEdges(); - connector.expand(nodeId) - .then(({ nodes, edges }) => { - const nodeIds = nodes.map(n => n.id); - const edgeIds = edges.map(e => e.id); - return ogma.addNodes(nodes, { ignoreInvalid: true }) - .then(() => ogma.addEdges(edges, { ignoreInvalid: true })) - .then(() => { - const neighbors = ogma.getNodes(nodeIds); - const edges = ogma.getEdges(edgeIds); - highlighted.add(neighbors); - highlighted.add(edges); - return ogma.layouts.force({ gpu: true }); - }); - }); + connector.expand(nodeId).then(({ nodes, edges }) => { + const nodeIds = nodes.map((n) => n.id) as string[]; + const edgeIds = edges.map((e) => e.id) as string[]; + return ogma + .addNodes(nodes, { ignoreInvalid: true }) + .then(() => ogma.addEdges(edges, { ignoreInvalid: true })) + .then(() => { + const neighbors = ogma.getNodes(nodeIds); + const edges = ogma.getEdges(edgeIds); + highlighted.add(neighbors); + highlighted.add(edges); + return ogma.layouts.force({ gpu: true }); + }); + }); }); - ogma.events.on('click', (evt) => { + ogma.events.on("click", (evt) => { if (!evt.target) return leftPanel.clear(); leftPanel.setGraphElement(evt.target); }); - showLoader('Loading Airports and Cities'); + showLoader("Loading Airports and Cities"); return Promise.all([ - connector.fetchNodesByType('city'), - connector.fetchNodesByType('airport'), + connector.fetchNodesByType("city"), + connector.fetchNodesByType("airport"), ]) - .then(([cities, airports]) => ogma.addNodes(cities - .slice(0, 300) - .concat(airports.slice(0, 300)))) + .then(([cities, airports]) => + Promise.all([ogma.addNodes(cities), ogma.addNodes(airports)]) + ) .then(() => { - showLoader('Loading Routes'); + showLoader("Loading Routes"); return Promise.all([ - connector.fetchEdgesByType('located_in'), - connector.fetchEdgesByType('route'), + connector.fetchEdgesByType("located_in"), + connector.fetchEdgesByType("route"), ]); }) .then(([located, route]) => { diff --git a/example/compose-stack/.env b/example/compose-stack/.env deleted file mode 100644 index a3eb5ac..0000000 --- a/example/compose-stack/.env +++ /dev/null @@ -1,2 +0,0 @@ -DBA_PWD='password' -USR_PWD='password' \ No newline at end of file diff --git a/example/compose-stack/dataset/dataset.zip b/example/compose-stack/dataset/dataset.zip deleted file mode 100644 index f0e11bd..0000000 Binary files a/example/compose-stack/dataset/dataset.zip and /dev/null differ diff --git a/example/compose-stack/docker-compose.yml b/example/compose-stack/docker-compose.yml deleted file mode 100644 index d5f2059..0000000 --- a/example/compose-stack/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -version: "3.9" -services: - oracle: - image: container-registry.oracle.com/database/free:23.3.0.0 - ports: - - 1521:1521 - environment: - - DBA_PWD=$DBA_PWD - - USR_PWD=$USR_PWD - volumes: - - oracle_data:/opt/oracle/oradata - - ./setup:/opt/oracle/scripts/setup - - ./dataset:/home/oracle/dataset - - ./sqlcl:/home/oracle/sqlcl - - ./scripts:/home/oracle/scripts -volumes: - oracle_data: diff --git a/example/compose-stack/scripts/create-property-graph.sql b/example/compose-stack/scripts/create-property-graph.sql deleted file mode 100644 index 36fae9c..0000000 --- a/example/compose-stack/scripts/create-property-graph.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Add primary keys - -alter table openflights_airports add constraint openflights_airports_pk primary key (id); - -alter table openflights_cities add constraint openflights_cities_pk primary key (id); - -alter table openflights_routes add constraint openflights_routes_pk primary key (id); - --- Add foreign keys - -alter table openflights_airports add constraint openflights_airports_city_fk foreign key (city_id) references openflights_cities(id); - -alter table openflights_routes add constraint openflights_routes_src_airport_fk foreign key (src_airport_id) references openflights_airports(id); - -alter table openflights_routes add constraint openflights_routes_dest_airport_fk foreign key (dest_airport_id) references openflights_airports(id); - -drop property graph openflights_graph; -create property graph openflights_graph vertex tables ( openflights_airports as airports key ( id ) label airport properties ( name, iata, icao, airport_type, longitude, latitude, altitude, timezone, tzdbtime, dst ), openflights_cities as cities key ( id ) label city properties ( city, country ) ) edge tables ( openflights_routes as routes source key ( src_airport_id ) references airports (id) destination key ( dest_airport_id ) references airports (id) label route properties ( codeshare, airline_id, equipment, stops, distance_in_mi, distance_in_km ), openflights_airports as airports_in_cities source key ( id ) references airports (id) destination key ( city_id ) references cities (id) label located_in no properties ); \ No newline at end of file diff --git a/example/compose-stack/scripts/create-tables.sql b/example/compose-stack/scripts/create-tables.sql deleted file mode 100644 index 720a254..0000000 --- a/example/compose-stack/scripts/create-tables.sql +++ /dev/null @@ -1,34 +0,0 @@ -DROP TABLE OPENFLIGHTS_AIRPORTS; -CREATE TABLE OPENFLIGHTS_AIRPORTS ( - ID number, - NAME varchar2 (100), - IATA varchar2 (10), - ICAO varchar2 (10), - LATITUDE number, - LONGITUDE number, - ALTITUDE number, - TIMEZONE number, - DST varchar2 (10), - TZDBTIME varchar2 (100), - AIRPORT_TYPE varchar2 (100), - SOURCE varchar2 (100), - CITY_ID number -); -DROP TABLE OPENFLIGHTS_CITIES; -CREATE TABLE OPENFLIGHTS_CITIES ( - ID number, - COUNTRY varchar(100), - CITY varchar(100) -); -DROP TABLE OPENFLIGHTS_ROUTES; -CREATE TABLE OPENFLIGHTS_ROUTES ( - ID number, - AIRLINE_ID number, - SRC_AIRPORT_ID number, - DEST_AIRPORT_ID number, - CODESHARE varchar(100), - STOPS number, - EQUIPMENT varchar(100), - DISTANCE_IN_KM number, - DISTANCE_IN_MI number -); \ No newline at end of file diff --git a/example/compose-stack/scripts/setup.sql b/example/compose-stack/scripts/setup.sql deleted file mode 100644 index 26d2d49..0000000 --- a/example/compose-stack/scripts/setup.sql +++ /dev/null @@ -1,43 +0,0 @@ -alter user sys identified by "password"; -alter user system identified by "password"; -alter session set container = FREEPDB1; -alter user pdbadmin identified by "password"; -create or replace user graphuser identified by "password"; -grant resource, connect to graphuser; -alter user graphuser quota unlimited on users; -grant create session to graphuser; -CREATE TABLE OPENFLIGHTS_AIRPORTS ( - ID number, - NAME varchar(100), - IATA varchar(10), - ICAO varchar(10), - LATITUDE number, - LONGITUDE number, - ALTITUDE number, - TIMEZONE number, - DST varchar(10), - TZDBTIME varchar(100), - AIRPORT_TYPE varchar(100), - SOURCE varchar(100), - CITY_ID number -); -CREATE TABLE OPENFLIGHTS_CITIES ( - ID number, - COUNTRY varchar(100), - CITY varchar(100) -); -CREATE TABLE OPENFLIGHTS_ROUTES ( - ID number, - AIRLINE_ID number, - SRC_AIRPORT_ID number, - DEST_AIRPORT_ID number, - CODESHARE varchar(100), - STOPS number, - EQUIPMENT varchar(100), - DISTANCE_IN_KM number, - DISTANCE_IN_MI number -); - -grant insert on OPENFLIGHTS_AIRPORTS to graphuser; -grant insert on OPENFLIGHTS_ROUTES to graphuser; -grant insert on OPENFLIGHTS_CITIES to graphuser; diff --git a/example/compose-stack/setup/01_setup.sql b/example/compose-stack/setup/01_setup.sql deleted file mode 100644 index 5390e2a..0000000 --- a/example/compose-stack/setup/01_setup.sql +++ /dev/null @@ -1,9 +0,0 @@ -alter user sys identified by "password"; -alter user system identified by "password"; -alter session set container = FREEPDB1; -alter user pdbadmin identified by "password"; -drop user graphuser; -create user graphuser identified by "password"; -grant resource, connect to graphuser; -alter user graphuser quota unlimited on users; -grant create session to graphuser; diff --git a/example/compose-stack/setup/02_load-dataset.sh b/example/compose-stack/setup/02_load-dataset.sh deleted file mode 100644 index d23a82a..0000000 --- a/example/compose-stack/setup/02_load-dataset.sh +++ /dev/null @@ -1,9 +0,0 @@ -unzip /home/oracle/dataset/dataset.zip -d /home/oracle/dataset -sqlplus -s graphuser/password@localhost:1521/freepdb1 @/home/oracle/scripts/create-tables.sql -sqlplus -s graphuser/password@localhost:1521/freepdb1 @/home/oracle/dataset/airports.sql -sqlplus -s graphuser/password@localhost:1521/freepdb1 @/home/oracle/dataset/cities.sql -sqlplus -s graphuser/password@localhost:1521/freepdb1 @/home/oracle/dataset/routes.sql -sqlplus -s graphuser/password@localhost:1521/freepdb1 @/home/oracle/scripts/create-property-graph.sql -sqlplus -s graphuser/password@localhost:1521/freepdb1 @/home/oracle/scripts/sqlgraph-to-json.sql -sqlplus -s graphuser/password@localhost:1521/freepdb1 @/home/oracle/scripts/cust-graph-json.sql - diff --git a/example/database/.env b/example/database/.env new file mode 100644 index 0000000..2f8bb5e --- /dev/null +++ b/example/database/.env @@ -0,0 +1,3 @@ +GRAPH_USER='graphuser' +GRAPH_PWD='Welcome_1234#' +ORACLE_PWD='Welcome_1234#' \ No newline at end of file diff --git a/example/database/dataset/dataset.zip b/example/database/dataset/dataset.zip new file mode 100644 index 0000000..2f9ce79 Binary files /dev/null and b/example/database/dataset/dataset.zip differ diff --git a/example/database/deflate-db.sh b/example/database/deflate-db.sh new file mode 100644 index 0000000..8b07b71 --- /dev/null +++ b/example/database/deflate-db.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +unzip ./dataset/dataset.zip -d ./dataset \ No newline at end of file diff --git a/example/database/scripts/create-property-graph.sql b/example/database/scripts/create-property-graph.sql new file mode 100644 index 0000000..cca02e4 --- /dev/null +++ b/example/database/scripts/create-property-graph.sql @@ -0,0 +1,26 @@ +-- Clean up existing property graph +drop property graph if exists openflights_graph; + +-- Create property graph +create property graph if not exists openflights_graph + vertex tables ( + openflights_airports as airports + key ( id ) + label airport + properties ( name, iata, icao, airport_type, longitude, latitude, altitude, timezone, tzdbtime, dst ), + openflights_cities as cities key ( id ) + label city + properties ( city, country ) + ) + edge tables ( + openflights_routes as routes + source key ( src_airport_id ) references airports (id) + destination key ( dest_airport_id ) references airports (id) + label route + properties ( codeshare, airline_id, equipment, stops, distance_in_mi, distance_in_km ), + openflights_airports as airports_in_cities + source key ( id ) references airports (id) + destination key ( city_id ) references cities (id) + label located_in + no properties + ); \ No newline at end of file diff --git a/example/database/scripts/create-tables.sql b/example/database/scripts/create-tables.sql new file mode 100644 index 0000000..126782a --- /dev/null +++ b/example/database/scripts/create-tables.sql @@ -0,0 +1,44 @@ +drop table if exists openflights_airports; +create table openflights_airports ( + id number, + name varchar2 (100), + iata varchar2 (10), + icao varchar2 (10), + latitude number, + longitude number, + altitude number, + timezone number, + dst varchar2 (10), + tzdbtime varchar2 (100), + airport_type varchar2 (100), + source varchar2 (100), + city_id number +); +drop table if exists openflights_cities; +create table openflights_cities ( + id number, + country varchar(100), + city varchar(100) +); +drop table if exists openflights_routes; +create table openflights_routes ( + id number, + airline_id number, + src_airport_id number, + dest_airport_id number, + codeshare varchar(100), + stops number, + equipment varchar(100), + distance_in_km number, + distance_in_mi number +); + +-- Add primary keys +alter table openflights_airports add constraint openflights_airports_pk primary key (id); +alter table openflights_cities add constraint openflights_cities_pk primary key (id); +alter table openflights_routes add constraint openflights_routes_pk primary key (id); + +-- Add foreign keys +alter table openflights_airports add constraint openflights_airports_city_fk foreign key (city_id) references openflights_cities(id); +alter table openflights_routes add constraint openflights_routes_src_airport_fk foreign key (src_airport_id) references openflights_airports(id); +alter table openflights_routes add constraint openflights_routes_dest_airport_fk foreign key (dest_airport_id) references openflights_airports(id); diff --git a/example/database/scripts/create-user.sql b/example/database/scripts/create-user.sql new file mode 100644 index 0000000..fba2349 --- /dev/null +++ b/example/database/scripts/create-user.sql @@ -0,0 +1,11 @@ +alter session set container = FREEPDB1; +drop user &1 cascade; +create user &1 identified by &2; +grant resource, connect to &1; +alter user &1 quota unlimited on users; +grant create session to &1; +grant create property graph to &1; +grant create any property graph to &1; +grant alter any property graph to &1; +grant drop any property graph to &1; +grant read any property graph to &1; \ No newline at end of file diff --git a/example/compose-stack/scripts/cust-graph-json.sql b/example/database/scripts/cust-sqlgraph-json.sql similarity index 100% rename from example/compose-stack/scripts/cust-graph-json.sql rename to example/database/scripts/cust-sqlgraph-json.sql diff --git a/example/compose-stack/scripts/gvt_sqlgraph_to_json.sql b/example/database/scripts/gvt-sqlgraph-to-json.sql similarity index 81% rename from example/compose-stack/scripts/gvt_sqlgraph_to_json.sql rename to example/database/scripts/gvt-sqlgraph-to-json.sql index b99abdb..16bee46 100644 --- a/example/compose-stack/scripts/gvt_sqlgraph_to_json.sql +++ b/example/database/scripts/gvt-sqlgraph-to-json.sql @@ -3,7 +3,7 @@ -- DBMS_GVT Package Specification ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- - + CREATE OR REPLACE PACKAGE DBMS_GVT AUTHID CURRENT_USER IS ----------------------------------------------------------------------------- @@ -32,6 +32,17 @@ CREATE OR REPLACE PACKAGE DBMS_GVT ) RETURN CLOB; FUNCTION GET_VERSION RETURN VARCHAR2; + + FUNCTION BUILD_JSON( + VERTEX_TABLE SYS.ODCIRIDLIST, + EDGE_TABLE SYS.ODCIRIDLIST, + COUNTER NUMBER, + GRAPHNAME VARCHAR2, + GRAPHOWNER VARCHAR2, + VERTEX_GROUP SYS.ODCINUMBERLIST, + EDGE_GROUP SYS.ODCINUMBERLIST + ) RETURN CLOB; + END DBMS_GVT; / ------------------------------------------------------------------------------- @@ -160,7 +171,7 @@ CREATE OR REPLACE PACKAGE BODY DBMS_GVT IS AND ELEMENTS.OWNER = GRAPHOWNER ORDER BY OBJECT_NAME, LABEL_PROPERTIES.PROPERTY_NAME; - + IF DB_TABLE_NAME_LIST.COUNT = 0 THEN RETURN 'SELECT NULL AS PROPERTIES FROM DUAL'; END IF; @@ -753,7 +764,7 @@ WHERE SYS.ALL_PG_KEYS KEYS INNER JOIN SYS.ALL_PG_ELEMENTS ELEMENTS ON ELEMENTS.ELEMENT_NAME = KEYS.ELEMENT_NAME - INNER JOIN ALL_TAB_COLUMNS + INNER JOIN SYS.ALL_TAB_COLUMNS ON ALL_TAB_COLUMNS.OWNER = ELEMENTS.OBJECT_OWNER WHERE ELEMENT_KIND = 'EDGE' @@ -960,521 +971,501 @@ WHERE END EDGE_LATERAL_STRING; FUNCTION GET_VERSION RETURN VARCHAR2 IS BEGIN - RETURN '23.4.6 (2023-10-31T05:16:42.761Z, build: 1b61e021)'; + RETURN '24.3.0 (2024-06-05T22:02:52.119185162Z, build: 689686be)'; END GET_VERSION; + + FUNCTION BUILD_JSON( + VERTEX_TABLE SYS.ODCIRIDLIST, + EDGE_TABLE SYS.ODCIRIDLIST, + COUNTER NUMBER, + GRAPHNAME VARCHAR2, + GRAPHOWNER VARCHAR2, + VERTEX_GROUP SYS.ODCINUMBERLIST, + EDGE_GROUP SYS.ODCINUMBERLIST + ) RETURN CLOB IS + VERTEX_UNDERLYING_DB_NAME_LIST SYS.ODCIVARCHAR2LIST; + VERTEX_DB_TABLE_OBJECT_OWNER SYS.ODCIVARCHAR2LIST; + EDGE_UNDERLYING_DB_NAME_LIST SYS.ODCIVARCHAR2LIST; + EDGE_DB_TABLE_OBJECT_OWNER SYS.ODCIVARCHAR2LIST; + QUERY_STRING VARCHAR2(M_VCSIZ_32K); + LATERALSTRING VARCHAR2(M_VCSIZ_32K); + SUB_QUERY_STRING VARCHAR2(M_VCSIZ_32K); + VERTEX JSON; + EDGE JSON; + EDGE_ELEMENT_NAME SYS.ODCIVARCHAR2LIST; + EDGE_UNDERLYING_DB_NAME_LIST_WITH_PROPS SYS.ODCIVARCHAR2LIST; + EDGE_DB_TABLE_OBJECT_OWNER_WITH_PROPS SYS.ODCIVARCHAR2LIST; + L_JSON VARCHAR2(M_VCSIZ_4K); + JSON_FILE clob; + DISTINCT_VERTEX_TABLE SYS.ODCIVARCHAR2LIST; + DISTINCT_EDGE_TABLE SYS.ODCIVARCHAR2LIST; + BEGIN + + /**********************************************************************************/ + --get all underlyting db table names for vertices with properties + + SELECT + DISTINCT ELEMENTS.OBJECT_NAME, + ELEMENTS.OBJECT_OWNER BULK COLLECT INTO VERTEX_UNDERLYING_DB_NAME_LIST, + VERTEX_DB_TABLE_OBJECT_OWNER + FROM + SYS.ALL_PG_ELEMENTS ELEMENTS + INNER JOIN SYS.ALL_PG_ELEMENT_LABELS ELEM_LABELS + ON (ELEMENTS.ELEMENT_NAME = ELEM_LABELS.ELEMENT_NAME + AND ELEMENTS.OWNER = ELEM_LABELS.OWNER + AND ELEMENTS.GRAPH_NAME = ELEM_LABELS.GRAPH_NAME ) + INNER JOIN SYS.ALL_PG_LABEL_PROPERTIES LABEL_PROPERTIES + ON (ELEM_LABELS.LABEL_NAME = LABEL_PROPERTIES.LABEL_NAME + AND ELEM_LABELS.OWNER = LABEL_PROPERTIES.OWNER + AND ELEM_LABELS.GRAPH_NAME = LABEL_PROPERTIES.GRAPH_NAME) + INNER JOIN SYS.ALL_PG_PROP_DEFINITIONS PROP_DEFINITIONS + ON PROP_DEFINITIONS.PROPERTY_NAME = LABEL_PROPERTIES.PROPERTY_NAME + WHERE + ELEMENTS.ELEMENT_KIND = 'VERTEX' + AND ELEMENTS.GRAPH_NAME = GRAPHNAME + AND ELEMENTS.OWNER = GRAPHOWNER; + --prepare query vertices string + + /* Example vertex query string + WITH VERTICES AS ( + SELECT + JSON_OBJECT ('id' VALUE JSON_VALUE(VT.COLUMN_VALUE, + '$.ELEM_TABLE') || JSON_QUERY(VT.COLUMN_VALUE, + '$.KEY_VALUE'), + 'properties' VALUE PROPERTIES_TABLE.PROPERTIES ABSENT ON NULL RETURNING CLOB) AS VERTEX + FROM + TABLE(:1) VT, + LATERAL( + SELECT + JSON_OBJECT('SAL' VALUE X.SAL, + 'AGE' VALUE X.AGE ABSENT + ON NULL) AS PROPERTIES + FROM + HR.VERTEX_3 X + WHERE + JSON_VALUE(VT.COLUMN_VALUE, + '$.ELEM_TABLE') = 'VERTEX_3' + AND X.V_ID= JSON_VALUE(VT.COLUMN_VALUE, + '$.KEY_VALUE.V_ID') UNION + SELECT + JSON_OBJECT('AGE' VALUE X.AGE, + 'SAL' VALUE X.SAL ABSENT + ON NULL) AS PROPERTIES + FROM + HR.VERTEX_1 X + WHERE + JSON_VALUE(VT.COLUMN_VALUE, + '$.ELEM_TABLE') = 'VERTEX_1' + AND X.V_ID= JSON_VALUE(VT.COLUMN_VALUE, + '$.KEY_VALUE.V_ID') UNION + SELECT + JSON_OBJECT('AGE' VALUE X.AGE ABSENT + ON NULL) AS PROPERTIES + FROM + HR.VERTEX_2 X + WHERE + JSON_VALUE(VT.COLUMN_VALUE, + '$.ELEM_TABLE') = 'VERTEX_2' + AND X.V_ID= JSON_VALUE(VT.COLUMN_VALUE, + '$.KEY_VALUE.V_ID') + ) PROPERTIES_TABLE + ) + SELECT + JSON_ARRAYAGG(VERTEX RETURNING CLOB) + FROM + VERTICES + */ + -- if there is vertex id, prepare vertex query string like above example query string + IF (VERTEX_TABLE.COUNT != 0) THEN + SELECT DISTINCT COLUMN_VALUE BULK COLLECT INTO DISTINCT_VERTEX_TABLE FROM TABLE(VERTEX_TABLE); + QUERY_STRING := 'with vertices as ('; + -- prepare string inside LATERAL() + LATERALSTRING := DBMS_GVT.PROPERTIES_LATERAL_STRING(VERTEX_UNDERLYING_DB_NAME_LIST, VERTEX_DB_TABLE_OBJECT_OWNER, 'VT', 'COLUMN_VALUE', GRAPHNAME, GRAPHOWNER, 'VERTEX'); + SUB_QUERY_STRING := 'select json_object (''id'' value json_value(VT.COLUMN_VALUE, ''$.ELEM_TABLE'') || json_query(VT.COLUMN_VALUE, ''$.KEY_VALUE''), ''properties'' value properties_table.PROPERTIES, ''labels'' value labels.label, ''groups'' value groups.groups absent on null returning clob) as vertex from (select distinct * from table(:1)) VT outer apply(' + || LATERALSTRING + || ') properties_table, ' + || ' LATERAL (SELECT JSON_ARRAYAGG(LABEL_NAME) AS LABEL FROM SYS.ALL_PG_ELEMENT_LABELS x WHERE x.OWNER = JSON_VALUE(VT.COLUMN_VALUE, ''$.GRAPH_OWNER'') AND x.GRAPH_NAME = JSON_VALUE(VT.COLUMN_VALUE, ''$.GRAPH_NAME'') AND x.ELEMENT_NAME = JSON_VALUE(VT.COLUMN_VALUE, ''$.ELEM_TABLE'')) LABELS, ' + || ' LATERAL (SELECT JSON_ARRAYAGG(C2) AS GROUPS FROM (select DISTINCT C2 from (select column_value as c1, rownum as r1 from table(:2)) t1 JOIN (select column_value as c2, rownum as r2 from table(:3)) t2 on t1.r1 = t2.r2 WHERE C1 = VT.COLUMN_VALUE ) ) GROUPS ) '; + + QUERY_STRING := QUERY_STRING + || SUB_QUERY_STRING; + QUERY_STRING := QUERY_STRING + || ' select json_arrayagg(vertex returning clob) from vertices'; + + EXECUTE IMMEDIATE QUERY_STRING INTO VERTEX USING DISTINCT_VERTEX_TABLE, VERTEX_TABLE, VERTEX_GROUP; + + ELSE + -- if there is no vertex id in global temp table, return an empty array to vertex value + SELECT + JSON_ARRAY() INTO VERTEX; + END IF; + --get edges table name and underlying db table names for edges + SELECT + ELEMENTS.ELEMENT_NAME, + ELEMENTS.OBJECT_NAME, + ELEMENTS.OBJECT_OWNER BULK COLLECT INTO EDGE_ELEMENT_NAME, + EDGE_UNDERLYING_DB_NAME_LIST, + EDGE_DB_TABLE_OBJECT_OWNER + FROM + SYS.ALL_PG_ELEMENTS ELEMENTS + INNER JOIN SYS.ALL_PG_ELEMENT_LABELS ELEMENTS_LABELS + ON ELEMENTS.ELEMENT_NAME = ELEMENTS_LABELS.ELEMENT_NAME + AND ELEMENTS.GRAPH_NAME = ELEMENTS_LABELS.GRAPH_NAME + WHERE + ELEMENTS.ELEMENT_KIND = 'EDGE' + AND ELEMENTS.GRAPH_NAME = GRAPHNAME + AND ELEMENTS.OWNER = GRAPHOWNER; + --get all underlyting db table names for edges with properties + + SELECT + DISTINCT ELEMENTS.OBJECT_NAME, + ELEMENTS.OBJECT_OWNER BULK COLLECT INTO EDGE_UNDERLYING_DB_NAME_LIST_WITH_PROPS, + EDGE_DB_TABLE_OBJECT_OWNER_WITH_PROPS + FROM + SYS.ALL_PG_ELEMENTS ELEMENTS + INNER JOIN SYS.ALL_PG_ELEMENT_LABELS ELEM_LABELS + ON (ELEMENTS.ELEMENT_NAME = ELEM_LABELS.ELEMENT_NAME + AND ELEMENTS.OWNER = ELEM_LABELS.OWNER + AND ELEMENTS.GRAPH_NAME = ELEM_LABELS.GRAPH_NAME ) + INNER JOIN SYS.ALL_PG_LABEL_PROPERTIES LABEL_PROPERTIES + ON (ELEM_LABELS.LABEL_NAME = LABEL_PROPERTIES.LABEL_NAME + AND ELEM_LABELS.OWNER = LABEL_PROPERTIES.OWNER + AND ELEM_LABELS.GRAPH_NAME = LABEL_PROPERTIES.GRAPH_NAME) + INNER JOIN SYS.ALL_PG_PROP_DEFINITIONS PROP_DEFINITIONS + ON PROP_DEFINITIONS.PROPERTY_NAME = LABEL_PROPERTIES.PROPERTY_NAME + WHERE + ELEMENTS.ELEMENT_KIND = 'EDGE' + AND ELEMENTS.GRAPH_NAME = GRAPHNAME + AND ELEMENTS.OWNER = GRAPHOWNER; + --prepare query vertices string + --prepare edge query string + + /*Example string + WITH EDGES AS ( + SELECT + JSON_OBJECT('id' VALUE JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') + || JSON_QUERY(ET.COLUMN_VALUE, '$.KEY_VALUE'), 'source' IS ID_TABLE.SOURCE, 'target' IS ID_TABLE.TARGET, 'properties' VALUE PROPERTIES_TABLE.PROPERTIES, 'labels' VALUE LABELS.LABEL ABSENT ON NULL RETURNING CLOB ) AS EDGE + FROM + ( + SELECT + DISTINCT * + FROM + TABLE(:1) + ) ET OUTER APPLY( + SELECT + JSON_OBJECT('COST' VALUE X."COST" ABSENT ON NULL) AS PROPERTIES + FROM + "HR"."EDGE_1_1_B" X + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') = 'EDGE_1_1_B' + AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_SRC"') + AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_DST"') + UNION + SELECT + JSON_OBJECT('COST' VALUE X."E_W" ABSENT ON NULL) AS PROPERTIES + FROM + "HR"."EDGE_2_1" X + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') = 'EDGE_2_1' + AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_SRC"') + AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_DST"') + UNION + SELECT + JSON_OBJECT('COST' VALUE X."E_WEIGHT" ABSENT ON NULL) AS PROPERTIES + FROM + "HR"."EDGE_1_2" X + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') = 'EDGE_1_2' + AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_SRC"') + AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_DST"') + UNION + SELECT + JSON_OBJECT('COST' VALUE X."E_COST" ABSENT ON NULL) AS PROPERTIES + FROM + "HR"."EDGE_1_1_A" X + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') = 'EDGE_1_1_A' + AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_DST"') + AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_SRC"') + UNION + SELECT + JSON_OBJECT('COST' VALUE X."E_C" ABSENT ON NULL) AS PROPERTIES + FROM + "HR"."EDGE_1_3" X + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') = 'EDGE_1_3' + AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_DST"') + AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE."E_SRC"') + ) PROPERTIES_TABLE, + LATERAL( + SELECT + 'VERTEX_1' + || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, + 'VERTEX_2' + || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET + FROM + HR.EDGE_1_2 EDGE_TABLE, + HR.VERTEX_1 SRC_TABLE, + HR.VERTEX_2 DST_TABLE + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') ='EDGE_1_2' + AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_SRC') + AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_DST') + AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID + AND EDGE_TABLE.E_DST= DST_TABLE.V_ID + UNION + SELECT + 'VERTEX_2' + || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, + 'VERTEX_1' + || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET + FROM + HR.EDGE_2_1 EDGE_TABLE, + HR.VERTEX_2 SRC_TABLE, + HR.VERTEX_1 DST_TABLE + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') ='EDGE_2_1' + AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_SRC') + AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_DST') + AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID + AND EDGE_TABLE.E_DST= DST_TABLE.V_ID + UNION + SELECT + 'VERTEX_1' + || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, + 'VERTEX_1' + || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET + FROM + HR.EDGE_1_1_B EDGE_TABLE, + HR.VERTEX_1 SRC_TABLE, + HR.VERTEX_1 DST_TABLE + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') ='EDGE_1_1_B' + AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_SRC') + AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_DST') + AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID + AND EDGE_TABLE.E_DST= DST_TABLE.V_ID + UNION + SELECT + 'VERTEX_1' + || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, + 'VERTEX_3' + || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET + FROM + HR.EDGE_1_3 EDGE_TABLE, + HR.VERTEX_1 SRC_TABLE, + HR.VERTEX_3 DST_TABLE + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') ='EDGE_1_3' + AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_DST') + AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_SRC') + AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID + AND EDGE_TABLE.E_DST= DST_TABLE.V_ID + UNION + SELECT + 'VERTEX_1' + || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, + 'VERTEX_1' + || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET + FROM + HR.EDGE_1_1_A EDGE_TABLE, + HR.VERTEX_1 SRC_TABLE, + HR.VERTEX_1 DST_TABLE + WHERE + JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') ='EDGE_1_1_A' + AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_DST') + AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, '$.KEY_VALUE.E_SRC') + AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID + AND EDGE_TABLE.E_DST= DST_TABLE.V_ID + ) ID_TABLE, + LATERAL ( + SELECT + JSON_ARRAYAGG(LABEL_NAME) AS LABEL + FROM + ALL_PG_ELEMENT_LABELS X + WHERE + X.OWNER = JSON_VALUE(ET.COLUMN_VALUE, '$.GRAPH_OWNER') + AND X.GRAPH_NAME = JSON_VALUE(ET.COLUMN_VALUE, '$.GRAPH_NAME') + AND X.ELEMENT_NAME = JSON_VALUE(ET.COLUMN_VALUE, '$.ELEM_TABLE') + ) LABELS + ) + SELECT + JSON_ARRAYAGG(EDGE RETURNING CLOB) + FROM + EDGES + */ + -- if there is edge id, prepare edge query string like above example query string + IF (EDGE_TABLE.COUNT != 0) THEN + SELECT DISTINCT COLUMN_VALUE BULK COLLECT INTO DISTINCT_EDGE_TABLE FROM TABLE(EDGE_TABLE); + QUERY_STRING := 'WITH EDGES AS (SELECT JSON_OBJECT(''id'' value json_value(ET.COLUMN_VALUE, ''$.ELEM_TABLE'') || json_query(ET.COLUMN_VALUE, ''$.KEY_VALUE''),''source'' is id_table.source, ''target'' is id_table.target, ''properties'' value PROPERTIES_TABLE.PROPERTIES, ''labels'' VALUE labels.label, ''groups'' VALUE groups.groups absent on null returning clob ) as edge FROM (select distinct * from table(:1)) ET outer apply('; + -- prepare string inside LATERAL() + LATERALSTRING := DBMS_GVT.PROPERTIES_LATERAL_STRING(EDGE_UNDERLYING_DB_NAME_LIST_WITH_PROPS, EDGE_DB_TABLE_OBJECT_OWNER_WITH_PROPS, 'ET', 'COLUMN_VALUE', GRAPHNAME, GRAPHOWNER, 'EDGE'); + QUERY_STRING := QUERY_STRING + || LATERALSTRING; + QUERY_STRING := QUERY_STRING + || ') properties_table, LATERAL('; + LATERALSTRING := DBMS_GVT.EDGE_LATERAL_STRING(EDGE_UNDERLYING_DB_NAME_LIST, EDGE_DB_TABLE_OBJECT_OWNER, EDGE_ELEMENT_NAME, 'ET', 'COLUMN_VALUE', GRAPHNAME, GRAPHOWNER); + QUERY_STRING := QUERY_STRING + || LATERALSTRING; + QUERY_STRING := QUERY_STRING + ||') id_table, LATERAL (SELECT JSON_ARRAYAGG(LABEL_NAME) AS LABEL FROM SYS.ALL_PG_ELEMENT_LABELS x WHERE x.OWNER = JSON_VALUE(ET.COLUMN_VALUE, ''$.GRAPH_OWNER'') AND x.GRAPH_NAME = JSON_VALUE(ET.COLUMN_VALUE, ''$.GRAPH_NAME'') AND x.ELEMENT_NAME = JSON_VALUE(ET.COLUMN_VALUE, ''$.ELEM_TABLE'')) LABELS, ' + ||' LATERAL (SELECT JSON_ARRAYAGG(C2) AS GROUPS FROM (select DISTINCT C2 from (select column_value as c1, rownum as r1 from table(:2)) t1 JOIN (select column_value as c2, rownum as r2 from table(:3)) t2 on t1.r1 = t2.r2 WHERE C1 = ET.COLUMN_VALUE ) ) GROUPS )' + ||' select JSON_ARRAYAGG(edge returning clob) from edges'; + + EXECUTE IMMEDIATE QUERY_STRING INTO EDGE USING DISTINCT_EDGE_TABLE, EDGE_TABLE, EDGE_GROUP; + + ELSE + -- if there is no edge id in global temp table, return an empty array to edge value + SELECT + JSON_ARRAY() INTO EDGE; + END IF; + -- prepare the final result + SELECT + JSON_OBJECT('vertices' VALUE VERTEX, + 'edges' VALUE EDGE, + 'numResults' VALUE COUNTER RETURNING CLOB) INTO JSON_FILE + FROM + DUAL; + RETURN JSON_FILE; + END BUILD_JSON; END DBMS_GVT; / -/* -APEX can render JSON in the format {"vertices" : [...], "edges":[...]} -In this function, we build two query strings, one for vertex and one for edge. -After we have query strings, we use dynamic sql to execute the quries and -put the result in required json format. -*/ -CREATE OR REPLACE FUNCTION ORA_SQLGRAPH_TO_JSON ( - CURS_ID INTEGER, - PAGE_START NUMBER DEFAULT -1, - PAGE_SIZE NUMBER DEFAULT -1 -) RETURN CLOB - AUTHID CURRENT_USER IS - M_VCSIZ_4K CONSTANT PLS_INTEGER := 4000; - M_VCSIZ_32K CONSTANT PLS_INTEGER := 32672; - JSON_FILE CLOB; --the returned result - GRAPHNAME VARCHAR2(M_VCSIZ_4K); - ELEMENT_NAME VARCHAR2(M_VCSIZ_4K); - GRAPHOWNER VARCHAR2(M_VCSIZ_4K); - VERTEX JSON; - EDGE JSON; - LATERALSTRING VARCHAR2(M_VCSIZ_32K); - VERTEX_UNDERLYING_DB_NAME_LIST SYS.ODCIVARCHAR2LIST; - VERTEX_DB_TABLE_OBJECT_OWNER SYS.ODCIVARCHAR2LIST; - EDGE_UNDERLYING_DB_NAME_LIST SYS.ODCIVARCHAR2LIST; - EDGE_DB_TABLE_OBJECT_OWNER SYS.ODCIVARCHAR2LIST; - EDGE_UNDERLYING_DB_NAME_LIST_WITH_PROPS SYS.ODCIVARCHAR2LIST; - EDGE_DB_TABLE_OBJECT_OWNER_WITH_PROPS SYS.ODCIVARCHAR2LIST; - EDGE_ELEMENT_NAME SYS.ODCIVARCHAR2LIST; - SUB_QUERY_STRING VARCHAR2(M_VCSIZ_32K); - QUERY_STRING VARCHAR2(M_VCSIZ_32K); - L_COLS INTEGER; - TAB_REC DBMS_SQL.DESC_TAB; - CUR SYS_REFCURSOR; - L_FLAG NUMBER; - L_JSON VARCHAR2(M_VCSIZ_4K); - VERTEX_ID_COLUMN_LIST SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST(); - EDGE_ID_COLUMN_LIST SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST(); - P1 NUMBER := 1; - V1 NUMBER := 1; - E1 NUMBER := 1; - VERTEX_COUNT NUMBER; - EDGE_COUNT NUMBER; - VERTEX_TABLE SYS.ODCIRIDLIST := SYS.ODCIRIDLIST(); - EDGE_TABLE SYS.ODCIRIDLIST := SYS.ODCIRIDLIST(); - V2 NUMBER := 1; - E2 NUMBER := 1; - L_HAVING_ELEMENT_ID BOOLEAN := FALSE; - COUNTER NUMBER := 0; -BEGIN - DBMS_SQL.DESCRIBE_COLUMNS( - C => CURS_ID, - COL_CNT => L_COLS, - DESC_T => TAB_REC - ); - FOR POS IN 1 .. L_COLS LOOP - CASE TAB_REC (POS).COL_TYPE - WHEN 119 THEN - DBMS_SQL.DEFINE_COLUMN (CURS_ID, POS, L_JSON, 2000); - L_HAVING_ELEMENT_ID := TRUE; - ELSE - NULL; - END CASE; - END LOOP; - --check if query results have element_id - IF L_HAVING_ELEMENT_ID = FALSE THEN - RAISE_APPLICATION_ERROR(-20000, 'Please add vertex_id/edge_id in CLOUMNS clause'); - END IF; - -- Print data fetched by query - LOOP - L_FLAG := DBMS_SQL.FETCH_ROWS (CURS_ID); - EXIT WHEN L_FLAG = 0; - COUNTER := COUNTER + 1; - IF ((PAGE_START < 0 - OR PAGE_SIZE <= 0) - AND (PAGE_START != -1 - AND PAGE_SIZE != -1)) THEN - RAISE_APPLICATION_ERROR(-20000, 'Please provide valid values for page_start and page_size parameter. page_start should be an integer equal to or greater than 0. page_size should be an integer greater than 0'); - END IF; - IF ((COUNTER > PAGE_START - AND COUNTER <= PAGE_START + PAGE_SIZE) - OR (PAGE_START = -1 - AND PAGE_START = -1) ) THEN - IF P1 = 1 THEN - FOR POS IN 1 .. L_COLS LOOP - IF TAB_REC(POS).COL_TYPE = 119 THEN - DBMS_SQL.COLUMN_VALUE (CURS_ID, POS, L_JSON); - IF JSON_EXISTS(L_JSON, '$.ELEM_TABLE') THEN - IF GRAPHNAME IS NULL AND GRAPHOWNER IS NULL THEN - GRAPHNAME := JSON_VALUE(L_JSON, '$.GRAPH_NAME'); - GRAPHOWNER := JSON_VALUE(L_JSON, '$.GRAPH_OWNER'); - END IF; - SELECT - ELEMENT_KIND INTO ELEMENT_NAME - FROM - ALL_PG_ELEMENTS - WHERE - ELEMENT_NAME = JSON_VALUE(L_JSON, '$.ELEM_TABLE') - AND GRAPH_NAME = GRAPHNAME - AND OWNER = GRAPHOWNER; - IF ELEMENT_NAME = 'VERTEX' THEN - VERTEX_ID_COLUMN_LIST.EXTEND; - VERTEX_ID_COLUMN_LIST(V1) := POS; - V1 := V1 + 1; - VERTEX_TABLE.EXTEND(); - VERTEX_TABLE(V2) := L_JSON; - V2 := V2 + 1; - ELSE - EDGE_ID_COLUMN_LIST.EXTEND; - EDGE_ID_COLUMN_LIST(E1) := POS; - E1 := E1 + 1; - EDGE_TABLE.EXTEND(); - EDGE_TABLE(E2) := L_JSON; - E2 := E2 + 1; - END IF; - END IF; - END IF; - END LOOP; - ELSE - FOR I IN 1..VERTEX_ID_COLUMN_LIST.COUNT LOOP - DBMS_SQL.COLUMN_VALUE (CURS_ID, VERTEX_ID_COLUMN_LIST(I), L_JSON); - VERTEX_TABLE.EXTEND(); - VERTEX_TABLE(V2) := L_JSON; - V2 := V2 + 1; - END LOOP; - FOR I IN 1..EDGE_ID_COLUMN_LIST.COUNT LOOP - DBMS_SQL.COLUMN_VALUE (CURS_ID, EDGE_ID_COLUMN_LIST(I), L_JSON); - EDGE_TABLE.EXTEND(); - EDGE_TABLE(E2) := L_JSON; - E2 := E2 + 1; - END LOOP; - END IF; - P1 := P1 + 1; - END IF; - END LOOP; - /**********************************************************************************/ - --get all underlyting db table names for vertices with properties - SELECT - DISTINCT ELEMENTS.OBJECT_NAME, - ELEMENTS.OBJECT_OWNER BULK COLLECT INTO VERTEX_UNDERLYING_DB_NAME_LIST, - VERTEX_DB_TABLE_OBJECT_OWNER - FROM - SYS.ALL_PG_ELEMENTS ELEMENTS - INNER JOIN SYS.ALL_PG_ELEMENT_LABELS ELEM_LABELS - ON (ELEMENTS.ELEMENT_NAME = ELEM_LABELS.ELEMENT_NAME - AND ELEMENTS.OWNER = ELEM_LABELS.OWNER - AND ELEMENTS.GRAPH_NAME = ELEM_LABELS.GRAPH_NAME ) - INNER JOIN SYS.ALL_PG_LABEL_PROPERTIES LABEL_PROPERTIES - ON (ELEM_LABELS.LABEL_NAME = LABEL_PROPERTIES.LABEL_NAME - AND ELEM_LABELS.OWNER = LABEL_PROPERTIES.OWNER - AND ELEM_LABELS.GRAPH_NAME = LABEL_PROPERTIES.GRAPH_NAME) - INNER JOIN SYS.ALL_PG_PROP_DEFINITIONS PROP_DEFINITIONS - ON PROP_DEFINITIONS.PROPERTY_NAME = LABEL_PROPERTIES.PROPERTY_NAME - WHERE - ELEMENTS.ELEMENT_KIND = 'VERTEX' - AND ELEMENTS.GRAPH_NAME = GRAPHNAME - AND ELEMENTS.OWNER = GRAPHOWNER; - --prepare query vertices string - - /* Example vertex query string - WITH VERTICES AS ( - SELECT - JSON_OBJECT ('id' VALUE JSON_VALUE(VT.COLUMN_VALUE, - '$.ELEM_TABLE') || JSON_QUERY(VT.COLUMN_VALUE, - '$.KEY_VALUE'), - 'properties' VALUE PROPERTIES_TABLE.PROPERTIES ABSENT ON NULL RETURNING CLOB) AS VERTEX - FROM - TABLE(:1) VT, - LATERAL( - SELECT - JSON_OBJECT('SAL' VALUE X.SAL, - 'AGE' VALUE X.AGE ABSENT - ON NULL) AS PROPERTIES - FROM - HR.VERTEX_3 X - WHERE - JSON_VALUE(VT.COLUMN_VALUE, - '$.ELEM_TABLE') = 'VERTEX_3' - AND X.V_ID= JSON_VALUE(VT.COLUMN_VALUE, - '$.KEY_VALUE.V_ID') UNION - SELECT - JSON_OBJECT('AGE' VALUE X.AGE, - 'SAL' VALUE X.SAL ABSENT - ON NULL) AS PROPERTIES - FROM - HR.VERTEX_1 X - WHERE - JSON_VALUE(VT.COLUMN_VALUE, - '$.ELEM_TABLE') = 'VERTEX_1' - AND X.V_ID= JSON_VALUE(VT.COLUMN_VALUE, - '$.KEY_VALUE.V_ID') UNION - SELECT - JSON_OBJECT('AGE' VALUE X.AGE ABSENT - ON NULL) AS PROPERTIES - FROM - HR.VERTEX_2 X - WHERE - JSON_VALUE(VT.COLUMN_VALUE, - '$.ELEM_TABLE') = 'VERTEX_2' - AND X.V_ID= JSON_VALUE(VT.COLUMN_VALUE, - '$.KEY_VALUE.V_ID') - ) PROPERTIES_TABLE - ) - SELECT - JSON_ARRAYAGG(VERTEX RETURNING CLOB) - FROM - VERTICES - */ - -- if there is vertex id, prepare vertex query string like above example query string - IF (VERTEX_TABLE.COUNT != 0) THEN - QUERY_STRING := 'with vertices as ('; - -- prepare string inside LATERAL() - LATERALSTRING := DBMS_GVT.PROPERTIES_LATERAL_STRING(VERTEX_UNDERLYING_DB_NAME_LIST, VERTEX_DB_TABLE_OBJECT_OWNER, 'VT', 'COLUMN_VALUE', GRAPHNAME, GRAPHOWNER, 'VERTEX'); - SUB_QUERY_STRING := 'select json_object (''id'' value json_value(VT.COLUMN_VALUE, ''$.ELEM_TABLE'') || json_query(VT.COLUMN_VALUE, ''$.KEY_VALUE''), ''properties'' value properties_table.PROPERTIES absent on null returning clob) as vertex from (select distinct * from table(:1)) VT outer apply(' - || LATERALSTRING - || ') properties_table'; - QUERY_STRING := QUERY_STRING - || SUB_QUERY_STRING; - QUERY_STRING := QUERY_STRING - || ') select json_arrayagg(vertex returning clob) from vertices'; - EXECUTE IMMEDIATE QUERY_STRING INTO VERTEX USING VERTEX_TABLE; - ELSE - -- if there is no vertex id in global temp table, return an empty array to vertex value - SELECT - JSON_ARRAY() INTO VERTEX; - END IF; - --get edges table name and underlying db table names for edges - SELECT - ELEMENTS.ELEMENT_NAME, - ELEMENTS.OBJECT_NAME, - ELEMENTS.OBJECT_OWNER BULK COLLECT INTO EDGE_ELEMENT_NAME, - EDGE_UNDERLYING_DB_NAME_LIST, - EDGE_DB_TABLE_OBJECT_OWNER - FROM - SYS.ALL_PG_ELEMENTS ELEMENTS - INNER JOIN SYS.ALL_PG_ELEMENT_LABELS ELEMENTS_LABELS - ON ELEMENTS.ELEMENT_NAME = ELEMENTS_LABELS.ELEMENT_NAME - AND ELEMENTS.GRAPH_NAME = ELEMENTS_LABELS.GRAPH_NAME - WHERE - ELEMENTS.ELEMENT_KIND = 'EDGE' - AND ELEMENTS.GRAPH_NAME = GRAPHNAME - AND ELEMENTS.OWNER = GRAPHOWNER; - --get all underlyting db table names for edges with properties - SELECT - DISTINCT ELEMENTS.OBJECT_NAME, - ELEMENTS.OBJECT_OWNER BULK COLLECT INTO EDGE_UNDERLYING_DB_NAME_LIST_WITH_PROPS, - EDGE_DB_TABLE_OBJECT_OWNER_WITH_PROPS - FROM - SYS.ALL_PG_ELEMENTS ELEMENTS - INNER JOIN SYS.ALL_PG_ELEMENT_LABELS ELEM_LABELS - ON (ELEMENTS.ELEMENT_NAME = ELEM_LABELS.ELEMENT_NAME - AND ELEMENTS.OWNER = ELEM_LABELS.OWNER - AND ELEMENTS.GRAPH_NAME = ELEM_LABELS.GRAPH_NAME ) - INNER JOIN SYS.ALL_PG_LABEL_PROPERTIES LABEL_PROPERTIES - ON (ELEM_LABELS.LABEL_NAME = LABEL_PROPERTIES.LABEL_NAME - AND ELEM_LABELS.OWNER = LABEL_PROPERTIES.OWNER - AND ELEM_LABELS.GRAPH_NAME = LABEL_PROPERTIES.GRAPH_NAME) - INNER JOIN SYS.ALL_PG_PROP_DEFINITIONS PROP_DEFINITIONS - ON PROP_DEFINITIONS.PROPERTY_NAME = LABEL_PROPERTIES.PROPERTY_NAME - WHERE - ELEMENTS.ELEMENT_KIND = 'EDGE' - AND ELEMENTS.GRAPH_NAME = GRAPHNAME - AND ELEMENTS.OWNER = GRAPHOWNER; - --prepare query vertices string - --prepare edge query string - - /*Example string - WITH EDGES AS ( - SELECT - JSON_OBJECT('id' VALUE JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') || JSON_QUERY(ET.COLUMN_VALUE, - '$.KEY_VALUE'), - 'source' IS ID_TABLE.SOURCE, - 'target' IS ID_TABLE.TARGET, - 'properties' VALUE PROPERTIES_TABLE.PROPERTIES ABSENT ON NULL RETURNING CLOB ) AS EDGE - FROM - TABLE(:1) ET, - LATERAL( - SELECT - 'VERTEX_1' || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, - 'VERTEX_3' || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET - FROM - HR.EDGE_1_3 EDGE_TABLE, - HR.VERTEX_1 SRC_TABLE, - HR.VERTEX_3 DST_TABLE - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') ='EDGE_1_3' - AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID - AND EDGE_TABLE.E_DST= DST_TABLE.V_ID UNION - SELECT - 'VERTEX_1' || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, - 'VERTEX_2' || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET - FROM - HR.EDGE_1_2 EDGE_TABLE, - HR.VERTEX_1 SRC_TABLE, - HR.VERTEX_2 DST_TABLE - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') ='EDGE_1_2' - AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID - AND EDGE_TABLE.E_DST= DST_TABLE.V_ID UNION - SELECT - 'VERTEX_1' || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, - 'VERTEX_1' || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET - FROM - HR.EDGE_1_1_B EDGE_TABLE, - HR.VERTEX_1 SRC_TABLE, - HR.VERTEX_1 DST_TABLE - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') ='EDGE_1_1_B' - AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID - AND EDGE_TABLE.E_DST= DST_TABLE.V_ID UNION - SELECT - 'VERTEX_1' || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, - 'VERTEX_1' || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET - FROM - HR.EDGE_1_1_B EDGE_TABLE, - HR.VERTEX_1 SRC_TABLE, - HR.VERTEX_1 DST_TABLE - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') ='EDGE_1_1_B' - AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID - AND EDGE_TABLE.E_DST= DST_TABLE.V_ID UNION - SELECT - 'VERTEX_2' || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, - 'VERTEX_1' || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET - FROM - HR.EDGE_2_1 EDGE_TABLE, - HR.VERTEX_2 SRC_TABLE, - HR.VERTEX_1 DST_TABLE - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') ='EDGE_2_1' - AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID - AND EDGE_TABLE.E_DST= DST_TABLE.V_ID UNION - SELECT - 'VERTEX_1' || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, - 'VERTEX_1' || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET - FROM - HR.EDGE_1_1_A EDGE_TABLE, - HR.VERTEX_1 SRC_TABLE, - HR.VERTEX_1 DST_TABLE - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') ='EDGE_1_1_A' - AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID - AND EDGE_TABLE.E_DST= DST_TABLE.V_ID UNION - SELECT - 'VERTEX_1' || JSON_OBJECT('V_ID' IS SRC_TABLE.V_ID ) AS SOURCE, - 'VERTEX_1' || JSON_OBJECT('V_ID' IS DST_TABLE.V_ID ) AS TARGET - FROM - HR.EDGE_1_1_A EDGE_TABLE, - HR.VERTEX_1 SRC_TABLE, - HR.VERTEX_1 DST_TABLE - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') ='EDGE_1_1_A' - AND EDGE_TABLE.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND EDGE_TABLE.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND EDGE_TABLE.E_SRC= SRC_TABLE.V_ID - AND EDGE_TABLE.E_DST= DST_TABLE.V_ID - ) ID_TABLE, - LATERAL( - SELECT - JSON_OBJECT('COST' VALUE X.E_C ABSENT - ON NULL) AS PROPERTIES - FROM - HR.EDGE_1_3 X - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') = 'EDGE_1_3' - AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') UNION - SELECT - JSON_OBJECT('COST' VALUE X.E_WEIGHT ABSENT - ON NULL) AS PROPERTIES - FROM - HR.EDGE_1_2 X - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') = 'EDGE_1_2' - AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') UNION - SELECT - JSON_OBJECT('COST' VALUE X.COST ABSENT - ON NULL) AS PROPERTIES - FROM - HR.EDGE_1_1_B X - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') = 'EDGE_1_1_B' - AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') UNION - SELECT - JSON_OBJECT('COST' VALUE X.COST ABSENT - ON NULL) AS PROPERTIES - FROM - HR.EDGE_1_1_B X - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') = 'EDGE_1_1_B' - AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') UNION - SELECT - JSON_OBJECT('COST' VALUE X.E_W ABSENT - ON NULL) AS PROPERTIES - FROM - HR.EDGE_2_1 X - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') = 'EDGE_2_1' - AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') UNION - SELECT - JSON_OBJECT('COST' VALUE X.E_COST ABSENT - ON NULL) AS PROPERTIES - FROM - HR.EDGE_1_1_A X - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') = 'EDGE_1_1_A' - AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') UNION - SELECT - JSON_OBJECT('COST' VALUE X.E_COST ABSENT - ON NULL) AS PROPERTIES - FROM - HR.EDGE_1_1_A X - WHERE - JSON_VALUE(ET.COLUMN_VALUE, - '$.ELEM_TABLE') = 'EDGE_1_1_A' - AND X.E_DST= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_DST') - AND X.E_SRC= JSON_VALUE(ET.COLUMN_VALUE, - '$.KEY_VALUE.E_SRC') - ) PROPERTIES_TABLE - ) - SELECT - JSON_ARRAYAGG(EDGE RETURNING CLOB) - FROM - EDGES - */ - -- if there is edge id, prepare edge query string like above example query string - IF (EDGE_TABLE.COUNT != 0) THEN - QUERY_STRING := 'WITH EDGES AS (SELECT JSON_OBJECT(''id'' value json_value(ET.COLUMN_VALUE, ''$.ELEM_TABLE'') || json_query(ET.COLUMN_VALUE, ''$.KEY_VALUE''),''source'' is id_table.source, ''target'' is id_table.target, ''properties'' value PROPERTIES_TABLE.PROPERTIES absent on null returning clob ) as edge FROM (select distinct * from table(:1)) ET outer apply('; - -- prepare string inside LATERAL() - LATERALSTRING := DBMS_GVT.PROPERTIES_LATERAL_STRING(EDGE_UNDERLYING_DB_NAME_LIST_WITH_PROPS, EDGE_DB_TABLE_OBJECT_OWNER_WITH_PROPS, 'ET', 'COLUMN_VALUE', GRAPHNAME, GRAPHOWNER, 'EDGE'); - QUERY_STRING := QUERY_STRING - || LATERALSTRING; - QUERY_STRING := QUERY_STRING - || ') properties_table, LATERAL('; - LATERALSTRING := DBMS_GVT.EDGE_LATERAL_STRING(EDGE_UNDERLYING_DB_NAME_LIST, EDGE_DB_TABLE_OBJECT_OWNER, EDGE_ELEMENT_NAME, 'ET', 'COLUMN_VALUE', GRAPHNAME, GRAPHOWNER); - QUERY_STRING := QUERY_STRING - || LATERALSTRING; - QUERY_STRING := QUERY_STRING - ||') id_table ) select JSON_ARRAYAGG(edge returning clob) from edges'; - EXECUTE IMMEDIATE QUERY_STRING INTO EDGE USING EDGE_TABLE; - ELSE - -- if there is no edge id in global temp table, return an empty array to edge value - SELECT - JSON_ARRAY() INTO EDGE; - END IF; - -- prepare the final result - SELECT - JSON_OBJECT('vertices' VALUE VERTEX, - 'edges' VALUE EDGE, - 'numResults' VALUE COUNTER RETURNING CLOB) INTO JSON_FILE - FROM - DUAL; - RETURN JSON_FILE; -END ORA_SQLGRAPH_TO_JSON; -/ +/* +APEX can render JSON in the format {"vertices" : [...], "edges":[...]} +In this function, we build two query strings, one for vertex and one for edge. +After we have query strings, we use dynamic sql to execute the quries and +put the result in required json format. +*/ +CREATE OR REPLACE FUNCTION ORA_SQLGRAPH_TO_JSON ( + CURS_ID INTEGER, + PAGE_START NUMBER DEFAULT NULL, + PAGE_SIZE NUMBER DEFAULT NULL +) RETURN CLOB + AUTHID CURRENT_USER IS + M_VCSIZ_4K CONSTANT PLS_INTEGER := 4000; + M_VCSIZ_32K CONSTANT PLS_INTEGER := 32672; + JSON_FILE CLOB; --the returned result + GRAPHNAME VARCHAR2(M_VCSIZ_4K); + ELEMENT_NAME VARCHAR2(M_VCSIZ_4K); + GRAPHOWNER VARCHAR2(M_VCSIZ_4K); + L_COLS INTEGER; + TAB_REC DBMS_SQL.DESC_TAB; + CUR SYS_REFCURSOR; + L_FLAG NUMBER; + L_JSON VARCHAR2(M_VCSIZ_4K); + VERTEX_ID_COLUMN_LIST SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST(); + EDGE_ID_COLUMN_LIST SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST(); + P1 NUMBER := 1; + V1 NUMBER := 1; + E1 NUMBER := 1; + VERTEX_TABLE SYS.ODCIRIDLIST := SYS.ODCIRIDLIST(); + EDGE_TABLE SYS.ODCIRIDLIST := SYS.ODCIRIDLIST(); + V2 NUMBER := 1; + E2 NUMBER := 1; + L_HAVING_ELEMENT_ID BOOLEAN := FALSE; + COUNTER NUMBER := 0; + VERTEX_GROUP SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST(); + EDGE_GROUP SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST(); +BEGIN + DBMS_SQL.DESCRIBE_COLUMNS( + C => CURS_ID, + COL_CNT => L_COLS, + DESC_T => TAB_REC + ); + + FOR POS IN 1 .. L_COLS LOOP + CASE TAB_REC (POS).COL_TYPE + WHEN 119 THEN + DBMS_SQL.DEFINE_COLUMN (CURS_ID, POS, L_JSON, 2000); + ELSE + NULL; + END CASE; + END LOOP; + + -- Print data fetched by query + WHILE DBMS_SQL.FETCH_ROWS (CURS_ID) != 0 LOOP + COUNTER := COUNTER + 1; + IF ((PAGE_START < 0 + OR PAGE_SIZE <= 0) + AND (PAGE_START IS NOT NULL + AND PAGE_SIZE IS NOT NULL)) THEN + RAISE_APPLICATION_ERROR(-20000, 'Please provide valid values for page_start and page_size parameter. page_start should be an integer equal to or greater than 0. page_size should be an integer greater than 0'); + END IF; + + IF ((COUNTER > PAGE_START + AND COUNTER <= PAGE_START + PAGE_SIZE) + OR (PAGE_START IS NULL + AND PAGE_START IS NULL) ) THEN + IF P1 = 1 THEN + FOR POS IN 1 .. L_COLS LOOP + IF TAB_REC(POS).COL_TYPE = 119 THEN + DBMS_SQL.COLUMN_VALUE (CURS_ID, POS, L_JSON); + IF JSON_EXISTS(L_JSON, '$.ELEM_TABLE') AND JSON_EXISTS(L_JSON, '$.GRAPH_OWNER') AND JSON_EXISTS(L_JSON, '$.GRAPH_NAME') AND JSON_EXISTS(L_JSON, '$.KEY_VALUE') THEN + L_HAVING_ELEMENT_ID := TRUE; + IF GRAPHNAME IS NULL AND GRAPHOWNER IS NULL THEN + GRAPHNAME := JSON_VALUE(L_JSON, '$.GRAPH_NAME'); + GRAPHOWNER := JSON_VALUE(L_JSON, '$.GRAPH_OWNER'); + END IF; + + SELECT + ELEMENT_KIND INTO ELEMENT_NAME + FROM + ALL_PG_ELEMENTS + WHERE + ELEMENT_NAME = JSON_VALUE(L_JSON, '$.ELEM_TABLE') + AND GRAPH_NAME = GRAPHNAME + AND OWNER = GRAPHOWNER; + IF ELEMENT_NAME = 'VERTEX' THEN + VERTEX_ID_COLUMN_LIST.EXTEND; + VERTEX_ID_COLUMN_LIST(V1) := POS; + V1 := V1 + 1; + VERTEX_TABLE.EXTEND(); + VERTEX_TABLE(V2) := L_JSON; + VERTEX_GROUP.EXTEND(); + VERTEX_GROUP(V2) := POS; + V2 := V2 + 1; + ELSE + EDGE_ID_COLUMN_LIST.EXTEND; + EDGE_ID_COLUMN_LIST(E1) := POS; + E1 := E1 + 1; + EDGE_TABLE.EXTEND(); + EDGE_TABLE(E2) := L_JSON; + EDGE_GROUP.EXTEND(); + EDGE_GROUP(E2) := POS; + E2 := E2 + 1; + END IF; + END IF; + END IF; + END LOOP; + ELSE + FOR I IN 1..VERTEX_ID_COLUMN_LIST.COUNT LOOP + DBMS_SQL.COLUMN_VALUE (CURS_ID, VERTEX_ID_COLUMN_LIST(I), L_JSON); + VERTEX_TABLE.EXTEND(); + VERTEX_TABLE(V2) := L_JSON; + VERTEX_GROUP.EXTEND(); + VERTEX_GROUP(V2) := VERTEX_ID_COLUMN_LIST(I); + V2 := V2 + 1; + END LOOP; + + FOR I IN 1..EDGE_ID_COLUMN_LIST.COUNT LOOP + DBMS_SQL.COLUMN_VALUE (CURS_ID, EDGE_ID_COLUMN_LIST(I), L_JSON); + EDGE_TABLE.EXTEND(); + EDGE_TABLE(E2) := L_JSON; + EDGE_GROUP.EXTEND(); + EDGE_GROUP(E2) := EDGE_ID_COLUMN_LIST(I); + E2 := E2 + 1; + END LOOP; + END IF; + + P1 := P1 + 1; + END IF; + END LOOP; + IF NOT L_HAVING_ELEMENT_ID AND COUNTER != 0 THEN + RAISE_APPLICATION_ERROR(-20000, 'Please add vertex_id/edge_id in CLOUMNS clause'); + END IF; + JSON_FILE := DBMS_GVT.BUILD_JSON(VERTEX_TABLE, EDGE_TABLE, COUNTER, GRAPHNAME, GRAPHOWNER, VERTEX_GROUP, EDGE_GROUP); + RETURN JSON_FILE; +END ORA_SQLGRAPH_TO_JSON; +/ \ No newline at end of file diff --git a/example/database/startup/01_setup.sh b/example/database/startup/01_setup.sh new file mode 100644 index 0000000..14e6141 --- /dev/null +++ b/example/database/startup/01_setup.sh @@ -0,0 +1,2 @@ +echo "Database user $GRAPH_USER identified by $GRAPH_PWD." +sqlplus -s "/ as sysdba" @/home/oracle/scripts/create-user.sql $GRAPH_USER $GRAPH_PWD \ No newline at end of file diff --git a/example/database/startup/02_create-tables.sh b/example/database/startup/02_create-tables.sh new file mode 100644 index 0000000..8a7134e --- /dev/null +++ b/example/database/startup/02_create-tables.sh @@ -0,0 +1,2 @@ +sqlplus -s $GRAPH_USER/$GRAPH_PWD@localhost:1521/freepdb1 @/home/oracle/scripts/create-tables.sql + diff --git a/example/database/startup/03_create-property-graph.sh b/example/database/startup/03_create-property-graph.sh new file mode 100644 index 0000000..dfe12aa --- /dev/null +++ b/example/database/startup/03_create-property-graph.sh @@ -0,0 +1 @@ +sqlplus -s $GRAPH_USER/$GRAPH_PWD@localhost:1521/freepdb1 @/home/oracle/scripts/create-property-graph.sql \ No newline at end of file diff --git a/example/database/startup/04_load-dataset.sh b/example/database/startup/04_load-dataset.sh new file mode 100644 index 0000000..317d396 --- /dev/null +++ b/example/database/startup/04_load-dataset.sh @@ -0,0 +1,4 @@ +sqlplus -s $GRAPH_USER/$GRAPH_PWD@localhost:1521/freepdb1 @/home/oracle/dataset/cities.sql +sqlplus -s $GRAPH_USER/$GRAPH_PWD@localhost:1521/freepdb1 @/home/oracle/dataset/airports.sql +sqlplus -s $GRAPH_USER/$GRAPH_PWD@localhost:1521/freepdb1 @/home/oracle/dataset/routes.sql + diff --git a/example/database/startup/05_create-functions.sh b/example/database/startup/05_create-functions.sh new file mode 100644 index 0000000..af90447 --- /dev/null +++ b/example/database/startup/05_create-functions.sh @@ -0,0 +1,3 @@ +sqlplus -s $GRAPH_USER/$GRAPH_PWD@localhost:1521/freepdb1 @/home/oracle/scripts/gvt-sqlgraph-to-json.sql +sqlplus -s $GRAPH_USER/$GRAPH_PWD@localhost:1521/freepdb1 @/home/oracle/scripts/cust-sqlgraph-json.sql + diff --git a/example/server/.env b/example/server/.env index 1e0e938..138578d 100644 --- a/example/server/.env +++ b/example/server/.env @@ -1,11 +1,6 @@ -DB_HOST_PROD=192.168.1.10 -DB_USER_PROD=YOUR_DB_USER -DB_PASS_PROD=YOUR_DB_PASS - DB_HOST_DEV=localhost DB_USER_DEV=graphuser -DB_PASS_DEV='password' - +DB_PASS_DEV='Welcome_1234#' DB_PORT=1521 DB_SERVICE=freepdb1 NODE_PORT=1337 \ No newline at end of file diff --git a/example/server/package.json b/example/server/package.json index bcf9fc6..c0829ba 100644 --- a/example/server/package.json +++ b/example/server/package.json @@ -1,6 +1,6 @@ { "name": "express-oracle", - "version": "0.0.1", + "version": "1.0.2", "description": "Express with oracle db", "main": "index.js", "scripts": { @@ -17,7 +17,6 @@ "express", "boilerplate" ], - "author": "Anatoliy Pechkin tolya.pechkin@gmail.com", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -43,4 +42,4 @@ "nodemon": "^3.0.2", "typescript": "^5.2.2" } -} \ No newline at end of file +} diff --git a/example/server/src/app.ts b/example/server/src/app.ts index ff7c21b..8c0cbea 100644 --- a/example/server/src/app.ts +++ b/example/server/src/app.ts @@ -1,30 +1,48 @@ -import { rowId, getRawGraph, labelFromId } from '@linkurious/ogma-oracle-parser'; -import bodyParser from 'body-parser'; -import cors from 'cors'; -import express from 'express'; -import oracledb from 'oracledb'; -import dbConfig from './config'; +import { + getRawGraph, + labelFromId, + rowId, +} from "@linkurious/ogma-oracle-parser"; +import bodyParser from "body-parser"; +import cors from "cors"; +import express from "express"; +import oracledb from "oracledb"; +import path from "path"; +import dbConfig from "./config"; const { user, password, connectString } = dbConfig; -const labelMap = new Map([[ - 'CITIE', 'CITY', -]]); +const labelMap = new Map([ + ["CITIES", "CITY"], + ["ROUTES", "ROUTE"], + ["AIRPORTS", "AIRPORT"], +]); export default function createApp() { const app = express(); - oracledb.getConnection({ - user, - password, - connectString, - }) + oracledb + .getConnection({ + user, + password, + connectString, + }) .then((conn) => { app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); - app.use(cors()); - app.get('/expand/:id', (req, res) => { - const label = labelMap.get(labelFromId(req.params.id)) || labelFromId(req.params.id); + app.use( + cors({ + origin: "*", + }) + ); + app.use( + "/", + express.static(path.resolve(__dirname, "../../client/dist")) + ); + app.get("/expand/:id", (req, res) => { + const label = + labelMap.get(labelFromId(req.params.id)) || + labelFromId(req.params.id); const index = rowId(req.params.id); const query = `select v, e - from graph_table ( + from graph_table ( openflights_graph match (v1 is ${label})-[e]-(v2) where (JSON_VALUE(VERTEX_ID(v1), ''$.KEY_VALUE.ID'') = ${index}) @@ -33,11 +51,10 @@ export default function createApp() { EDGE_ID(e) as e ) )`; - return getRawGraph({ query, conn }) - .then((r) => res.json(r)); + return getRawGraph({ query, conn }).then((r) => res.json(r)); }); - app.get('/node/:id', (req, res) => { + app.get("/node/:id", (req, res) => { const label = labelFromId(req.params.id); const index = rowId(req.params.id); const query = `select v @@ -49,10 +66,9 @@ export default function createApp() { VERTEX_ID(v1) as v ) )`; - return getRawGraph({ query, conn }) - .then((r) => res.json(r)); + return getRawGraph({ query, conn }).then((r) => res.json(r)); }); - app.get('/edge/:id', (req, res) => { + app.get("/edge/:id", (req, res) => { const label = labelFromId(req.params.id); const index = rowId(req.params.id); const query = `select e @@ -64,24 +80,25 @@ export default function createApp() { EDGE_ID(e1) as e ) )`; - return getRawGraph({ query, conn }) - .then((r) => res.json(r)); + return getRawGraph({ query, conn }).then((r) => res.json(r)); }); - app.get('/edges/:type/:pageStart?/:pageLength?/:maxResults?', (req, res) => { - const { type, pageStart, pageLength, maxResults } = req.params; - const query = `select e - from graph_table ( + app.get("/edges/:type/:pageStart/:pageLength", (req, res) => { + const { type, pageStart, pageLength } = req.params; + const query = `SELECT e + FROM graph_table ( openflights_graph - match ()-[e1 is ${type}]-() - columns ( - EDGE_ID(e1) as e + MATCH ()-[e1 IS ${type}]-() + COLUMNS ( + EDGE_ID(e1) AS e ) - )`; - - return getRawGraph({ query, conn, maxResults: +maxResults, pageStart: +pageStart, pageLength: +pageLength }) - .then((r) => res.json(r)); + ) + OFFSET ${pageStart} ROWS FETCH NEXT ${pageLength} ROWS ONLY`; + return getRawGraph({ + query, + conn, + }).then((r) => res.json(r)); }); - app.get('/nodes/:type', (req, res) => { + app.get("/nodes/:type", (req, res) => { const query = `select v from graph_table ( openflights_graph @@ -90,10 +107,10 @@ export default function createApp() { VERTEX_ID(v1) as v ) )`; - return getRawGraph({ query, conn }) - .then((r) => res.json(r)); + return getRawGraph({ query, conn, maxResults: 300 }).then((r) => + res.json(r) + ); }); }); return app; } - diff --git a/example/server/src/config.ts b/example/server/src/config.ts index 93d19c6..5da9dd6 100644 --- a/example/server/src/config.ts +++ b/example/server/src/config.ts @@ -1,11 +1,11 @@ -import { config } from 'dotenv'; +import { config } from "dotenv"; config(); -const env = process.env.NODE_ENV || 'dev'; -let host = ''; -let user = ''; -let password = ''; +const env = process.env.NODE_ENV || "dev"; +let host = ""; +let user = ""; +let password = ""; -if (env === 'dev') { +if (env === "dev") { host = process.env.DB_HOST_DEV!; user = process.env.DB_USER_DEV!; password = process.env.DB_PASS_DEV!; @@ -16,7 +16,7 @@ if (env === 'dev') { } const port = process.env.DB_PORT; const service = process.env.DB_SERVICE; -const connectString = host + ':' + port + '/' + service; +const connectString = host + ":" + port + "/" + service; const dbConfig = { user: user, @@ -25,4 +25,3 @@ const dbConfig = { }; export default dbConfig; - diff --git a/example/server/src/index.ts b/example/server/src/index.ts index be8d99a..6746c15 100644 --- a/example/server/src/index.ts +++ b/example/server/src/index.ts @@ -1,7 +1,7 @@ -import createApp from './app'; +import createApp from "./app"; const app = createApp(); const port = process.env.NODE_PORT; app.listen(port, function () { - console.log('Express server listening on port ' + port); + console.log("Express server listening on port " + port); }); diff --git a/package-lock.json b/package-lock.json index 392abb8..753a024 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@linkurious/ogma-oracle-parser", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@linkurious/ogma-oracle-parser", - "version": "1.0.1", + "version": "1.0.2", "license": "ISC", "devDependencies": { "@linkurious/eslint-config-ogma": "^1.0.5", diff --git a/package.json b/package.json index 7c8c975..a5faddc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@linkurious/ogma-oracle-parser", - "version": "1.0.1", + "version": "1.0.2", "description": "Parses responses from Oracle graph DB into Ogma format", "main": "dist/ogma-oracle-parser.umd.js", "module": "dist/ogma-oracle-parser.umd.js", @@ -27,7 +27,8 @@ "predocs:publish": "npm run docs:build", "docs:publish": "gh-pages -d docs/.vitepress/dist", "test:unit": "vitest run --coverage --reporter=junit --reporter=default --outputFile reports/unit/junit-test-results.xml", - "test:dev": "vitest" + "test:dev": "vitest", + "bump:patch": "npm version --no-git-tag-version patch" }, "author": "Leo Nicolle", "license": "Apache-2.0",