This is an automated email from the ASF dual-hosted git repository.
ako pushed a commit to branch ageviewer_go
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/ageviewer_go by this push:
new 5a0b58a4 [Ageviewer-go] Retrieve metadata of all the graphs within a
connected database. (#896)
5a0b58a4 is described below
commit 5a0b58a4a9ceae08f11aadcb67241a9b3a3d8495
Author: Kamlesh Kumar <[email protected]>
AuthorDate: Tue May 30 09:19:15 2023 +0500
[Ageviewer-go] Retrieve metadata of all the graphs within a connected
database. (#896)
* Function to disconnect database add to backend
* Added functionalities to read query response dynamically
* Meatdata functionalities changes to retrieve data of all graphs, rather a
single graph
---
backend/APIfunctions.js | 84 ++++++++++++++++++++++------------------------
backend/db/age.go | 9 ++---
backend/main.go | 2 +-
backend/models/graph.go | 6 ++--
backend/models/metadata.go | 21 +++++++++---
backend/routes/connect.go | 3 ++
backend/routes/query.go | 60 ++++++++++++++++++++++++++-------
7 files changed, 115 insertions(+), 70 deletions(-)
diff --git a/backend/APIfunctions.js b/backend/APIfunctions.js
index 5e03ea3d..d96bc2d7 100644
--- a/backend/APIfunctions.js
+++ b/backend/APIfunctions.js
@@ -1,18 +1,18 @@
// some pre-requisites to run these functions on desktop enviroment
-// const fetch = require("node-fetch");
-// const readline = require("readline");
+const fetch = require("node-fetch");
+const readline = require("readline");
-// const rl = readline.createInterface({
-// input: process.stdin,
-// output: process.stdout
-// });
+const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout
+ });
// Test data which will come from the frontend
const conn = {
port: "5432",
host: "localhost",
- password: "ABCDEFGH",
+ password: "Welcome@1",
user: "kamleshk",
dbname: "demodb",
ssl: "disable",
@@ -49,31 +49,27 @@ function connect() {
}
function queryMetadata() {
- const graph = {
- name: "demo_graph",
- };
-
fetch('http://localhost:8080/query/metadata', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': cookies.join('; '),
},
- body: JSON.stringify(graph),
})
.then(response => {
console.log(response.status);
-
return response.json();
})
.then(data => {
- console.log(data);
+ // visualize the data in formatted ways
+ console.log(JSON.stringify(data, null, 2));
})
.catch(error => {
console.error(error);
});
}
+
function query() {
const payload = {
query: "SELECT * FROM cypher('demo_graph', $$ MATCH (v) RETURN v $$) as (v
agtype);",
@@ -124,33 +120,33 @@ function disconnect() {
// dummy function to test the above functions
-// let choice;
-// function chooseFunction() {
-
-// rl.question('Enter the function number: ', (input) => {
-// const choice = parseInt(input);
-// if (Number.isNaN(choice) || choice < 1 || choice > 4) {
-// console.log('Invalid choice. Please try again.');
-// } else {
-// switch (choice) {
-// case 1:
-// connect();
-// break;
-// case 2:
-// queryMetadata();
-// break;
-// case 3:
-// query();
-// break;
-// case 4:
-// disconnect();
-// break;
-// }DisconnectFromDb
-// }
-// // rl.close();
-// chooseFunction()
-// });
-
-// }
-
-// chooseFunction();
\ No newline at end of file
+let choice;
+function chooseFunction() {
+
+rl.question('Enter the function number: ', (input) => {
+ const choice = parseInt(input);
+ if (Number.isNaN(choice) || choice < 1 || choice > 4) {
+ console.log('Invalid choice. Please try again.');
+ } else {
+ switch (choice) {
+ case 1:
+ connect();
+ break;
+ case 2:
+ queryMetadata();
+ break;
+ case 3:
+ query();
+ break;
+ case 4:
+ disconnect();
+ break;
+ }
+ }
+ // rl.close();
+ chooseFunction()
+ });
+
+}
+
+chooseFunction();
\ No newline at end of file
diff --git a/backend/db/age.go b/backend/db/age.go
index 36849be1..f826bbd6 100644
--- a/backend/db/age.go
+++ b/backend/db/age.go
@@ -5,13 +5,14 @@ CREATE EXTENSION IF NOT EXISTS age;
LOAD 'age';
`
const SET_SEARCH_PATH = `SET search_path = ag_catalog, "$user", public;`
-const META_DATA_11 = `SELECT label, cnt, kind FROM (
+
+const META_DATA_11 = `SELECT label, cnt, kind, namespace, namespace_id, graph,
relation::text FROM (
SELECT c.relname AS label, n.oid as namespace_id, c.reltuples AS cnt
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n
ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
- AND n.nspname = $1
+
) as q1
JOIN ag_graph as g ON q1.namespace_id = g.namespace
INNER JOIN ag_label as label
@@ -19,13 +20,13 @@ INNER JOIN ag_label as label
ON label.name = q1.label
AND label.graph = g.oid;`
-const META_DATA_12 = `SELECT label, cnt, kind FROM (
+const META_DATA_12 = `SELECT label, cnt, kind, namespace, namespace_id, graph,
relation::text FROM (
SELECT c.relname AS label, n.oid as namespace_id, c.reltuples AS cnt
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n
ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
- AND n.nspname = $1
+
) as q1
JOIN ag_graph as g ON q1.namespace_id = g.namespace
INNER JOIN ag_label as label
diff --git a/backend/main.go b/backend/main.go
index 677ceb82..7d0db213 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -15,7 +15,7 @@ func main() {
app := echo.New()
gob.Register(models.Connection{})
app.Use(session.UserSessions())
-
+
app.POST("/connect", routes.ConnectToDb)
app.POST("/disconnect", routes.DisconnectFromDb)
diff --git a/backend/models/graph.go b/backend/models/graph.go
index cb5df677..545bb182 100644
--- a/backend/models/graph.go
+++ b/backend/models/graph.go
@@ -15,7 +15,7 @@ type Graph struct {
GetMetaData returns the metadata for a given graph instance g, based on the
version of the database.
and returns a set of rows and an error (if any)
*/
-func (g *Graph) GetMetaData(conn *sql.DB, v int, dataChan chan<- *sql.Rows,
errorChan chan<- error) {
+func GetMetaData(conn *sql.DB, v int, dataChan chan<- *sql.Rows, errorChan
chan<- error) {
defer conn.Close()
var data *sql.Rows
@@ -23,9 +23,9 @@ func (g *Graph) GetMetaData(conn *sql.DB, v int, dataChan
chan<- *sql.Rows, erro
switch v {
case 11:
- data, err = conn.Query(db.META_DATA_11, g.Name)
+ data, err = conn.Query(db.META_DATA_11)
case 12:
- data, err = conn.Query(db.META_DATA_12, g.Name)
+ data, err = conn.Query(db.META_DATA_12)
default:
err = errors.New("unsupported version")
}
diff --git a/backend/models/metadata.go b/backend/models/metadata.go
index af62f537..309d89db 100644
--- a/backend/models/metadata.go
+++ b/backend/models/metadata.go
@@ -1,14 +1,25 @@
package models
-// MetaDataContainer is a struct that contains the metadata for a graph. It
contains a list of nodes and a list of edges.
+// struct that holds the graph metadata and user role information.
type MetaDataContainer struct {
+ Graphs map[string]GraphData
+}
+
+// struct that contains separate slices for storing nodes and edges
+type GraphData struct {
Nodes []MetaData
Edges []MetaData
}
-// MetaData is a struct that contains the metadata for a node or an edge.
+// struct that represents a single row of metadata from the database.
+// It contains information about the label, count, kind, namespace,
namespace_id,
+// graph, and relation.
type MetaData struct {
- Label string
- Cnt int
- Kind string
+ Label string
+ Cnt int
+ Kind string
+ NameSpace string
+ Namespace_id int
+ Graph string
+ Relation string
}
diff --git a/backend/routes/connect.go b/backend/routes/connect.go
index 71c82e0c..407999de 100644
--- a/backend/routes/connect.go
+++ b/backend/routes/connect.go
@@ -16,6 +16,7 @@ set to "connected". It handles errors related to invalid
data, connection establ
*/
func ConnectToDb(c echo.Context) error {
udata, err := models.FromRequestBody(c)
+
if err != nil {
return echo.NewHTTPError(400, "invalid data")
}
@@ -30,6 +31,7 @@ func ConnectToDb(c echo.Context) error {
defer db.Close()
sess := c.Get("database").(*sessions.Session)
sess.Values["db"] = udata
+
err = sess.Save(c.Request(), c.Response().Writer)
if err != nil {
return echo.NewHTTPError(400, "could not save session")
@@ -45,6 +47,7 @@ JSON response with a status message indicating that the
disconnection was succes
*/
func DisconnectFromDb(c echo.Context) error {
sess := c.Get("database").(*sessions.Session)
+
dbObj := sess.Values["db"]
if dbObj == nil {
return echo.NewHTTPError(400, "no database connection found")
diff --git a/backend/routes/query.go b/backend/routes/query.go
index 39aedf3b..c5da65ff 100644
--- a/backend/routes/query.go
+++ b/backend/routes/query.go
@@ -100,39 +100,73 @@ func Cypher(ctx echo.Context) error {
}
/*
-retrieves graph metadata for a given graph, and returns it as JSON. It uses
the graph object to determine
-which graph to query, and the user object to determine the database version.
The retrieved metadata is
-then organized into two lists: one for nodes and one for edges, before being
returned as a JSON response.
+queries the database for graph metadata, such as labels, count, kind, and
namespace, and organizes this information
+into a structured format, separating nodes and edges for each unique
namespace. The function then returns this metadata
+as a JSON object.
*/
func GraphMetaData(c echo.Context) error {
+ // Create channels to handle data and errors
dataChan := make(chan *sql.Rows, 1)
errChan := make(chan error, 1)
+ // Retrieve user and connection information from the context
user := c.Get("user").(models.Connection)
conn := c.Get("conn").(*sql.DB)
- graph := models.Graph{}
- c.Bind(&graph)
+
+ // Analyze the database for faster processing
conn.Exec(db.ANALYZE)
- go graph.GetMetaData(conn, user.Version, dataChan, errChan)
+
+ // Run GetMetaData in a goroutine
+ models.GetMetaData(conn, user.Version, dataChan, errChan)
+
+ // Wait for the data and error results
data, err := <-dataChan, <-errChan
+ // Check if there is any error
if err != nil {
return echo.NewHTTPError(400, err.Error())
}
- results := models.MetaDataContainer{}
+ // Initialize a new MetaDataContainer with an empty map for Graphs
+ results := models.MetaDataContainer{
+ Graphs: make(map[string]models.GraphData),
+ }
+
+ // Iterate through the data rows
for data.Next() {
row := models.MetaData{}
- err := data.Scan(&row.Label, &row.Cnt, &row.Kind)
- if row.Kind == "v" {
- results.Nodes = append(results.Nodes, row)
- } else {
- results.Edges = append(results.Edges, row)
- }
+ // Scan the row data into the MetaData struct
+ err := data.Scan(&row.Label, &row.Cnt, &row.Kind,
&row.NameSpace, &row.Namespace_id, &row.Graph, &row.Relation)
if err != nil {
print(err.Error())
}
+
+ // Check for empty namespace (no graphs) and return an error
+ if row.NameSpace == "" {
+ return echo.NewHTTPError(400, "No Graph exists for the
database")
+ }
+
+ // Initialize GraphData if it doesn't exist for the current
namespace
+ if _, exists := results.Graphs[row.NameSpace]; !exists {
+ results.Graphs[row.NameSpace] = models.GraphData{}
+ }
+
+ // Get the current GraphData for the namespace
+ graphData := results.Graphs[row.NameSpace]
+
+ // Append the row data to Nodes or Edges based on the Kind
+ if row.Kind == "v" {
+ graphData.Nodes = append(graphData.Nodes, row)
+ } else {
+ graphData.Edges = append(graphData.Edges, row)
+ }
+
+ // Update the GraphData in the results map
+ results.Graphs[row.NameSpace] = graphData
}
+
+ // Return the final results as JSON
+
return c.JSON(200, results)
}