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)
 }

Reply via email to