├── .github └── workflows │ └── docker-publish.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── NOTICE.md ├── README.md ├── cmd └── server │ ├── handlers.go │ └── server.go ├── docker └── entrypoint.sh ├── go.mod ├── go.sum ├── gremlin-go ├── LICENSE ├── NOTICE ├── README.md ├── design.md ├── docker-compose.yml ├── driver │ ├── README.md │ ├── anonymousTraversal.go │ ├── authInfo.go │ ├── bytecode.go │ ├── bytecode_test.go │ ├── client.go │ ├── client_test.go │ ├── connection.go │ ├── connectionPool.go │ ├── connectionPool_test.go │ ├── connection_test.go │ ├── cucumber │ │ ├── cucumberSteps_test.go │ │ ├── cucumberWorld.go │ │ └── gremlin.go │ ├── driverRemoteConnection.go │ ├── driverRemoteConnection_test.go │ ├── error_codes.go │ ├── gorillaTransporter.go │ ├── gorillaTransporter_test.go │ ├── graph.go │ ├── graphBinary.go │ ├── graphBinary_test.go │ ├── graphTraversal.go │ ├── graphTraversalSource.go │ ├── graphTraversalSource_test.go │ ├── graph_test.go │ ├── graphsonSerializer.go │ ├── logger.go │ ├── performance │ │ └── performanceSuite.go │ ├── protocol.go │ ├── protocol_test.go │ ├── request.go │ ├── requestOptions.go │ ├── requestOptions_test.go │ ├── request_test.go │ ├── resources │ │ ├── error-messages │ │ │ └── en.json │ │ ├── logger-messages │ │ │ └── en.json │ │ └── resources.go │ ├── response.go │ ├── result.go │ ├── resultSet.go │ ├── resultSet_test.go │ ├── result_test.go │ ├── serializer.go │ ├── serializer_test.go │ ├── strategies.go │ ├── strategies_test.go │ ├── translator.go │ ├── translator_test.go │ ├── transporter.go │ ├── transporterFactory.go │ ├── traversal.go │ ├── traversal_test.go │ └── user_agent.go ├── examples │ ├── basic_gremlin.go │ ├── connections.go │ ├── go.mod │ ├── go.sum │ └── modern_traversals.go ├── go.mod ├── go.sum ├── pom.xml └── run.sh ├── html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── index.html │ ├── logo.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── AuthContext.js │ ├── ProtectedRoute.js │ ├── StatusContext.js │ ├── components │ │ ├── common │ │ │ ├── connection │ │ │ │ └── Gremlin.js │ │ │ ├── gson │ │ │ │ └── GsonUtils.js │ │ │ └── vis │ │ │ │ ├── ForceGraph.js │ │ │ │ ├── ManyBodyFast.js │ │ │ │ ├── Vis.js │ │ │ │ └── canvas │ │ │ │ ├── Canvas.js │ │ │ │ ├── ContextMenu.js │ │ │ │ ├── DetailsPanel.js │ │ │ │ ├── Labels.js │ │ │ │ ├── Legend.js │ │ │ │ ├── SearchBar.js │ │ │ │ └── Watermark.js │ │ ├── navigation │ │ │ ├── HeaderDisplay.js │ │ │ ├── ProfileDropdown.js │ │ │ ├── SidebarLayout.js │ │ │ └── SystemStatusButton.js │ │ └── query │ │ │ ├── QueryWidget.js │ │ │ ├── code │ │ │ ├── CodeEditor.js │ │ │ └── CodePanel.js │ │ │ ├── context │ │ │ ├── QueryContext.js │ │ │ └── VisDataContext.js │ │ │ ├── props │ │ │ └── usePropsPrefetch.js │ │ │ ├── responseview │ │ │ ├── GsonView.js │ │ │ └── ResponseView.js │ │ │ └── vis │ │ │ ├── Help.js │ │ │ ├── ProgressBar.js │ │ │ └── Vis.js │ ├── images │ │ ├── horizontal_logo.png │ │ └── logo.png │ ├── index.css │ ├── index.js │ ├── reportWebVitals.js │ ├── screens │ │ ├── Login.js │ │ └── QueryWidgetScreen.js │ ├── setupProxy.js │ ├── setupTests.js │ ├── styles │ │ └── xterm.css │ └── theme.js ├── tailwind.config.js └── third-party-licenses.txt ├── integrationtest ├── janusgraph │ ├── cmd.txt │ ├── docker-compose.yml │ └── init │ │ ├── modern.groovy │ │ └── test.groovy └── puppygraph │ ├── docker-compose.yml │ └── schema.json ├── lib ├── auth.go ├── config.go ├── encrypt.go ├── gremlin.go └── log.go ├── makefile ├── screenshot.png └── tool └── gremlin_client.py /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: 'Release tag' 8 | required: true 9 | 10 | env: 11 | DOCKER_IMAGE_NAME: puppygraph-query 12 | 13 | jobs: 14 | build-and-push: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Set up QEMU 21 | uses: docker/setup-qemu-action@v3 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v3 24 | - name: Login to Docker Hub 25 | uses: docker/login-action@v3 26 | with: 27 | username: ${{ secrets.DOCKER_USERNAME }} 28 | password: ${{ secrets.DOCKER_PASSWORD }} 29 | - name: Build and push 30 | uses: docker/build-push-action@v5 31 | with: 32 | push: true 33 | platforms: linux/amd64,linux/arm64 34 | tags: | 35 | ${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.event.inputs.release_tag }} 36 | ${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:latest 37 | - name: Create Release 38 | id: create_release 39 | uses: actions/create-release@v1 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.PAT_RELEASE }} 42 | with: 43 | tag_name: ${{ github.event.inputs.release_tag }} 44 | release_name: Release ${{ github.event.inputs.release_tag }} 45 | body: | 46 | Docker Hub image: [${{ env.IMAGE_NAME }}:${{ github.event.inputs.release_tag }}](https://hub.docker.com/r/${{ secrets.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}/tags) 47 | draft: false 48 | prerelease: false 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nohup.out 2 | build/ 3 | html/build/ 4 | html/node_modules/ 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # React build 2 | FROM node:20.10-alpine as react-build 3 | COPY html/ /usr/src/app 4 | WORKDIR /usr/src/app 5 | RUN npm install 6 | RUN npm run build 7 | 8 | # Golang build 9 | FROM golang:1.21.0 AS server 10 | ARG TARGETARCH 11 | ARG TARGETOS 12 | 13 | WORKDIR /app 14 | 15 | COPY ./gremlin-go ./gremlin-go 16 | COPY ./cmd ./cmd 17 | COPY ./lib ./lib 18 | COPY go.mod go.sum makefile ./ 19 | 20 | RUN < puppygraph/puppygraph-query:latest 19 | ``` 20 | 21 | Visit localhost:8081 and enter username/password to visit the UI. 22 | 23 | ### Use Gremlin Server username/password for authentication 24 | 25 | If gremlin server requires authentication, enable USE_GREMLIN_AUTH=true, the server will use gremlin to validate the username and password for login. 26 | 27 | Example: 28 | 29 | ```docker run -d --rm -p 8081:8081 --name puppygraph-query -e PORT=8081 -e USE_GREMLIN_AUTH=true -e GREMLINSERVER_HOST= puppygraph/puppygraph-query:latest``` 30 | 31 | ### Other environment options 32 | 33 | - `GREMLINSERVER_URL`: Override `GREMLINSERVER_HOST` and `GREMLINSERVER_PATH`, use the full URL. E.g. `GREMLINSERVER_URL=ws://127.0.0.1:8182/gremlin` 34 | - `GREMLINSERVER_ALIAS`: Append a `{'aliases': {'key': 'value'}}` to the gremlin request. E.g. `key:value` 35 | - `GREMLINSERVER_SKIPCERTVERIFY`: Skip the TLS token verification for testing. E.g. `GREMLINSERVER_SKIPCERTVERIFY=true` 36 | - `HTTP_PROXY`: Proxy options from golang html library https://pkg.go.dev/net/http#ProxyFromEnvironment. E.g. `HTTP_PROXY=http://proxyIp:proxyPort` 37 | 38 | ## Features 39 | 40 | 1. **Run gremlin query and visualize the response**. Input your gremlin query on the left panel, the UI will automatically visualize the response based on the gremlin response type (vertices, edges, paths). 41 | 1. Queries are automatically stored in the browser localStorage. 42 | 1. Pick the graph **layout** between radial, vertical and force graph on the top right selector. 43 | 1. View the vertex and edge labels on the legend top-right. You can **change colors** for visualization by click the color rectangle on the legend. 44 | 1. Use mouse to **drag** on the canvas to move the view. 45 | 1. **Zoom**-in and out with mouse wheel on the canvas. 46 | 1. Use mouse to **drag and drop** any vertices to reposition them. 47 | 1. Left click on a vertex to **view the properties**. 48 | 1. Hover on a vertex and then left click on it's connected edge to **view the properties of an edge**. 49 | 1. Right click on a vertex to open the **action menu**: 50 | 1. *Center* - Move the view to the vertex. 51 | 1. *Query & view properties* - View the properties of the vertex. 52 | 1. *Expand with edge label* - Pick or input an edge label, generate the query to expand one-hop from the given vertex, and add the results to the view. 53 | 1. *Expand with all edge labels* - Generate the query to expand one-hop from the given vertex, and add all the results to the view. 54 | 1. Right click on the canvas to open the **graph action menu**: 55 | 1. *Reset view* - Move the view to origin with default zooming. 56 | 1. *Refresh layout* - Rerun the layout algorithm and automatically reposition the vertices. 57 | 1. *Full screen* - Make the canvas display fullscreen. 58 | 1. *Prefetch props and enable search* - Prefetch properties for all elements on the canvas and enable property search text box. 59 | 1. *Scale to fit* - Automatically zoom-in or out the view to include as much content as possible. 60 | 1. *Toggle labels* - Display labels for elements on the canvas. 61 | 1. *Toogle grid* - Display grid on the canvas. 62 | 1. Property prefetch and related features 63 | 1. To enable **search on properties**, right click on the canvas and make sure "Prefetch props and enable search" is selected. When it's enabled, the UI will automatically prefetch the properties in batches from the backend and gremlin server. Input search terms in the format like "id=1" or "code=LAX" to search the elements on the canvas. 64 | 1. To **customize the label format**, right click on the canvas and make sure "Prefetch props and enable search" is selected. When it's enabled, click on the legend color edit panel a new text field will be displayed for label editing. Use a format string like `ID: {id}`, or `{code} - {city}` to render the properties as labels. 65 | 66 | ## Build 67 | 68 | ### Prerequisite 69 | 70 | 1. make 71 | 1. docker 72 | 1. docker compose (for testing) 73 | 74 | ### Build 75 | 76 | Build docker image: `make docker`. The command will build a docker image `puppygraph/puppygraph-query:latest`. 77 | 78 | ## Test 79 | 80 | ```cd integrationtest/puppygraph && docker compose up``` 81 | 82 | The command will start docker and puppygraph, initialize puppygraph with a built-in "modern graph" and connect puppygraph query to it. 83 | 84 | ## Local build 85 | 86 | To build and run the project locally. 87 | 88 | 1. Make sure golang (>=18) is installed on the local server. 89 | ``` 90 | curl -OL https://go.dev/dl/go1.20.7.linux-amd64.tar.gz 91 | sudo tar -C /usr/local -xf go1.20.7.linux-amd64.tar.gz 92 | ``` 93 | Add `export PATH=$PATH:/usr/local/go/bin` to bashrc 94 | 95 | 2. Make sure you can run `npm run build` under the `html` folder to build the React app. Install nodejs. Use this: https://github.com/nodesource/distributions/blob/master/README.md. Make sure nodejs version is >= 20. 96 | ``` 97 | cd html 98 | npm install 99 | ``` 100 | 101 | 3. To build and run 102 | ``` 103 | make html build/server 104 | PORT=8081 PUPPYGRAPH_USERNAME=puppygraph PUPPYGRAPH_PASSWORD=puppygraph123 GREMLINSERVER_HOST= build/server 105 | ``` 106 | -------------------------------------------------------------------------------- /cmd/server/handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "sync" 9 | "uiserver/lib" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | type ServerStatus struct { 16 | GremlinServer string 17 | GremlinHealthy string 18 | PrefetchPageSize int 19 | WatermarkText string 20 | } 21 | 22 | func statusHandler(c *gin.Context) { 23 | v, exists := c.Get("conf") 24 | if !exists { 25 | c.JSON(http.StatusInternalServerError, "Cannot load config") 26 | } 27 | config := v.(*lib.Config) 28 | 29 | result := "OK" 30 | ok, err := lib.Healthcheck(c, config) 31 | if !ok { 32 | if err != nil { 33 | logrus.Infof("Healthcheck error: %v", err) 34 | result = "Error" 35 | } else { 36 | result = "Empty" 37 | } 38 | } 39 | 40 | url := lib.GetWsUrl(config) 41 | status := ServerStatus{ 42 | GremlinServer: url, 43 | GremlinHealthy: result, 44 | PrefetchPageSize: config.Prefetch.BatchCount * config.Prefetch.BatchSize, 45 | WatermarkText: config.Customization.Watermark, 46 | } 47 | c.JSON(http.StatusOK, status) 48 | } 49 | 50 | type SubmitRequest struct { 51 | Query string `json:"query"` 52 | } 53 | 54 | func submitHandler(c *gin.Context) { 55 | v, exists := c.Get("conf") 56 | if !exists { 57 | c.JSON(http.StatusInternalServerError, "Cannot load config") 58 | } 59 | config := v.(*lib.Config) 60 | 61 | var req SubmitRequest 62 | if err := c.BindJSON(&req); err != nil { 63 | c.JSON(http.StatusBadRequest, "Invalid request") 64 | return 65 | } 66 | 67 | response, err := lib.Submit(c, config, req.Query) 68 | if err != nil { 69 | c.JSON(http.StatusBadRequest, fmt.Sprintf("gremlin query error: %v", err)) 70 | return 71 | } 72 | 73 | responseBytes, err := json.Marshal(response) 74 | if err != nil { 75 | c.JSON(http.StatusBadRequest, fmt.Sprintf("gremlin query parse error: %v", err)) 76 | return 77 | } 78 | 79 | c.Data(http.StatusOK, "application/json", responseBytes) 80 | } 81 | 82 | func getPropsHandler(c *gin.Context) { 83 | v, exists := c.Get("conf") 84 | if !exists { 85 | c.JSON(http.StatusInternalServerError, "Cannot load config") 86 | return 87 | } 88 | config := v.(*lib.Config) 89 | 90 | var requestBody struct { 91 | Type string `json:"type"` 92 | IDs []string `json:"ids"` 93 | } 94 | 95 | if err := c.BindJSON(&requestBody); err != nil { 96 | c.JSON(http.StatusBadRequest, "Invalid request body") 97 | return 98 | } 99 | 100 | elementType := requestBody.Type 101 | ids := requestBody.IDs 102 | 103 | if elementType != "V" && elementType != "E" { 104 | c.JSON(http.StatusBadRequest, fmt.Sprintf("Invalid element type: %s", elementType)) 105 | return 106 | } 107 | 108 | if len(ids) == 0 { 109 | c.JSON(http.StatusBadRequest, "Missing ids") 110 | return 111 | } 112 | 113 | if len(ids) > config.Prefetch.BatchCount*config.Prefetch.BatchSize { 114 | c.JSON(http.StatusBadRequest, "Maximum number of ids exceeded") 115 | return 116 | } 117 | 118 | batchSize := config.Prefetch.BatchSize 119 | numBatches := (len(ids) + batchSize - 1) / batchSize 120 | var combinedResult lib.GsonResponse 121 | 122 | var wg sync.WaitGroup 123 | wg.Add(numBatches) 124 | 125 | for i := 0; i < numBatches; i++ { 126 | go func(batchIndex int) { 127 | defer wg.Done() 128 | 129 | start := batchIndex * batchSize 130 | end := (batchIndex + 1) * batchSize 131 | if end > len(ids) { 132 | end = len(ids) 133 | } 134 | 135 | batchIDs := ids[start:end] 136 | quotedIDs := make([]string, len(batchIDs)) 137 | for i, id := range batchIDs { 138 | quotedIDs[i] = fmt.Sprintf(`"%s"`, id) 139 | } 140 | 141 | var query string 142 | if elementType == "V" { 143 | query = fmt.Sprintf("g.V(%s).elementMap()", strings.Join(quotedIDs, ",")) 144 | } else { 145 | query = fmt.Sprintf("g.E(%s).elementMap()", strings.Join(quotedIDs, ",")) 146 | } 147 | result, err := lib.Submit(c, config, query) 148 | if err != nil { 149 | c.JSON(http.StatusBadRequest, fmt.Sprintf("Gremlin query error: %v", err)) 150 | return 151 | } 152 | 153 | combinedResult.Type = result.Type 154 | combinedResult.Value = append(combinedResult.Value, result.Value...) 155 | }(i) 156 | } 157 | 158 | wg.Wait() 159 | 160 | responseBytes, err := json.Marshal(combinedResult) 161 | if err != nil { 162 | c.JSON(http.StatusBadRequest, fmt.Sprintf("gremlin query parse error: %v", err)) 163 | return 164 | } 165 | c.Data(http.StatusOK, "application/json", responseBytes) 166 | } 167 | -------------------------------------------------------------------------------- /cmd/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httputil" 7 | "net/url" 8 | "uiserver/lib" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func noCacheMiddleware() gin.HandlerFunc { 15 | return func(c *gin.Context) { 16 | c.Header("Cache-Control", "no-cache, no-store, must-revalidate") 17 | c.Header("Pragma", "no-cache") 18 | c.Header("Expires", "0") 19 | c.Next() 20 | } 21 | } 22 | 23 | func main() { 24 | logrus.SetFormatter(&lib.PuppyLogFormatter{ModuleName: "UiServer"}) 25 | 26 | conf, err := lib.LoadConfig() 27 | if err != nil { 28 | logrus.Fatal("Cannot initialize the config. Exiting.") 29 | } 30 | if conf.Debug { 31 | logrus.SetLevel(logrus.DebugLevel) 32 | } 33 | 34 | r := gin.Default() 35 | 36 | // for large data size, should rely on client side to run small batch update 37 | r.MaxMultipartMemory = 8 << 30 // 8GB 38 | 39 | requestScopedMiddleware := func(c *gin.Context) { 40 | c.Set("conf", conf) 41 | c.Next() 42 | } 43 | r.Use(requestScopedMiddleware) 44 | 45 | jwtMiddleware := lib.InitJwtMiddleware(conf.Authentication.FrontendJWT.SecretKey, conf.Authentication.FrontendJWT.Timeout) 46 | 47 | r.POST("/login", jwtMiddleware.LoginHandler) 48 | r.POST("/logout", jwtMiddleware.LogoutHandler) 49 | r.GET("/refresh_token", jwtMiddleware.RefreshHandler) 50 | 51 | auth := func(c *gin.Context) { 52 | jwtMiddleware.MiddlewareFunc()(c) 53 | } 54 | 55 | // gremlin reverse proxy 56 | r.Any("/gremlin", func(c *gin.Context) { 57 | logrus.Debugf("gremlin: %+v", c.Request) 58 | v, exists := c.Get("conf") 59 | if !exists { 60 | c.JSON(http.StatusInternalServerError, "Cannot load config.") 61 | return 62 | } 63 | conf := v.(*lib.Config) 64 | origin := "http://" + conf.GremlinServer.Host 65 | remote, err := url.Parse(origin) 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | proxy := httputil.NewSingleHostReverseProxy(remote) 71 | proxy.Director = func(req *http.Request) { 72 | req.Header = c.Request.Header 73 | req.Header.Set("Origin", origin) 74 | req.Host = remote.Host 75 | req.URL.Scheme = remote.Scheme 76 | req.URL.Host = remote.Host 77 | req.URL.Path = conf.GremlinServer.Path 78 | } 79 | 80 | proxy.ServeHTTP(c.Writer, c.Request) 81 | }) 82 | 83 | // html 84 | r.Use(noCacheMiddleware()) 85 | r.StaticFile("/", "./html/build/index.html") 86 | r.StaticFile("/index.html", "./html/build/index.html") 87 | r.StaticFile("/logo.png", "./html/build/logo.png") 88 | r.StaticFile("/manifest.json", "./html/build/manifest.json") 89 | r.StaticFile("/robots.txt", "./html/build/robots.txt") 90 | r.Static("/static", "./html/build/static") 91 | 92 | r.GET("/status", auth, statusHandler) 93 | r.POST("/submit", auth, submitHandler) 94 | r.POST("/ui-api/props", auth, getPropsHandler) 95 | 96 | r.Run(fmt.Sprintf(":%d", conf.Port)) 97 | } 98 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /opt/puppygraph 4 | bin/server 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module uiserver 2 | 3 | go 1.20 4 | 5 | replace github.com/apache/tinkerpop/gremlin-go => ./gremlin-go 6 | 7 | require ( 8 | github.com/apache/tinkerpop/gremlin-go v0.0.0-20220530191148-29272fa563ec 9 | github.com/appleboy/gin-jwt/v2 v2.9.1 10 | github.com/gin-gonic/gin v1.9.1 11 | github.com/kelseyhightower/envconfig v1.4.0 12 | github.com/sirupsen/logrus v1.9.3 13 | ) 14 | 15 | require ( 16 | github.com/apache/tinkerpop/gremlin-go/v3 v3.7.1 // indirect 17 | github.com/bytedance/sonic v1.9.1 // indirect 18 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 19 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 20 | github.com/gin-contrib/sse v0.1.0 // indirect 21 | github.com/go-playground/locales v0.14.1 // indirect 22 | github.com/go-playground/universal-translator v0.18.1 // indirect 23 | github.com/go-playground/validator/v10 v10.14.0 // indirect 24 | github.com/goccy/go-json v0.10.2 // indirect 25 | github.com/golang-jwt/jwt/v4 v4.4.3 // indirect 26 | github.com/google/uuid v1.6.0 // indirect 27 | github.com/gorilla/websocket v1.5.1 // indirect 28 | github.com/json-iterator/go v1.1.12 // indirect 29 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 30 | github.com/leodido/go-urn v1.2.4 // indirect 31 | github.com/mattn/go-isatty v0.0.19 // indirect 32 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 33 | github.com/modern-go/reflect2 v1.0.2 // indirect 34 | github.com/nicksnyder/go-i18n/v2 v2.3.0 // indirect 35 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 36 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 37 | github.com/ugorji/go/codec v1.2.11 // indirect 38 | golang.org/x/arch v0.3.0 // indirect 39 | golang.org/x/crypto v0.31.0 // indirect 40 | golang.org/x/net v0.21.0 // indirect 41 | golang.org/x/sys v0.28.0 // indirect 42 | golang.org/x/text v0.21.0 // indirect 43 | google.golang.org/protobuf v1.30.0 // indirect 44 | gopkg.in/yaml.v3 v3.0.1 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /gremlin-go/NOTICE: -------------------------------------------------------------------------------- 1 | Apache TinkerPop 2 | Copyright 2015-2023 The Apache Software Foundation. 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org/). -------------------------------------------------------------------------------- /gremlin-go/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | version: "3.8" 19 | 20 | services: 21 | 22 | gremlin-server-test: 23 | container_name: gremlin-server-test 24 | image: tinkerpop/gremlin-server-test:${GREMLIN_SERVER} 25 | build: 26 | context: ../ 27 | dockerfile: docker/gremlin-test-server/Dockerfile 28 | args: 29 | - GREMLIN_SERVER=${GREMLIN_SERVER} 30 | ports: 31 | - "45940:45940" 32 | - "45941:45941" 33 | - "45942:45942" 34 | - "4588:4588" 35 | volumes: 36 | - ${HOME}/.groovy:/root/.groovy 37 | - ${HOME}/.m2:/root/.m2 38 | - ${ABS_PROJECT_HOME}/gremlin-test/target:/opt/gremlin-test 39 | healthcheck: 40 | test: [ "CMD-SHELL", "apk add curl && curl -f http://localhost:45940?gremlin=100-1" ] 41 | interval: 30s 42 | timeout: 10s 43 | retries: 30 44 | start_period: 30s 45 | depends_on: 46 | - gremlin-socket-server 47 | 48 | gremlin-go-integration-tests: 49 | container_name: gremlin-go-integration-tests 50 | image: golang:1.20 51 | volumes: 52 | - .:/go_app 53 | - ../gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features:/gremlin-test 54 | - ../docker/gremlin-test-server:/go_app/gremlin-test-server 55 | - ../gremlin-tools/gremlin-socket-server/conf:/go_app/gremlin-socket-server/conf/ 56 | environment: 57 | - CUCUMBER_FEATURE_FOLDER=/gremlin-test 58 | - GREMLIN_SERVER_URL=ws://gremlin-server-test:45940/gremlin 59 | - GREMLIN_SERVER_BASIC_AUTH_URL=wss://gremlin-server-test:45941/gremlin 60 | - RUN_INTEGRATION_TESTS=true 61 | - RUN_INTEGRATION_WITH_ALIAS_TESTS=true 62 | - RUN_BASIC_AUTH_INTEGRATION_TESTS=true 63 | - GREMLIN_SOCKET_SERVER_URL=ws://gremlin-socket-server-go 64 | - GREMLIN_SOCKET_SERVER_CONFIG_PATH=/go_app/gremlin-socket-server/conf/test-ws-gremlin.yaml 65 | working_dir: /go_app 66 | command: > 67 | bash -c "go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest 68 | && go test -v -json ./... -race -covermode=atomic -coverprofile=\"coverage.out\" -coverpkg=./... | gotestfmt" 69 | depends_on: 70 | gremlin-server-test: 71 | condition: service_healthy 72 | 73 | gremlin-socket-server: 74 | container_name: gremlin-socket-server-go 75 | image: tinkerpop/gremlin-socket-server:${GREMLIN_SERVER} 76 | ports: 77 | - "45943:45943" 78 | -------------------------------------------------------------------------------- /gremlin-go/driver/authInfo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import "net/http" 23 | 24 | // AuthInfoProvider is an interface that allows authentication information to be specified. 25 | type AuthInfoProvider interface { 26 | GetHeader() http.Header 27 | GetBasicAuth() (ok bool, username, password string) 28 | } 29 | 30 | // AuthInfo is an option struct that allows authentication information to be specified statically. 31 | // Authentication can be provided via http.Header directly. 32 | // Basic authentication can also be used via the BasicAuthInfo function. 33 | type AuthInfo struct { 34 | Header http.Header 35 | Username string 36 | Password string 37 | } 38 | 39 | var _ AuthInfoProvider = (*AuthInfo)(nil) 40 | 41 | // GetHeader provides a safe way to get a header from the AuthInfo even if it is nil. 42 | // This way we don't need any additional logic in the transport layer. 43 | func (authInfo *AuthInfo) GetHeader() http.Header { 44 | if authInfo == nil { 45 | return nil 46 | } else { 47 | return authInfo.Header 48 | } 49 | } 50 | 51 | // GetBasicAuth provides a safe way to check if basic auth info is available from the AuthInfo even if it is nil. 52 | // This way we don't need any additional logic in the transport layer. 53 | func (authInfo *AuthInfo) GetBasicAuth() (bool, string, string) { 54 | if authInfo == nil || (authInfo.Username == "" && authInfo.Password == "") { 55 | return false, "", "" 56 | } 57 | return true, authInfo.Username, authInfo.Password 58 | } 59 | 60 | // BasicAuthInfo provides a way to generate AuthInfo. Enter username and password and get the AuthInfo back. 61 | func BasicAuthInfo(username string, password string) *AuthInfo { 62 | return &AuthInfo{Username: username, Password: password} 63 | } 64 | 65 | // HeaderAuthInfo provides a way to generate AuthInfo with only Header information. 66 | func HeaderAuthInfo(header http.Header) *AuthInfo { 67 | return &AuthInfo{Header: header} 68 | } 69 | 70 | // DynamicAuth is an AuthInfoProvider that allows dynamic credential generation. 71 | type DynamicAuth struct { 72 | fn func() AuthInfoProvider 73 | } 74 | 75 | var ( 76 | _ AuthInfoProvider = (*DynamicAuth)(nil) 77 | 78 | // NoopAuthInfo is a no-op AuthInfoProvider that can be used to disable authentication. 79 | NoopAuthInfo = NewDynamicAuth(func() AuthInfoProvider { return &AuthInfo{} }) 80 | ) 81 | 82 | // NewDynamicAuth provides a way to generate dynamic credentials with the specified generator function. 83 | func NewDynamicAuth(f func() AuthInfoProvider) *DynamicAuth { 84 | return &DynamicAuth{fn: f} 85 | } 86 | 87 | // GetHeader calls the stored function to get the header dynamically. 88 | func (d *DynamicAuth) GetHeader() http.Header { 89 | return d.fn().GetHeader() 90 | } 91 | 92 | // GetHeader calls the stored function to get basic authentication dynamically. 93 | func (d *DynamicAuth) GetBasicAuth() (bool, string, string) { 94 | return d.fn().GetBasicAuth() 95 | } 96 | -------------------------------------------------------------------------------- /gremlin-go/driver/bytecode.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "fmt" 24 | "reflect" 25 | ) 26 | 27 | // Bytecode a list of ordered instructions for traversal that can be serialized between environments and machines. 28 | type Bytecode struct { 29 | sourceInstructions []instruction 30 | stepInstructions []instruction 31 | bindings map[string]interface{} 32 | } 33 | 34 | // NewBytecode creates a new Bytecode to be used in traversals. 35 | func NewBytecode(bc *Bytecode) *Bytecode { 36 | sourceInstructions := make([]instruction, 0) 37 | stepInstructions := make([]instruction, 0) 38 | bindingMap := make(map[string]interface{}) 39 | if bc != nil { 40 | sourceInstructions = append(sourceInstructions, bc.sourceInstructions...) 41 | stepInstructions = append(stepInstructions, bc.stepInstructions...) 42 | } 43 | 44 | return &Bytecode{ 45 | sourceInstructions: sourceInstructions, 46 | stepInstructions: stepInstructions, 47 | bindings: bindingMap, 48 | } 49 | } 50 | 51 | func (bytecode *Bytecode) createInstruction(operator string, args ...interface{}) (*instruction, error) { 52 | instruction := &instruction{ 53 | operator: operator, 54 | arguments: make([]interface{}, 0), 55 | } 56 | 57 | for _, arg := range args { 58 | converted, err := bytecode.convertArgument(arg) 59 | if err != nil { 60 | return nil, err 61 | } 62 | instruction.arguments = append(instruction.arguments, converted) 63 | } 64 | 65 | return instruction, nil 66 | } 67 | 68 | // AddSource add a traversal source instruction to the bytecode. 69 | func (bytecode *Bytecode) AddSource(sourceName string, args ...interface{}) error { 70 | instruction, err := bytecode.createInstruction(sourceName, args...) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | bytecode.sourceInstructions = append(bytecode.sourceInstructions, *instruction) 76 | return err 77 | } 78 | 79 | // AddStep adds a traversal instruction to the bytecode 80 | func (bytecode *Bytecode) AddStep(stepName string, args ...interface{}) error { 81 | instruction, err := bytecode.createInstruction(stepName, args...) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | bytecode.stepInstructions = append(bytecode.stepInstructions, *instruction) 87 | return err 88 | } 89 | 90 | func (bytecode *Bytecode) convertArgument(arg interface{}) (interface{}, error) { 91 | if arg == nil { 92 | return nil, nil 93 | } 94 | t := reflect.TypeOf(arg) 95 | if t == nil { 96 | return nil, nil 97 | } 98 | switch t.Kind() { 99 | case reflect.Map: 100 | newMap := make(map[interface{}]interface{}) 101 | iter := reflect.ValueOf(arg).MapRange() 102 | for iter.Next() { 103 | k := iter.Key().Interface() 104 | v := iter.Value().Interface() 105 | convertedKey, err := bytecode.convertArgument(k) 106 | if err != nil { 107 | return nil, err 108 | } 109 | convertedValue, err := bytecode.convertArgument(v) 110 | if err != nil { 111 | return nil, err 112 | } 113 | newMap[convertedKey] = convertedValue 114 | } 115 | return newMap, nil 116 | case reflect.Slice: 117 | newSlice := make([]interface{}, 0) 118 | oldSlice := reflect.ValueOf(arg) 119 | for i := 0; i < oldSlice.Len(); i++ { 120 | converted, err := bytecode.convertArgument(oldSlice.Index(i).Interface()) 121 | if err != nil { 122 | return nil, err 123 | } 124 | newSlice = append(newSlice, converted) 125 | } 126 | return newSlice, nil 127 | default: 128 | switch v := arg.(type) { 129 | case *Binding: 130 | convertedValue, err := bytecode.convertArgument(v.Value) 131 | if err != nil { 132 | return nil, err 133 | } 134 | bytecode.bindings[v.Key] = v.Value 135 | return &Binding{ 136 | Key: v.Key, 137 | Value: convertedValue, 138 | }, nil 139 | case *GraphTraversal: 140 | if v.graph != nil { 141 | return nil, newError(err1001ConvertArgumentChildTraversalNotFromAnonError) 142 | } 143 | for k, val := range v.Bytecode.bindings { 144 | bytecode.bindings[k] = val 145 | } 146 | return v.Bytecode, nil 147 | default: 148 | return arg, nil 149 | } 150 | } 151 | 152 | } 153 | 154 | type instruction struct { 155 | operator string 156 | arguments []interface{} 157 | } 158 | 159 | // Binding associates a string variable with a value 160 | type Binding struct { 161 | Key string 162 | Value interface{} 163 | } 164 | 165 | // String returns the key value binding in string format 166 | func (b *Binding) String() string { 167 | return fmt.Sprintf("binding[%v=%v]", b.Key, b.Value) 168 | } 169 | 170 | // Bindings are used to associate a variable with a value. They enable the creation of Binding, usually used with 171 | // Lambda scripts to avoid continued recompilation costs. Bindings allow a remote engine to cache traversals that 172 | // will be reused over and over again save that some parameterization may change. 173 | // Used as g.V().Out(&Bindings{}.Of("key", value)) 174 | type Bindings struct{} 175 | 176 | // Of creates a Binding 177 | func (*Bindings) Of(key string, value interface{}) *Binding { 178 | return &Binding{ 179 | Key: key, 180 | Value: value, 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /gremlin-go/driver/bytecode_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "testing" 24 | 25 | "github.com/stretchr/testify/assert" 26 | ) 27 | 28 | func TestBytecode(t *testing.T) { 29 | t.Run("Constructor", func(t *testing.T) { 30 | bc1 := NewBytecode(nil) 31 | assert.NotNil(t, bc1.bindings) 32 | assert.NotNil(t, bc1.sourceInstructions) 33 | assert.NotNil(t, bc1.stepInstructions) 34 | assert.Empty(t, bc1.bindings) 35 | assert.Empty(t, bc1.sourceInstructions) 36 | assert.Empty(t, bc1.stepInstructions) 37 | 38 | sourceInstructions := []instruction{{ 39 | operator: "mockSource", 40 | arguments: nil, 41 | }} 42 | stepInstructions := []instruction{{ 43 | operator: "mockStep", 44 | arguments: nil, 45 | }} 46 | bindingMap := make(map[string]interface{}) 47 | bindingMap["mock"] = 123 48 | bc1.sourceInstructions = sourceInstructions 49 | bc1.stepInstructions = stepInstructions 50 | bc1.bindings = bindingMap 51 | 52 | bc2 := NewBytecode(bc1) 53 | assert.NotNil(t, bc2.bindings) 54 | assert.NotNil(t, bc2.sourceInstructions) 55 | assert.NotNil(t, bc2.stepInstructions) 56 | assert.Empty(t, bc2.bindings) 57 | assert.Equal(t, sourceInstructions, bc2.sourceInstructions) 58 | assert.Equal(t, stepInstructions, bc2.stepInstructions) 59 | }) 60 | 61 | t.Run("AddSource", func(t *testing.T) { 62 | expectedSourceInstructions := []instruction{{ 63 | operator: "mockSource", 64 | arguments: []interface{}{123}, 65 | }} 66 | bc := NewBytecode(nil) 67 | err := bc.AddSource("mockSource", 123) 68 | assert.Nil(t, err) 69 | assert.Equal(t, expectedSourceInstructions, bc.sourceInstructions) 70 | }) 71 | 72 | t.Run("addStep", func(t *testing.T) { 73 | expectedStepInstructions := []instruction{{ 74 | operator: "mockStep", 75 | arguments: []interface{}{123}, 76 | }} 77 | bc := NewBytecode(nil) 78 | err := bc.AddStep("mockStep", 123) 79 | assert.Nil(t, err) 80 | assert.Equal(t, expectedStepInstructions, bc.stepInstructions) 81 | }) 82 | 83 | t.Run("convertArgument", func(t *testing.T) { 84 | bc := NewBytecode(nil) 85 | 86 | t.Run("map", func(t *testing.T) { 87 | testMap := make(map[string]int) 88 | testMap["test"] = 123 89 | converted, err := bc.convertArgument(testMap) 90 | assert.Nil(t, err) 91 | for k, v := range converted.(map[interface{}]interface{}) { 92 | key := k.(string) 93 | value := v.(int) 94 | assert.Equal(t, "test", key) 95 | assert.Equal(t, 123, value) 96 | } 97 | }) 98 | 99 | t.Run("slice", func(t *testing.T) { 100 | testSlice := []int{1, 2, 3} 101 | converted, err := bc.convertArgument(testSlice) 102 | assert.Nil(t, err) 103 | for i, value := range converted.([]interface{}) { 104 | assert.Equal(t, testSlice[i], value) 105 | } 106 | }) 107 | 108 | t.Run("binding", func(t *testing.T) { 109 | testKey := "testKey" 110 | testValue := "testValue" 111 | testBinding := &Binding{ 112 | Key: testKey, 113 | Value: testValue, 114 | } 115 | converted, err := bc.convertArgument(testBinding) 116 | assert.Nil(t, err) 117 | assert.Equal(t, testBinding, converted) 118 | assert.Equal(t, testValue, bc.bindings[testKey]) 119 | }) 120 | }) 121 | 122 | t.Run("Test Bytecode traversal argument conversion without Graph", func(t *testing.T) { 123 | bc := Bytecode{} 124 | traversal := &GraphTraversal{ 125 | &Traversal{}, 126 | } 127 | traversal.graph = nil 128 | traversal.Bytecode = &Bytecode{} 129 | traversalBytecode, err := bc.convertArgument(traversal) 130 | assert.Nil(t, err) 131 | assert.Equal(t, traversal.Bytecode, traversalBytecode) 132 | }) 133 | 134 | t.Run("Test Bytecode traversal argument conversion with Graph", func(t *testing.T) { 135 | // This should fail. 136 | bc := Bytecode{} 137 | traversal := &GraphTraversal{ 138 | &Traversal{}, 139 | } 140 | traversal.graph = &Graph{} 141 | traversal.Bytecode = &Bytecode{} 142 | traversalBytecode, err := bc.convertArgument(traversal) 143 | assert.Equal(t, newError(err1001ConvertArgumentChildTraversalNotFromAnonError), err) 144 | assert.Nil(t, traversalBytecode) 145 | }) 146 | 147 | t.Run("Test Bytecode traversal argument multiple bindings", func(t *testing.T) { 148 | bc := Bytecode{} 149 | bc.bindings = map[string]interface{}{} 150 | testTraversal := &GraphTraversal{ 151 | &Traversal{}, 152 | } 153 | testTraversal.Bytecode = &Bytecode{} 154 | testTraversal.Bytecode.bindings = map[string]interface{}{"mock": "123"} 155 | traversalBytecode, err := bc.convertArgument(testTraversal) 156 | assert.Nil(t, err) 157 | assert.Equal(t, testTraversal.Bytecode, traversalBytecode) 158 | }) 159 | } 160 | -------------------------------------------------------------------------------- /gremlin-go/driver/connection.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "crypto/tls" 24 | "sync" 25 | "time" 26 | ) 27 | 28 | type connectionState int 29 | 30 | const ( 31 | initialized connectionState = iota + 1 32 | established 33 | closed 34 | closedDueToError 35 | ) 36 | 37 | type connection struct { 38 | logHandler *logHandler 39 | protocol protocol 40 | results *synchronizedMap 41 | state connectionState 42 | } 43 | 44 | type connectionSettings struct { 45 | authInfo AuthInfoProvider 46 | tlsConfig *tls.Config 47 | keepAliveInterval time.Duration 48 | writeDeadline time.Duration 49 | connectionTimeout time.Duration 50 | enableCompression bool 51 | readBufferSize int 52 | writeBufferSize int 53 | enableUserAgentOnConnect bool 54 | serializerType SerializerType 55 | } 56 | 57 | func (connection *connection) errorCallback() { 58 | connection.logHandler.log(Error, errorCallback) 59 | connection.state = closedDueToError 60 | 61 | // This callback is called from within protocol.readLoop. Therefore, 62 | // it cannot wait for it to finish to avoid a deadlock. 63 | if err := connection.protocol.close(false); err != nil { 64 | connection.logHandler.logf(Error, failedToCloseInErrorCallback, err.Error()) 65 | } 66 | } 67 | 68 | func (connection *connection) close() error { 69 | if connection.state != established { 70 | return newError(err0101ConnectionCloseError) 71 | } 72 | connection.logHandler.log(Info, closeConnection) 73 | var err error 74 | if connection.protocol != nil { 75 | err = connection.protocol.close(true) 76 | } 77 | connection.state = closed 78 | return err 79 | } 80 | 81 | func (connection *connection) write(request *request) (ResultSet, error) { 82 | if connection.state != established { 83 | return nil, newError(err0102WriteConnectionClosedError) 84 | } 85 | connection.logHandler.log(Debug, writeRequest) 86 | requestID := request.requestID.String() 87 | connection.logHandler.logf(Debug, creatingRequest, requestID) 88 | resultSet := newChannelResultSet(requestID, connection.results) 89 | connection.results.store(requestID, resultSet) 90 | return resultSet, connection.protocol.write(request) 91 | } 92 | 93 | func (connection *connection) activeResults() int { 94 | return connection.results.size() 95 | } 96 | 97 | // createConnection establishes a connection with the given parameters. A connection should always be closed to avoid 98 | // leaking connections. The connection has the following states: 99 | // 100 | // initialized: connection struct is created but has not established communication with server 101 | // established: connection has established communication established with the server 102 | // closed: connection was closed by the user. 103 | // closedDueToError: connection was closed internally due to an error. 104 | func createConnection(url string, logHandler *logHandler, connSettings *connectionSettings) (*connection, error) { 105 | conn := &connection{ 106 | logHandler, 107 | nil, 108 | &synchronizedMap{map[string]ResultSet{}, sync.Mutex{}}, 109 | initialized, 110 | } 111 | logHandler.log(Info, connectConnection) 112 | protocol, err := newGremlinServerWSProtocol(logHandler, Gorilla, url, connSettings, conn.results, conn.errorCallback) 113 | if err != nil { 114 | logHandler.logf(Warning, failedConnection) 115 | conn.state = closedDueToError 116 | return nil, err 117 | } 118 | conn.protocol = protocol 119 | conn.state = established 120 | return conn, err 121 | } 122 | 123 | type synchronizedMap struct { 124 | internalMap map[string]ResultSet 125 | syncLock sync.Mutex 126 | } 127 | 128 | func (s *synchronizedMap) store(key string, value ResultSet) { 129 | s.syncLock.Lock() 130 | defer s.syncLock.Unlock() 131 | s.internalMap[key] = value 132 | } 133 | 134 | func (s *synchronizedMap) load(key string) ResultSet { 135 | s.syncLock.Lock() 136 | defer s.syncLock.Unlock() 137 | return s.internalMap[key] 138 | } 139 | 140 | func (s *synchronizedMap) delete(key string) { 141 | s.syncLock.Lock() 142 | defer s.syncLock.Unlock() 143 | delete(s.internalMap, key) 144 | } 145 | 146 | func (s *synchronizedMap) size() int { 147 | s.syncLock.Lock() 148 | defer s.syncLock.Unlock() 149 | return len(s.internalMap) 150 | } 151 | 152 | func (s *synchronizedMap) closeAll(err error) { 153 | s.syncLock.Lock() 154 | defer s.syncLock.Unlock() 155 | for _, resultSet := range s.internalMap { 156 | resultSet.setError(err) 157 | resultSet.unlockedClose() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /gremlin-go/driver/connectionPool_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "github.com/stretchr/testify/assert" 24 | "golang.org/x/text/language" 25 | "strconv" 26 | "sync" 27 | "testing" 28 | ) 29 | 30 | // Arbitrarily high value to use to not trigger creation of new connections 31 | const newConnectionThreshold = 100 32 | 33 | var logger = newLogHandler(&defaultLogger{}, Info, language.English) 34 | 35 | func getPoolForTesting() *loadBalancingPool { 36 | return &loadBalancingPool{ 37 | url: "", 38 | connSettings: newDefaultConnectionSettings(), 39 | logHandler: newLogHandler(&defaultLogger{}, Info, language.English), 40 | newConnectionThreshold: newConnectionThreshold, 41 | connections: nil, 42 | loadBalanceLock: sync.Mutex{}, 43 | } 44 | } 45 | 46 | func getMockConnection() *connection { 47 | return &connection{ 48 | logHandler: logger, 49 | protocol: nil, 50 | results: &synchronizedMap{ 51 | internalMap: make(map[string]ResultSet), 52 | syncLock: sync.Mutex{}, 53 | }, 54 | state: established, 55 | } 56 | } 57 | 58 | func TestConnectionPool(t *testing.T) { 59 | t.Run("loadBalancingPool", func(t *testing.T) { 60 | smallMap := make(map[string]ResultSet) 61 | bigMap := make(map[string]ResultSet) 62 | for i := 1; i < 4; i++ { 63 | bigMap[strconv.Itoa(i)] = nil 64 | if i < 3 { 65 | smallMap[strconv.Itoa(i)] = nil 66 | } 67 | } 68 | 69 | t.Run("getLeastUsedConnection", func(t *testing.T) { 70 | t.Run("getting the least used connection", func(t *testing.T) { 71 | pool := getPoolForTesting() 72 | defer pool.close() 73 | mockConnection1 := getMockConnection() 74 | mockConnection2 := getMockConnection() 75 | mockConnection3 := getMockConnection() 76 | mockConnection1.results.internalMap = bigMap 77 | mockConnection2.results.internalMap = smallMap 78 | mockConnection3.results.internalMap = bigMap 79 | connections := []*connection{mockConnection1, mockConnection2, mockConnection3} 80 | pool.connections = connections 81 | 82 | connection, err := pool.getLeastUsedConnection() 83 | assert.Nil(t, err) 84 | assert.Equal(t, mockConnection2, connection) 85 | }) 86 | 87 | t.Run("purge non-established connections", func(t *testing.T) { 88 | pool := getPoolForTesting() 89 | defer pool.close() 90 | mockConnection := getMockConnection() 91 | mockConnection.results.internalMap = smallMap 92 | nonEstablished := &connection{ 93 | logHandler: logger, 94 | protocol: nil, 95 | results: nil, 96 | state: closed, 97 | } 98 | connections := []*connection{nonEstablished, mockConnection} 99 | pool.connections = connections 100 | 101 | connection, err := pool.getLeastUsedConnection() 102 | assert.Nil(t, err) 103 | assert.Equal(t, mockConnection, connection) 104 | assert.Len(t, pool.connections, 1) 105 | }) 106 | }) 107 | 108 | t.Run("close", func(t *testing.T) { 109 | pool := getPoolForTesting() 110 | empty := &synchronizedMap{ 111 | internalMap: make(map[string]ResultSet), 112 | syncLock: sync.Mutex{}, 113 | } 114 | openConn1 := &connection{ 115 | logHandler: logger, 116 | protocol: nil, 117 | results: empty, 118 | state: established, 119 | } 120 | openConn2 := &connection{ 121 | logHandler: logger, 122 | protocol: nil, 123 | results: empty, 124 | state: established, 125 | } 126 | connections := []*connection{openConn1, openConn2} 127 | pool.connections = connections 128 | 129 | pool.close() 130 | assert.Equal(t, closed, openConn1.state) 131 | assert.Equal(t, closed, openConn2.state) 132 | }) 133 | }) 134 | } 135 | -------------------------------------------------------------------------------- /gremlin-go/driver/driverRemoteConnection_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "net/http" 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestAuthentication(t *testing.T) { 30 | 31 | t.Run("Test BasicAuthInfo.", func(t *testing.T) { 32 | header := BasicAuthInfo("Lyndon", "Bauto") 33 | assert.Nil(t, header.GetHeader()) 34 | b, _, _ := header.GetBasicAuth() 35 | assert.True(t, b) 36 | }) 37 | 38 | t.Run("Test GetHeader.", func(t *testing.T) { 39 | header := &AuthInfo{} 40 | assert.Nil(t, header.GetHeader()) 41 | header = nil 42 | assert.Nil(t, header.GetHeader()) 43 | httpHeader := http.Header{} 44 | header = &AuthInfo{Header: httpHeader} 45 | assert.Equal(t, httpHeader, header.GetHeader()) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /gremlin-go/driver/gorillaTransporter_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "errors" 24 | "sync" 25 | "testing" 26 | "time" 27 | 28 | "github.com/stretchr/testify/assert" 29 | "github.com/stretchr/testify/mock" 30 | "golang.org/x/text/language" 31 | ) 32 | 33 | const mockMessage string = "MockMessage" 34 | const mockReadErrMessage string = "MockReadMessageErrMessage" 35 | 36 | type mockWebsocketConn struct { 37 | mock.Mock 38 | } 39 | 40 | func (conn *mockWebsocketConn) WriteMessage(messageType int, data []byte) error { 41 | args := conn.Called(messageType, data) 42 | return args.Error(0) 43 | } 44 | 45 | func (conn *mockWebsocketConn) ReadMessage() (int, []byte, error) { 46 | args := conn.Called() 47 | return args.Get(0).(int), args.Get(1).([]byte), args.Error(2) 48 | } 49 | 50 | func (conn *mockWebsocketConn) Close() error { 51 | args := conn.Called() 52 | return args.Error(0) 53 | } 54 | 55 | func (conn *mockWebsocketConn) SetReadDeadline(time time.Time) error { 56 | args := conn.Called(time) 57 | return args.Error(0) 58 | } 59 | 60 | func (conn *mockWebsocketConn) SetWriteDeadline(time time.Time) error { 61 | args := conn.Called(time) 62 | return args.Error(0) 63 | } 64 | 65 | func (conn *mockWebsocketConn) SetPongHandler(h func(appData string) error) { 66 | conn.Called(h) 67 | } 68 | 69 | func getNewGorillaTransporter() (gorillaTransporter, *mockWebsocketConn) { 70 | mockConn := new(mockWebsocketConn) 71 | return gorillaTransporter{ 72 | url: "ws://mockHost:8182/gremlin", 73 | logHandler: newLogHandler(&defaultLogger{}, Info, language.English), 74 | connection: mockConn, 75 | isClosed: false, 76 | connSettings: newDefaultConnectionSettings(), 77 | writeChannel: make(chan []byte, 100), 78 | wg: &sync.WaitGroup{}, 79 | }, mockConn 80 | } 81 | 82 | func TestGorillaTransporter(t *testing.T) { 83 | t.Run("Success", func(t *testing.T) { 84 | transporter, mockConn := getNewGorillaTransporter() 85 | t.Run("WriteMessage", func(t *testing.T) { 86 | mockConn.On("WriteMessage", 2, make([]byte, 10)).Return(nil) 87 | err := transporter.Write(make([]byte, 10)) 88 | assert.Nil(t, err) 89 | }) 90 | 91 | t.Run("Read", func(t *testing.T) { 92 | mockConn.On("ReadMessage").Return(0, []byte(mockMessage), nil) 93 | mockConn.On("SetPongHandler", mock.AnythingOfType("func(string) error")).Return(nil) 94 | mockConn.On("SetReadDeadline", mock.Anything).Return(nil) 95 | mockConn.On("SetWriteDeadline", mock.Anything).Return(nil) 96 | message, err := transporter.Read() 97 | assert.Nil(t, err) 98 | assert.Equal(t, mockMessage, string(message[:])) 99 | }) 100 | 101 | t.Run("Close and IsClosed", func(t *testing.T) { 102 | mockConn.On("Close").Return(nil) 103 | isClosed := transporter.IsClosed() 104 | assert.False(t, isClosed) 105 | err := transporter.Close() 106 | assert.Nil(t, err) 107 | isClosed = transporter.IsClosed() 108 | assert.True(t, isClosed) 109 | }) 110 | }) 111 | 112 | t.Run("Error", func(t *testing.T) { 113 | transporter, mockConn := getNewGorillaTransporter() 114 | t.Run("Read", func(t *testing.T) { 115 | mockConn.On("ReadMessage").Return(0, []byte{}, errors.New(mockReadErrMessage)) 116 | mockConn.On("SetPongHandler", mock.AnythingOfType("func(string) error")).Return(nil) 117 | mockConn.On("SetReadDeadline", mock.Anything).Return(nil) 118 | mockConn.On("SetWriteDeadline", mock.Anything).Return(nil) 119 | mockConn.On("WriteMessage", mock.Anything, mock.Anything).Return(nil) 120 | _, err := transporter.Read() 121 | assert.NotNil(t, err) 122 | assert.Equal(t, mockReadErrMessage, err.Error()) 123 | }) 124 | 125 | t.Run("Close and IsClosed", func(t *testing.T) { 126 | mockConn.On("Close").Return(nil) 127 | isClosed := transporter.IsClosed() 128 | assert.False(t, isClosed) 129 | err := transporter.Close() 130 | assert.Nil(t, err) 131 | isClosed = transporter.IsClosed() 132 | assert.True(t, isClosed) 133 | }) 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /gremlin-go/driver/graphTraversalSource_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "github.com/stretchr/testify/assert" 24 | "testing" 25 | ) 26 | 27 | func TestGraphTraversalSource(t *testing.T) { 28 | 29 | t.Run("GraphTraversalSource.With tests", func(t *testing.T) { 30 | t.Run("Test for single property", func(t *testing.T) { 31 | g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil} 32 | traversal := g.With("foo", "bar") 33 | assert.NotNil(t, traversal) 34 | assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions)) 35 | instruction := traversal.bytecode.sourceInstructions[0] 36 | assert.Equal(t, "withStrategies", instruction.operator) 37 | assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy", 38 | instruction.arguments[0].(*traversalStrategy).name) 39 | config := instruction.arguments[0].(*traversalStrategy).configuration 40 | assert.Equal(t, map[string]interface{}{"foo": "bar"}, config) 41 | }) 42 | 43 | t.Run("Test for multiple property", func(t *testing.T) { 44 | g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil} 45 | traversal := g.With("foo", "bar").With("foo2", "bar2") 46 | assert.NotNil(t, traversal) 47 | assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions)) 48 | instruction := traversal.bytecode.sourceInstructions[0] 49 | assert.Equal(t, "withStrategies", instruction.operator) 50 | assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy", 51 | instruction.arguments[0].(*traversalStrategy).name) 52 | config := instruction.arguments[0].(*traversalStrategy).configuration 53 | assert.Equal(t, map[string]interface{}{"foo": "bar", "foo2": "bar2"}, config) 54 | }) 55 | 56 | t.Run("Test for property replacement", func(t *testing.T) { 57 | g := &GraphTraversalSource{graph: &Graph{}, bytecode: NewBytecode(nil), remoteConnection: nil} 58 | traversal := g.With("foo", "bar").With("foo", "not bar") 59 | assert.NotNil(t, traversal) 60 | assert.Equal(t, 1, len(traversal.bytecode.sourceInstructions)) 61 | instruction := traversal.bytecode.sourceInstructions[0] 62 | assert.Equal(t, "withStrategies", instruction.operator) 63 | assert.Equal(t, "org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy", 64 | instruction.arguments[0].(*traversalStrategy).name) 65 | config := instruction.arguments[0].(*traversalStrategy).configuration 66 | assert.Equal(t, map[string]interface{}{"foo": "not bar"}, config) 67 | }) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /gremlin-go/driver/graphsonSerializer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "encoding/json" 24 | 25 | "github.com/google/uuid" 26 | ) 27 | 28 | const graphsonMimeType = "application/vnd.gremlin-v3.0+json" 29 | 30 | // graphBinarySerializer serializes/deserializes message to/from GraphBinary. 31 | type graphsonSerializer struct { 32 | logHandler *logHandler 33 | } 34 | 35 | func newGraphsonSerializer(handler *logHandler) Serializer { 36 | return graphsonSerializer{ 37 | logHandler: handler, 38 | } 39 | } 40 | 41 | // serializeMessage serializes a request message into GraphBinary. 42 | func (gs graphsonSerializer) serializeMessage(request *request) ([]byte, error) { 43 | finalMessage, err := gs.buildMessage(request.requestID, byte(len(graphsonMimeType)), request.op, request.processor, request.args) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return finalMessage, nil 48 | } 49 | 50 | type Message struct { 51 | Id uuid.UUID `json:"requestId"` 52 | Op string `json:"op"` 53 | Processor string `json:"processor"` 54 | Args map[string]interface{} `json:"args"` 55 | } 56 | 57 | func (gs *graphsonSerializer) buildMessage(id uuid.UUID, mimeLen byte, op string, processor string, args map[string]interface{}) ([]byte, error) { 58 | message := Message{ 59 | Id: id, 60 | Op: op, 61 | Processor: processor, 62 | Args: args, 63 | } 64 | result := []byte{mimeLen} 65 | result = append(result, []byte(graphsonMimeType)...) 66 | jsonData, err := json.Marshal(message) 67 | if err != nil { 68 | return jsonData, err 69 | } 70 | result = append(result, jsonData...) 71 | return result, nil 72 | } 73 | 74 | // deserializeMessage deserializes a response message. 75 | 76 | type Response struct { 77 | RequestId string `json:"requestId"` 78 | Status ResponseStatus `json:"status"` 79 | Result ResponseResult `json:"result"` 80 | } 81 | 82 | type ResponseStatus struct { 83 | Code uint16 `json:"code"` 84 | Message string `json:"message"` 85 | Attributes map[string]interface{} `json:"attributes"` 86 | } 87 | 88 | type ResponseResult struct { 89 | Meta map[string]interface{} `json:"meta"` 90 | Data json.RawMessage `json:"data"` 91 | } 92 | 93 | func (gs graphsonSerializer) deserializeMessage(message []byte) (response, error) { 94 | var msg response 95 | var resp Response 96 | err := json.Unmarshal(message, &resp) 97 | if err != nil { 98 | return msg, err 99 | } 100 | msg.responseID = uuid.MustParse(resp.RequestId) 101 | msg.responseStatus.code = resp.Status.Code 102 | msg.responseStatus.message = resp.Status.Message 103 | msg.responseStatus.attributes = resp.Status.Attributes 104 | msg.responseResult.meta = resp.Result.Meta 105 | msg.responseResult.data = string(resp.Result.Data) 106 | 107 | return msg, nil 108 | } 109 | -------------------------------------------------------------------------------- /gremlin-go/driver/logger.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "encoding/json" 24 | "log" 25 | 26 | "github.com/apache/tinkerpop/gremlin-go/v3/driver/resources" 27 | "github.com/nicksnyder/go-i18n/v2/i18n" 28 | "golang.org/x/text/language" 29 | ) 30 | 31 | // LogVerbosity is an alias for valid logging verbosity levels. 32 | type LogVerbosity int 33 | 34 | const ( 35 | // Debug verbosity will log everything, including fine details. 36 | Debug LogVerbosity = iota + 1 37 | // Info verbosity will log messages up to standard procedure flow. 38 | Info 39 | // Warning verbosity will log messages up to warnings. 40 | Warning 41 | // Error verbosity level log only error messages. 42 | Error 43 | // Off verbosity level disables logging. 44 | Off 45 | ) 46 | 47 | // Logger is the interface required to be implemented for use with gremlingo. 48 | type Logger interface { 49 | Log(verbosity LogVerbosity, v ...interface{}) 50 | Logf(verbosity LogVerbosity, format string, v ...interface{}) 51 | } 52 | 53 | type defaultLogger struct { 54 | } 55 | 56 | // Log writes a message to the defaultLogger. 57 | func (logger *defaultLogger) Log(_ LogVerbosity, v ...interface{}) { 58 | log.Print(v...) 59 | } 60 | 61 | // Logf writes a formatted message to the defaultLogger. 62 | func (logger *defaultLogger) Logf(_ LogVerbosity, format string, v ...interface{}) { 63 | log.Printf(format, v...) 64 | } 65 | 66 | type logHandler struct { 67 | logger Logger 68 | verbosity LogVerbosity 69 | localizer *i18n.Localizer 70 | } 71 | 72 | func newLogHandler(logger Logger, verbosity LogVerbosity, locale language.Tag) *logHandler { 73 | bundle := i18n.NewBundle(language.English) 74 | bundle.RegisterUnmarshalFunc("json", json.Unmarshal) 75 | 76 | // Register resource package here for additional languages. 77 | langFile := "logger-messages/en.json" 78 | bundle.LoadMessageFileFS(resources.LoggerMessagesFS, langFile) 79 | 80 | localizer := i18n.NewLocalizer(bundle, locale.String()) 81 | return &logHandler{logger, verbosity, localizer} 82 | } 83 | 84 | func (logHandler *logHandler) log(verbosity LogVerbosity, errorKey errorKey) { 85 | logHandler.logf(verbosity, errorKey) 86 | } 87 | 88 | func (logHandler *logHandler) logf(verbosity LogVerbosity, errorKey errorKey, v ...interface{}) { 89 | if verbosity >= logHandler.verbosity { 90 | config := i18n.LocalizeConfig{ 91 | MessageID: string(errorKey), 92 | } 93 | localizedMessage, _ := logHandler.localizer.Localize(&config) 94 | logHandler.logger.Logf(verbosity, localizedMessage, v...) 95 | } 96 | } 97 | 98 | type errorKey string 99 | 100 | const ( 101 | serializeDataTypeError errorKey = "UNKNOWN_SER_DATATYPE" 102 | deserializeDataTypeError errorKey = "UNKNOWN_DESER_DATATYPE" 103 | nullInput errorKey = "NULL_INPUT" 104 | unexpectedNull errorKey = "UNEXPECTED_NULL_VALUE" 105 | closeConnection errorKey = "CLOSING_CONNECTION" 106 | connectConnection errorKey = "OPENING_CONNECTION" 107 | failedConnection errorKey = "FAILED_CONNECTION" 108 | writeRequest errorKey = "WRITE_REQUEST" 109 | readLoopError errorKey = "READ_LOOP_ERROR" 110 | errorCallback errorKey = "ERROR_CALLBACK" 111 | creatingRequest errorKey = "CREATING_REQUEST" 112 | readComplete errorKey = "READ_COMPLETE" 113 | submitStartedString errorKey = "SUBMIT_STARTED_STRING" 114 | submitStartedBytecode errorKey = "SUBMIT_STARTED_BYTECODE" 115 | failedToCloseInErrorCallback errorKey = "FAILED_TO_CLOSE_IN_ERROR_CALLBACK" 116 | failedToWriteMessage errorKey = "FAILED_TO_WRITE_MESSAGE" 117 | failedToSetWriteDeadline errorKey = "FAILED_TO_SET_WRITE_DEADLINE" 118 | logErrorGeneric errorKey = "LOG_ERROR_GENERIC" 119 | creatingSessionConnection errorKey = "CREATING_SESSION_CONNECTION" 120 | closeSession errorKey = "CLOSE_SESSION" 121 | closeSessionRequestError errorKey = "CLOSE_SESSION_REQUEST_ERROR" 122 | closeDriverRemoteConnection errorKey = "CLOSE_DRIVER_REMOTE_CONNECTION" 123 | closingSpawnedSessions errorKey = "CLOSING_SPAWNED_SESSIONS" 124 | closeClient errorKey = "CLOSE_CLIENT" 125 | errorClosingConnection errorKey = "ERROR_CLOSING_CONNECTION" 126 | createConnectionError errorKey = "CREATE_CONNECTION_ERROR" 127 | poolNewConnectionError errorKey = "POOL_NEW_CONNECTION_ERROR" 128 | sessionDetected errorKey = "SESSION_DETECTED" 129 | poolInitialExceedsMaximum errorKey = "POOL_INITIAL_EXCEEDS_MAXIMUM" 130 | ) 131 | -------------------------------------------------------------------------------- /gremlin-go/driver/protocol_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "sync" 24 | "testing" 25 | "time" 26 | 27 | "github.com/stretchr/testify/assert" 28 | "golang.org/x/text/language" 29 | ) 30 | 31 | func TestProtocol(t *testing.T) { 32 | t.Run("Test protocol connect error.", func(t *testing.T) { 33 | connSettings := newDefaultConnectionSettings() 34 | connSettings.authInfo, connSettings.tlsConfig = nil, nil 35 | connSettings.keepAliveInterval, connSettings.writeDeadline, connSettings.writeDeadline = keepAliveIntervalDefault, writeDeadlineDefault, connectionTimeoutDefault 36 | 37 | protocol, err := newGremlinServerWSProtocol(newLogHandler(&defaultLogger{}, Info, language.English), Gorilla, 38 | "ws://localhost:9000/gremlin", connSettings, 39 | nil, nil) 40 | assert.NotNil(t, err) 41 | assert.Nil(t, protocol) 42 | }) 43 | 44 | t.Run("Test protocol close wait", func(t *testing.T) { 45 | wg := &sync.WaitGroup{} 46 | protocol := &gremlinServerWSProtocol{ 47 | closed: true, 48 | mutex: sync.Mutex{}, 49 | wg: wg, 50 | } 51 | wg.Add(1) 52 | 53 | done := make(chan bool) 54 | 55 | go func() { 56 | protocol.close(true) 57 | done <- true 58 | }() 59 | 60 | select { 61 | case <-time.After(1 * time.Second): 62 | // Ok. Close must wait. 63 | case <-done: 64 | t.Fatal("protocol.close is not waiting") 65 | } 66 | }) 67 | 68 | t.Run("Test protocol close no wait", func(t *testing.T) { 69 | wg := &sync.WaitGroup{} 70 | protocol := &gremlinServerWSProtocol{ 71 | closed: true, 72 | mutex: sync.Mutex{}, 73 | wg: wg, 74 | } 75 | wg.Add(1) 76 | 77 | done := make(chan bool) 78 | 79 | go func() { 80 | protocol.close(false) 81 | done <- true 82 | }() 83 | 84 | select { 85 | case <-time.After(1 * time.Second): 86 | t.Fatal("protocol.close is waiting") 87 | case <-done: 88 | // Ok. Close must not wait. 89 | } 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /gremlin-go/driver/requestOptions.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "github.com/google/uuid" 24 | ) 25 | 26 | type RequestOptions struct { 27 | requestID uuid.UUID 28 | evaluationTimeout int 29 | batchSize int 30 | userAgent string 31 | bindings map[string]interface{} 32 | materializeProperties string 33 | aliases map[string]interface{} 34 | } 35 | 36 | type RequestOptionsBuilder struct { 37 | requestID uuid.UUID 38 | evaluationTimeout int 39 | batchSize int 40 | userAgent string 41 | bindings map[string]interface{} 42 | materializeProperties string 43 | aliases map[string]interface{} 44 | } 45 | 46 | func (builder *RequestOptionsBuilder) SetRequestId(requestId uuid.UUID) *RequestOptionsBuilder { 47 | builder.requestID = requestId 48 | return builder 49 | } 50 | 51 | func (builder *RequestOptionsBuilder) SetEvaluationTimeout(evaluationTimeout int) *RequestOptionsBuilder { 52 | builder.evaluationTimeout = evaluationTimeout 53 | return builder 54 | } 55 | 56 | func (builder *RequestOptionsBuilder) SetBatchSize(batchSize int) *RequestOptionsBuilder { 57 | builder.batchSize = batchSize 58 | return builder 59 | } 60 | 61 | func (builder *RequestOptionsBuilder) SetUserAgent(userAgent string) *RequestOptionsBuilder { 62 | builder.userAgent = userAgent 63 | return builder 64 | } 65 | 66 | func (builder *RequestOptionsBuilder) SetBindings(bindings map[string]interface{}) *RequestOptionsBuilder { 67 | builder.bindings = bindings 68 | return builder 69 | } 70 | 71 | func (builder *RequestOptionsBuilder) SetMaterializeProperties(materializeProperties string) *RequestOptionsBuilder { 72 | builder.materializeProperties = materializeProperties 73 | return builder 74 | } 75 | 76 | func (builder *RequestOptionsBuilder) AddBinding(key string, binding interface{}) *RequestOptionsBuilder { 77 | if builder.bindings == nil { 78 | builder.bindings = make(map[string]interface{}) 79 | } 80 | builder.bindings[key] = binding 81 | return builder 82 | } 83 | 84 | func (builder *RequestOptionsBuilder) AddAliases(key string, aliases interface{}) *RequestOptionsBuilder { 85 | if builder.aliases == nil { 86 | builder.aliases = make(map[string]interface{}) 87 | } 88 | builder.aliases[key] = aliases 89 | return builder 90 | } 91 | 92 | func (builder *RequestOptionsBuilder) Create() RequestOptions { 93 | requestOptions := new(RequestOptions) 94 | 95 | requestOptions.requestID = builder.requestID 96 | requestOptions.evaluationTimeout = builder.evaluationTimeout 97 | requestOptions.batchSize = builder.batchSize 98 | requestOptions.userAgent = builder.userAgent 99 | requestOptions.bindings = builder.bindings 100 | requestOptions.materializeProperties = builder.materializeProperties 101 | requestOptions.aliases = builder.aliases 102 | 103 | return *requestOptions 104 | } 105 | -------------------------------------------------------------------------------- /gremlin-go/driver/requestOptions_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "github.com/google/uuid" 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestRequestOptions(t *testing.T) { 30 | t.Run("Test RequestOptionsBuilder with custom requestID", func(t *testing.T) { 31 | requestId := uuid.New() 32 | r := new(RequestOptionsBuilder).SetRequestId(requestId).Create() 33 | assert.Equal(t, requestId, r.requestID) 34 | }) 35 | t.Run("Test RequestOptionsBuilder with custom evaluationTimeout", func(t *testing.T) { 36 | r := new(RequestOptionsBuilder).SetEvaluationTimeout(1234).Create() 37 | assert.Equal(t, 1234, r.evaluationTimeout) 38 | }) 39 | t.Run("Test RequestOptionsBuilder with custom batchSize", func(t *testing.T) { 40 | r := new(RequestOptionsBuilder).SetBatchSize(123).Create() 41 | assert.Equal(t, 123, r.batchSize) 42 | }) 43 | t.Run("Test RequestOptionsBuilder with custom userAgent", func(t *testing.T) { 44 | r := new(RequestOptionsBuilder).SetUserAgent("TestUserAgent").Create() 45 | assert.Equal(t, "TestUserAgent", r.userAgent) 46 | }) 47 | t.Run("Test RequestOptionsBuilder with custom materializeProperties", func(t *testing.T) { 48 | r := new(RequestOptionsBuilder).SetMaterializeProperties("TestMaterializeProperties").Create() 49 | assert.Equal(t, "TestMaterializeProperties", r.materializeProperties) 50 | }) 51 | t.Run("Test RequestOptionsBuilder with custom bindings", func(t *testing.T) { 52 | bindings := map[string]interface{}{"x": 2, "y": 5} 53 | r := new(RequestOptionsBuilder).SetBindings(bindings).Create() 54 | assert.Equal(t, bindings, r.bindings) 55 | }) 56 | t.Run("Test RequestOptionsBuilder AddBinding() with no other bindings", func(t *testing.T) { 57 | r := new(RequestOptionsBuilder).AddBinding("x", 2).AddBinding("y", 5).Create() 58 | expectedBindings := map[string]interface{}{"x": 2, "y": 5} 59 | assert.Equal(t, expectedBindings, r.bindings) 60 | }) 61 | t.Run("Test RequestOptionsBuilder AddBinding() overwriting existing key", func(t *testing.T) { 62 | r := new(RequestOptionsBuilder).AddBinding("x", 2).AddBinding("x", 5).Create() 63 | expectedBindings := map[string]interface{}{"x": 5} 64 | assert.Equal(t, expectedBindings, r.bindings) 65 | }) 66 | t.Run("Test RequestOptionsBuilder AddBinding() with existing bindings", func(t *testing.T) { 67 | bindings := map[string]interface{}{"x": 2, "y": 5} 68 | r := new(RequestOptionsBuilder).SetBindings(bindings).AddBinding("z", 7).Create() 69 | expectedBindings := map[string]interface{}{"x": 2, "y": 5, "z": 7} 70 | assert.Equal(t, expectedBindings, r.bindings) 71 | }) 72 | t.Run("Test RequestOptionsBuilder SetBinding(...), SetBinding(nil), AddBinding(...)", func(t *testing.T) { 73 | bindings := map[string]interface{}{"x": 2, "y": 5} 74 | r := new(RequestOptionsBuilder).SetBindings(bindings). 75 | SetBindings(nil).AddBinding("z", 7).Create() 76 | expectedBindings := map[string]interface{}{"z": 7} 77 | assert.Equal(t, expectedBindings, r.bindings) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /gremlin-go/driver/request_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "github.com/google/uuid" 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestRequest(t *testing.T) { 30 | t.Run("Test makeStringRequest() with custom requestID", func(t *testing.T) { 31 | requestId := uuid.New() 32 | r := makeStringRequest("g.V()", "g", "", 33 | new(RequestOptionsBuilder).SetRequestId(requestId).Create()) 34 | assert.Equal(t, requestId, r.requestID) 35 | }) 36 | 37 | t.Run("Test makeStringRequest() with no bindings", func(t *testing.T) { 38 | r := makeStringRequest("g.V()", "g", "", *new(RequestOptions)) 39 | assert.NotNil(t, r.requestID) 40 | assert.NotEqual(t, uuid.Nil, r.requestID) 41 | }) 42 | 43 | t.Run("Test makeStringRequest() with custom evaluationTimeout", func(t *testing.T) { 44 | r := makeStringRequest("g.V()", "g", "", 45 | new(RequestOptionsBuilder).SetEvaluationTimeout(1234).Create()) 46 | assert.NotNil(t, r.requestID) 47 | assert.NotEqual(t, uuid.Nil, r.requestID) 48 | assert.Equal(t, 1234, r.args["evaluationTimeout"]) 49 | }) 50 | 51 | t.Run("Test makeStringRequest() with custom batchSize", func(t *testing.T) { 52 | r := makeStringRequest("g.V()", "g", "", 53 | new(RequestOptionsBuilder).SetBatchSize(123).Create()) 54 | assert.NotNil(t, r.requestID) 55 | assert.NotEqual(t, uuid.Nil, r.requestID) 56 | assert.Equal(t, 123, r.args["batchSize"]) 57 | }) 58 | 59 | t.Run("Test makeStringRequest() with custom userAgent", func(t *testing.T) { 60 | r := makeStringRequest("g.V()", "g", "", 61 | new(RequestOptionsBuilder).SetUserAgent("TestUserAgent").Create()) 62 | assert.NotNil(t, r.requestID) 63 | assert.NotEqual(t, uuid.Nil, r.requestID) 64 | assert.Equal(t, "TestUserAgent", r.args["userAgent"]) 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /gremlin-go/driver/resources/error-messages/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "E0101_CONNECTION_CLOSE_ERROR": "E0101: cannot close connection that has already been closed or has not been connected", 3 | "E0102_CONNECTION_WRITE_CLOSED_ERROR": "E0102: cannot write to connection that has already been closed or has not been connected", 4 | "E0103_CONNECTIONPOOL_CLOSED_ERROR": "E0103: cannot invoke methods after connections are closed", 5 | "E0104_CONNECTIONPOOL_INSTANTIATE_FAIL": "E0104: no successful connections could be made: %s", 6 | "E0105_CONNECTIONPOOL_FULL_NONE_VALID": "E0105: no valid connections found and maximum concurrent connection count reached", 7 | 8 | "E0201_DRIVER_REMOTE_CONNECTION_CREATESESSION_MULTIPLE_UUIDS_ERROR": "E0201: more than one Session ID specified. Cannot create Session with multiple UUIDs", 9 | "E0202_DRIVER_REMOTE_CONNECTION_CREATESESSION_SESSION_FROM_SESSION_ERROR": "E0202: connection is already bound to a Session - child sessions are not allowed", 10 | "E0203_DRIVER_REMOTE_CONNECTION_SUBMITBYTECODE_TO_CLOSED_CONNECTION_ERROR": "E0203: cannot submit bytecode to closed connection", 11 | 12 | "E0301_GRAPH_GETPATHOBJECT_UNEQUAL_LABELS_OBJECTS_LENGTH_ERROR": "E0301: path is invalid because it does not contain an equal number of Labels and Objects", 13 | "E0302_GRAPH_GETPATHOBJECT_NON_STRING_VALUE_IN_LABELS_ERROR": "E0302: path is invalid because labels contains a non string type", 14 | "E0303_GRAPH_GETPATHOBJECT_NO_LABEL_ERROR": "E0303: path does not contain a Label of '%s'", 15 | 16 | "E0401_GRAPH_BINARY_WRITETYPEVALUE_UNEXPECTED_NULL_ERROR":"E0401: unexpected null value to writeTypeValue when nullable is false", 17 | "E0402_GRAPH_BINARY_WRITER_BYTECODE_ERROR": "E0402: need GraphTraversal or bytecode to write bytecode", 18 | "E0403_GRAPH_BINARY_WRITEVALUE_UNEXPECTED_NULL_ERROR":"E0403: unexpected null value to writeValue when nullable is false", 19 | "E0404_GRAPH_BINARY_READ_NULLTYPE_ERROR": "E0404: expected isNull check to be true for NullType", 20 | "E0405_GRAPH_BINARY_READVALUE_NULL_INPUT_ERROR":"E0405: input cannot be null", 21 | "E0406_GRAPH_BINARY_ENUMREADER_INVALID_TYPE_ERROR": "E0406: error, expected string type for enum, but got % x", 22 | "E0407_GRAPH_BINARY_GETSERIALIZERTOWRITE_UNKNOWN_TYPE_ERROR":"E0407: unknown data type to serialize %s", 23 | "E0408_GRAPH_BINARY_GETSERIALIZERTOREAD_UNKNOWN_TYPE_ERROR": "E0408: unknown data type to deserialize 0x%x", 24 | "E0409_GRAPH_BINARY_GETSERIALIZERTOREAD_UNKNOWN_CUSTOM_TYPE_ERROR": "E0409: unknown custom data type to deserialize %s", 25 | 26 | "E0501_PROTOCOL_RESPONSEHANDLER_NO_RESULTSET_ON_DATA_RECEIVE":"E0501: resultSet was not created before data was received", 27 | "E0502_PROTOCOL_RESPONSEHANDLER_READ_LOOP_ERROR": "E0502: error in read loop, error message '%+v'. statusCode: %d", 28 | "E0503_PROTOCOL_RESPONSEHANDLER_AUTH_ERROR":"E0503: failed to authenticate %v : %v", 29 | 30 | "E0601_RESULT_NOT_VERTEX_ERROR":"E0601: result is not a Vertex", 31 | "E0602_RESULT_NOT_EDGE_ERROR": "E0602: result is not an Edge", 32 | "E0603_RESULT_NOT_ELEMENT_ERROR":"E0603: result is not an Element", 33 | "E0604_RESULT_NOT_PATH_ERROR": "E0604: result is not a Path", 34 | "E0605_RESULT_NOT_PROPERTY_ERROR":"E0605: result is not a Property", 35 | "E0606_RESULT_NOT_VERTEX_PROPERTY_ERROR": "E0606: result is not a VertexProperty", 36 | "E0607_RESULT_NOT_TRAVERSER_ERROR":"E0607: result is not a Traverser", 37 | "E0608_RESULT_NOT_SLICE_ERROR": "E0608: result is not a Slice", 38 | 39 | "E0701_SERIALIZER_READMAP_NULL_KEY_ERROR":"E0701: expected non-null Key for map", 40 | "E0703_SERIALIZER_READMAP_NON_STRING_KEY_ERROR":"E0703: expected string Key for map, got type='0x%x'", 41 | "E0704_SERIALIZER_CONVERTARGS_NO_SERIALIZER_ERROR": "E0704: failed to find serializer for type %q", 42 | 43 | "E0801_TRANSPORTERFACTORY_GETTRANSPORTLAYER_NO_TYPE_ERROR":"E0801: transport layer type was not specified and cannot be initialized", 44 | 45 | "E0901_TRAVERSAL_TOLIST_ANON_TRAVERSAL_ERROR":"E0901: cannot invoke this method from an anonymous traversal", 46 | "E0902_TRAVERSAL_ITERATE_ANON_TRAVERSAL_ERROR": "E0902: cannot invoke this method from an anonymous traversal", 47 | "E0903_TRAVERSAL_NEXT_NO_RESULTS_LEFT_ERROR":"E0903: there are no results left", 48 | 49 | "E1001_BYTECODE_CHILD_T_NOT_ANON_ERROR": "E1001: the child traversal was not spawned anonymously - use the T__ class rather than a TraversalSource to construct the child traversal", 50 | 51 | "E1101_TRANSACTION_REPEATED_OPEN_ERROR": "E1101: transaction already started on this object", 52 | "E1102_TRANSACTION_ROLLBACK_NOT_OPENED_ERROR": "E1102: cannot rollback a transaction that is not started", 53 | "E1103_TRANSACTION_COMMIT_NOT_OPENED_ERROR": "E1103: cannot commit a transaction that is not started", 54 | "E1104_TRANSACTION_REPEATED_CLOSE_ERROR": "E1104: cannot close a transaction that has previously been closed" 55 | } 56 | -------------------------------------------------------------------------------- /gremlin-go/driver/resources/logger-messages/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "UNKNOWN_SER_DATATYPE": "Unknown data type to serialize type='%s'.", 3 | "UNKNOWN_DESER_DATATYPE": "Unknown data type to deserialize code='%x'.", 4 | "NULL_INPUT": "Input cannot be null.", 5 | "UNEXPECTED_NULL_VALUE": "Unexpected null value to write when value is not nullable.", 6 | "CLOSING_CONNECTION": "Closing the connection.", 7 | "FAILED_CONNECTION" : "Failed to instantiate the new connection; setting connection state to closed.", 8 | "OPENING_CONNECTION": "Connecting.", 9 | "WRITE_REQUEST": "Writing request.", 10 | "READ_LOOP_ERROR": "Read loop error '%s', closing read loop.", 11 | "ERROR_CALLBACK": "Connection error callback invoked, closing protocol.", 12 | "CREATING_REQUEST": "Created request with id '%s'", 13 | "READ_COMPLETE": "Read complete for response with id '%s'", 14 | "SUBMIT_STARTED_STRING": "Client invoked Submit with string argument: %s", 15 | "SUBMIT_STARTED_BYTECODE": "Client invoked Submit with bytecode argument: %+v\n", 16 | "FAILED_TO_CLOSE_IN_ERROR_CALLBACK": "Failed to close protocol in error callback '%s'", 17 | "FAILED_TO_WRITE_MESSAGE": "Failed to write message type: '%s', error: '%s'", 18 | "FAILED_TO_SET_WRITE_DEADLINE": "Failed to set write deadline, error: '%s'", 19 | "LOG_ERROR_GENERIC": "Error occurred during operation %s: '%s'", 20 | "CREATING_SESSION_CONNECTION": "Creating Session based connection", 21 | "CLOSING_SPAWNED_SESSIONS": "Closing spawned sessions from DriverRemoteConnection with url '%s'", 22 | "CLOSE_SESSION": "Closing DriverRemoteConnection with url '%s' with session '%s'", 23 | "CLOSE_SESSION_REQUEST_ERROR": "Error while closing DriverRemoteConnection with url '%s' and with session '%s': %s", 24 | "CLOSE_DRIVER_REMOTE_CONNECTION": "Closing DriverRemoteConnection with url '%s'", 25 | "CLOSE_CLIENT": "Closing Client with url '%s'", 26 | "ERROR_CLOSING_CONNECTION": "Ignoring error closing connection: %s", 27 | "CREATE_CONNECTION_ERROR": "Error creating new connection for connection pool: %s", 28 | "POOL_NEW_CONNECTION_ERROR": "Falling back to least-used connection. Creating new connection due to least-used connection exceeding concurrent usage threshold failed: %s", 29 | "SESSION_DETECTED": "Session detected. Setting connection pool size maximum to 1.", 30 | "POOL_INITIAL_EXCEEDS_MAXIMUM": "InitialConcurrentConnections setting %d exceeded MaximumConcurrentConnections setting %d - limiting InitialConcurrentConnections to %d." 31 | } 32 | -------------------------------------------------------------------------------- /gremlin-go/driver/resources/resources.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package resources 21 | 22 | import ( 23 | "embed" 24 | ) 25 | 26 | //go:embed logger-messages/*.json 27 | var LoggerMessagesFS embed.FS 28 | 29 | //go:embed error-messages/*.json 30 | var ErrorMessagesFS embed.FS 31 | -------------------------------------------------------------------------------- /gremlin-go/driver/response.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import "github.com/google/uuid" 23 | 24 | // responseStatus contains the status info of the response. 25 | type responseStatus struct { 26 | code uint16 27 | message string 28 | attributes map[string]interface{} 29 | } 30 | 31 | // responseResult contains the result info of the response. 32 | type responseResult struct { 33 | meta map[string]interface{} 34 | data interface{} 35 | } 36 | 37 | // response represents a response from the server. 38 | type response struct { 39 | responseID uuid.UUID 40 | responseStatus responseStatus 41 | responseResult responseResult 42 | } 43 | -------------------------------------------------------------------------------- /gremlin-go/driver/resultSet_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "fmt" 24 | "sync" 25 | "testing" 26 | "time" 27 | 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func getSyncMap() *synchronizedMap { 32 | return &synchronizedMap{ 33 | make(map[string]ResultSet), 34 | sync.Mutex{}, 35 | } 36 | } 37 | 38 | func TestChannelResultSet(t *testing.T) { 39 | const mockID = "mockID" 40 | 41 | t.Run("Test ResultSet test getter/setters.", func(t *testing.T) { 42 | r := newChannelResultSet(mockID, getSyncMap()) 43 | testStatusAttribute := map[string]interface{}{ 44 | "1": 1234, 45 | "2": "foo", 46 | } 47 | testAggregateTo := "test2" 48 | r.setStatusAttributes(testStatusAttribute) 49 | assert.Equal(t, r.GetStatusAttributes(), testStatusAttribute) 50 | r.setAggregateTo(testAggregateTo) 51 | assert.Equal(t, r.GetAggregateTo(), testAggregateTo) 52 | }) 53 | 54 | t.Run("Test ResultSet close.", func(t *testing.T) { 55 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 56 | assert.NotPanics(t, func() { channelResultSet.Close() }) 57 | }) 58 | 59 | t.Run("Test ResultSet one.", func(t *testing.T) { 60 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 61 | AddResults(channelResultSet, 10) 62 | idx := 0 63 | for i := 0; i < 10; i++ { 64 | result, ok, err := channelResultSet.One() 65 | assert.Nil(t, err) 66 | assert.True(t, ok) 67 | assert.Equal(t, result.GetString(), fmt.Sprintf("%v", idx)) 68 | idx++ 69 | } 70 | go closeAfterTime(500, channelResultSet) 71 | res, ok, err := channelResultSet.One() 72 | assert.Nil(t, err) 73 | assert.False(t, ok) 74 | assert.Nil(t, res) 75 | }) 76 | 77 | t.Run("Test ResultSet one Paused.", func(t *testing.T) { 78 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 79 | go AddResultsPause(channelResultSet, 10, 500) 80 | idx := 0 81 | for i := 0; i < 10; i++ { 82 | result, ok, err := channelResultSet.One() 83 | assert.Nil(t, err) 84 | assert.True(t, ok) 85 | assert.Equal(t, result.GetString(), fmt.Sprintf("%v", idx)) 86 | idx++ 87 | } 88 | go closeAfterTime(500, channelResultSet) 89 | result, ok, err := channelResultSet.One() 90 | assert.Nil(t, err) 91 | assert.False(t, ok) 92 | assert.Nil(t, result) 93 | }) 94 | 95 | t.Run("Test ResultSet one close.", func(t *testing.T) { 96 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 97 | channelResultSet.Close() 98 | }) 99 | 100 | t.Run("Test ResultSet All.", func(t *testing.T) { 101 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 102 | AddResults(channelResultSet, 10) 103 | go closeAfterTime(500, channelResultSet) 104 | results, err := channelResultSet.All() 105 | assert.Nil(t, err) 106 | for idx, result := range results { 107 | assert.Equal(t, result.GetString(), fmt.Sprintf("%v", idx)) 108 | } 109 | }) 110 | 111 | t.Run("Test ResultSet All close before.", func(t *testing.T) { 112 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 113 | AddResults(channelResultSet, 10) 114 | channelResultSet.Close() 115 | results, err := channelResultSet.All() 116 | assert.Nil(t, err) 117 | assert.Equal(t, len(results), 10) 118 | for idx, result := range results { 119 | assert.Equal(t, result.GetString(), fmt.Sprintf("%v", idx)) 120 | } 121 | }) 122 | 123 | t.Run("Test ResultSet IsEmpty before signal.", func(t *testing.T) { 124 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 125 | go closeAfterTime(500, channelResultSet) 126 | empty := channelResultSet.IsEmpty() 127 | assert.True(t, empty) 128 | }) 129 | 130 | t.Run("Test ResultSet IsEmpty after signal.", func(t *testing.T) { 131 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 132 | channelResultSet.Close() 133 | empty := channelResultSet.IsEmpty() 134 | assert.True(t, empty) 135 | }) 136 | 137 | t.Run("Test ResultSet IsEmpty after close.", func(t *testing.T) { 138 | channelResultSet := newChannelResultSet(mockID, getSyncMap()) 139 | go addAfterTime(500, channelResultSet) 140 | empty := channelResultSet.IsEmpty() 141 | assert.False(t, empty) 142 | channelResultSet.One() 143 | go closeAfterTime(500, channelResultSet) 144 | empty = channelResultSet.IsEmpty() 145 | assert.True(t, empty) 146 | }) 147 | 148 | t.Run("Test ResultSet removes self from container.", func(t *testing.T) { 149 | container := getSyncMap() 150 | assert.Equal(t, 0, container.size()) 151 | channelResultSet := newChannelResultSet(mockID, container) 152 | container.store(mockID, channelResultSet) 153 | assert.Equal(t, 1, container.size()) 154 | channelResultSet.Close() 155 | assert.Equal(t, 0, container.size()) 156 | }) 157 | } 158 | 159 | func AddResultsPause(resultSet ResultSet, count int, ticks time.Duration) { 160 | for i := 0; i < count/2; i++ { 161 | resultSet.addResult(&Result{i}) 162 | } 163 | time.Sleep(ticks * time.Millisecond) 164 | for i := count / 2; i < count; i++ { 165 | resultSet.addResult(&Result{i}) 166 | } 167 | } 168 | 169 | func AddResults(resultSet ResultSet, count int) { 170 | for i := 0; i < count; i++ { 171 | resultSet.addResult(&Result{i}) 172 | } 173 | } 174 | 175 | func closeAfterTime(millisecondTicks time.Duration, resultSet ResultSet) { 176 | time.Sleep(millisecondTicks * time.Millisecond) 177 | resultSet.Close() 178 | } 179 | 180 | func addAfterTime(millisecondTicks time.Duration, resultSet ResultSet) { 181 | time.Sleep(millisecondTicks * time.Millisecond) 182 | resultSet.addResult(&Result{1}) 183 | } 184 | -------------------------------------------------------------------------------- /gremlin-go/driver/transporter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import "time" 23 | 24 | type transporter interface { 25 | Connect() error 26 | Write(data []byte) error 27 | Read() ([]byte, error) 28 | Close() error 29 | IsClosed() bool 30 | getAuthInfo() AuthInfoProvider 31 | } 32 | 33 | type websocketConn interface { 34 | WriteMessage(int, []byte) error 35 | ReadMessage() (int, []byte, error) 36 | SetPongHandler(h func(appData string) error) 37 | Close() error 38 | SetReadDeadline(t time.Time) error 39 | SetWriteDeadline(t time.Time) error 40 | } 41 | -------------------------------------------------------------------------------- /gremlin-go/driver/transporterFactory.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package gremlingo 21 | 22 | import ( 23 | "sync" 24 | ) 25 | 26 | // TransporterType is an alias for valid transport protocols. 27 | type TransporterType int 28 | 29 | const ( 30 | // Gorilla transport layer: github.com/gorilla/websocket 31 | Gorilla TransporterType = iota + 1 32 | ) 33 | 34 | func getTransportLayer(transporterType TransporterType, url string, connSettings *connectionSettings, logHandler *logHandler) (transporter, error) { 35 | var transporter transporter 36 | switch transporterType { 37 | case Gorilla: 38 | transporter = &gorillaTransporter{ 39 | url: url, 40 | logHandler: logHandler, 41 | connSettings: connSettings, 42 | writeChannel: make(chan []byte, writeChannelSizeDefault), 43 | wg: &sync.WaitGroup{}, 44 | } 45 | default: 46 | return nil, newError(err0801GetTransportLayerNoTypeError) 47 | } 48 | err := transporter.Connect() 49 | if err != nil { 50 | return nil, err 51 | } 52 | return transporter, nil 53 | } 54 | -------------------------------------------------------------------------------- /gremlin-go/driver/user_agent.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package gremlingo 20 | 21 | import ( 22 | "fmt" 23 | "os" 24 | "path/filepath" 25 | "runtime" 26 | "strings" 27 | ) 28 | 29 | /** 30 | * User Agent body to be sent in web socket handshake 31 | * Has the form of: 32 | * [Application Name] [GLV Name]/[Version] [Language Runtime Version] [OS]/[Version] [CPU Architecture] 33 | * Note: Go does not have any builtin functionality to detect the OS version, therefore OS version will always be 34 | * reported as NotAvailable 35 | */ 36 | var userAgent string 37 | 38 | const userAgentHeader = "User-Agent" 39 | 40 | const gremlinVersion = "4.0.0-SNAPSHOT" // DO NOT MODIFY - Configured automatically by Maven Replacer Plugin 41 | 42 | func init() { 43 | applicationName := "NotAvailable" 44 | 45 | path, err := os.Executable() 46 | if err == nil { 47 | applicationName = filepath.Base(path) 48 | } 49 | 50 | applicationName = strings.ReplaceAll(applicationName, " ", "_") 51 | runtimeVersion := strings.ReplaceAll(runtime.Version(), " ", "_") 52 | osName := strings.ReplaceAll(runtime.GOOS, " ", "_") 53 | architecture := strings.ReplaceAll(runtime.GOARCH, " ", "_") 54 | userAgent = fmt.Sprintf("%v Gremlin-Go.%v %v %v.NotAvailable %v", applicationName, gremlinVersion, runtimeVersion, osName, architecture) 55 | } 56 | -------------------------------------------------------------------------------- /gremlin-go/examples/basic_gremlin.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/apache/tinkerpop/gremlin-go/v3/driver" 26 | ) 27 | 28 | func main() { 29 | driverRemoteConnection, err := gremlingo.NewDriverRemoteConnection("ws://localhost:8182/gremlin") 30 | if err != nil { 31 | fmt.Println(err) 32 | return 33 | } 34 | defer driverRemoteConnection.Close() 35 | g := gremlingo.Traversal_().WithRemote(driverRemoteConnection) 36 | 37 | // Basic Gremlin: adding and retrieving data 38 | v1, err := g.AddV("person").Property("name", "marko").Next() 39 | v2, err := g.AddV("person").Property("name", "stephen").Next() 40 | v3, err := g.AddV("person").Property("name", "vadas").Next() 41 | v1Vertex, err := v1.GetVertex() 42 | v2Vertex, err := v2.GetVertex() 43 | v3Vertex, err := v3.GetVertex() 44 | 45 | // Be sure to use a terminating step like Next() or Iterate() so that the traversal "executes" 46 | // Iterate() does not return any data and is used to just generate side-effects (i.e. write data to the database) 47 | promise := g.V(v1Vertex).AddE("knows").To(v2Vertex).Property("weight", 0.75).Iterate() 48 | err = <-promise 49 | if err != nil { 50 | fmt.Println(err) 51 | return 52 | } 53 | promise = g.V(v1Vertex).AddE("knows").To(v3Vertex).Property("weight", 0.75).Iterate() 54 | err = <-promise 55 | if err != nil { 56 | fmt.Println(err) 57 | return 58 | } 59 | 60 | // Retrieve the data from the "marko" vertex 61 | marko, err := g.V().Has("person", "name", "marko").Values("name").Next() 62 | fmt.Println("name:", marko.GetString()) 63 | 64 | // Find the "marko" vertex and then traverse to the people he "knows" and return their data 65 | peopleMarkoKnows, err := g.V().Has("person", "name", "marko").Out("knows").Values("name").ToList() 66 | for _, person := range peopleMarkoKnows { 67 | fmt.Println("marko knows", person.GetString()) 68 | } 69 | } -------------------------------------------------------------------------------- /gremlin-go/examples/connections.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | ) 25 | 26 | func main() { 27 | withRemote() 28 | withConfigs() 29 | } 30 | 31 | func withRemote() { 32 | // Creating the connection to the server 33 | driverRemoteConnection, err := gremlingo.NewDriverRemoteConnection("ws://localhost:8182/gremlin") 34 | 35 | // Error handling 36 | if err != nil { 37 | fmt.Println(err) 38 | return 39 | } 40 | 41 | // Cleanup 42 | defer driverRemoteConnection.Close() 43 | 44 | // Creating graph traversal 45 | g := gremlingo.Traversal_().With(driverRemoteConnection) 46 | 47 | // Drop existing vertices 48 | prom := g.V().Drop().Iterate() 49 | <-prom 50 | 51 | // Simple query to verify connection 52 | g.AddV().Iterate() 53 | count, _ := g.V().Count().Next() 54 | fmt.Println("Vertex count:", *count) 55 | } 56 | 57 | func withConfigs() { 58 | // Connecting to the server with customized configurations 59 | driverRemoteConnection, err := gremlingo.NewDriverRemoteConnection("ws://localhost:8182/gremlin", 60 | func(settings *gremlingo.DriverRemoteConnectionSettings) { 61 | settings.TraversalSource = "g" 62 | settings.NewConnectionThreshold = 4 63 | settings.EnableCompression = false 64 | settings.ReadBufferSize = 0 65 | settings.WriteBufferSize = 0 66 | }) 67 | 68 | if err != nil { 69 | fmt.Println(err) 70 | return 71 | } 72 | 73 | defer driverRemoteConnection.Close() 74 | g := gremlingo.Traversal_().WithRemote(driverRemoteConnection) 75 | 76 | g.AddV().Iterate() 77 | count, _ := g.V().Count().Next() 78 | fmt.Println("Vertex count:", *count) 79 | } 80 | -------------------------------------------------------------------------------- /gremlin-go/examples/go.mod: -------------------------------------------------------------------------------- 1 | // Licensed to the Apache Software Foundation (ASF) under one 2 | // or more contributor license agreements. See the NOTICE file 3 | // distributed with this work for additional information 4 | // regarding copyright ownership. The ASF licenses this file 5 | // to you under the Apache License, Version 2.0 (the 6 | // "License"); you may not use this file except in compliance 7 | // with the License. You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, 12 | // software distributed under the License is distributed on an 13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | // KIND, either express or implied. See the License for the 15 | // specific language governing permissions and limitations 16 | // under the License. 17 | 18 | module example 19 | 20 | go 1.20 21 | 22 | require github.com/apache/tinkerpop/gremlin-go/v3 v3.7.0 23 | 24 | require ( 25 | github.com/google/uuid v1.3.0 // indirect 26 | github.com/gorilla/websocket v1.5.0 // indirect 27 | github.com/nicksnyder/go-i18n/v2 v2.2.1 // indirect 28 | golang.org/x/text v0.11.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /gremlin-go/examples/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= 2 | github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/apache/tinkerpop/gremlin-go/v3 v3.7.0 h1:WSHdJ/UKSSYZj8E2QzEdUsaV54rWAoNlQSPm4x/o/sY= 4 | github.com/apache/tinkerpop/gremlin-go/v3 v3.7.0/go.mod h1:3cydTAyTJzOEI4RWqbNHtsbtnUuYmBR8ZeAxNs+yRcw= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 7 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 8 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 9 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 10 | github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA= 11 | github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 14 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 15 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 16 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 17 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 18 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 19 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 20 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 21 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 22 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 23 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 25 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 26 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 27 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 30 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 31 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 32 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 33 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 34 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 35 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 36 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 37 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 38 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 39 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 40 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 42 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 43 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 45 | -------------------------------------------------------------------------------- /gremlin-go/examples/modern_traversals.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/apache/tinkerpop/gremlin-go/v3/driver" 26 | ) 27 | 28 | var __ = gremlingo.T__ 29 | var T = gremlingo.T 30 | var P = gremlingo.P 31 | 32 | func main() { 33 | driverRemoteConnection, err := gremlingo.NewDriverRemoteConnection("ws://localhost:8182/gremlin") 34 | if err != nil { 35 | fmt.Println(err) 36 | return 37 | } 38 | defer driverRemoteConnection.Close() 39 | g := gremlingo.Traversal_().WithRemote(driverRemoteConnection) 40 | 41 | /* 42 | This example requires the Modern toy graph to be preloaded upon launching the Gremlin server. 43 | For details, see https://tinkerpop.apache.org/docs/current/reference/#gremlin-server-docker-image and use 44 | conf/gremlin-server-modern.yaml. 45 | */ 46 | e1, err := g.V(1).BothE().ToList() // (1) 47 | e2, err := g.V().BothE().Where(__.OtherV().HasId(2)).ToList() // (2) 48 | v1, err := g.V(1).Next() 49 | v2, err := g.V(2).Next() 50 | v1Vertex, err := v1.GetVertex() 51 | v2Vertex, err := v2.GetVertex() 52 | e3, err := g.V(v1Vertex).BothE().Where(__.OtherV().Is(v2Vertex)).ToList() // (3) 53 | e4, err := g.V(v1Vertex).OutE().Where(__.InV().Is(v2Vertex)).ToList() // (4) 54 | e5, err := g.V(1).OutE().Where(__.InV().Has(T.Id, P.Within(2, 3))).ToList() // (5) 55 | e6, err := g.V(1).Out().Where(__.In().HasId(6)).ToList() // (6) 56 | 57 | fmt.Println(e1) 58 | fmt.Println(e2) 59 | fmt.Println(e3) 60 | fmt.Println(e4) 61 | fmt.Println(e5) 62 | fmt.Println(e6) 63 | 64 | /* 65 | 1. There are three edges from the vertex with the identifier of "1". 66 | 2. Filter those three edges using the Where()-step using the identifier of the vertex returned by OtherV() to 67 | ensure it matches on the vertex of concern, which is the one with an identifier of "2". 68 | 3. Note that the same traversal will work if there are actual Vertex instances rather than just vertex 69 | identifiers. 70 | 4. The vertex with identifier "1" has all outgoing edges, so it would also be acceptable to use the directional 71 | steps of OutE() and InV() since the schema allows it. 72 | 5. There is also no problem with filtering the terminating side of the traversal on multiple vertices, in this 73 | case, vertices with identifiers "2" and "3". 74 | 6. There’s no reason why the same pattern of exclusion used for edges with Where() can’t work for a vertex 75 | between two vertices. 76 | */ 77 | } 78 | -------------------------------------------------------------------------------- /gremlin-go/go.mod: -------------------------------------------------------------------------------- 1 | // Licensed to the Apache Software Foundation (ASF) under one 2 | // or more contributor license agreements. See the NOTICE file 3 | // distributed with this work for additional information 4 | // regarding copyright ownership. The ASF licenses this file 5 | // to you under the Apache License, Version 2.0 (the 6 | // "License"); you may not use this file except in compliance 7 | // with the License. You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, 12 | // software distributed under the License is distributed on an 13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | // KIND, either express or implied. See the License for the 15 | // specific language governing permissions and limitations 16 | // under the License. 17 | 18 | module github.com/apache/tinkerpop/gremlin-go/v3 19 | 20 | go 1.20 21 | 22 | require ( 23 | github.com/cucumber/godog v0.13.0 24 | github.com/google/uuid v1.6.0 25 | github.com/gorilla/websocket v1.5.1 26 | github.com/nicksnyder/go-i18n/v2 v2.3.0 27 | github.com/stretchr/testify v1.8.4 28 | golang.org/x/text v0.14.0 29 | gopkg.in/yaml.v3 v3.0.1 30 | ) 31 | 32 | require ( 33 | github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect 34 | github.com/cucumber/messages/go/v21 v21.0.1 // indirect 35 | github.com/davecgh/go-spew v1.1.1 // indirect 36 | github.com/gofrs/uuid v4.3.1+incompatible // indirect 37 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 38 | github.com/hashicorp/go-memdb v1.3.4 // indirect 39 | github.com/hashicorp/golang-lru v0.5.4 // indirect 40 | github.com/pmezard/go-difflib v1.0.0 // indirect 41 | github.com/spf13/pflag v1.0.5 // indirect 42 | github.com/stretchr/objx v0.5.0 // indirect 43 | golang.org/x/net v0.17.0 // indirect 44 | gopkg.in/yaml.v3 v3.0.1 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /gremlin-go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 3 | github.com/cucumber/gherkin/go/v26 v26.2.0 h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI= 4 | github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0= 5 | github.com/cucumber/godog v0.13.0 h1:KvX9kNWmAJwp882HmObGOyBbNUP5SXQ+SDLNajsuV7A= 6 | github.com/cucumber/godog v0.13.0/go.mod h1:FX3rzIDybWABU4kuIXLZ/qtqEe1Ac5RdXmqvACJOces= 7 | github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= 8 | github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= 9 | github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay93k7Rn3Dee7iyPJjs= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 14 | github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= 15 | github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 16 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 17 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 18 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 19 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 20 | github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 21 | github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= 22 | github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 23 | github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= 24 | github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= 25 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 26 | github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= 27 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 28 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 29 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 30 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 31 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 32 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 33 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 34 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 35 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 36 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 37 | github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ= 38 | github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= 39 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 40 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 41 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 42 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 43 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 44 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 45 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 46 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 47 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 48 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 49 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 50 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 51 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 52 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 53 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 54 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 55 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 56 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 57 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 58 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 59 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 61 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 62 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 63 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 64 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 65 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 66 | -------------------------------------------------------------------------------- /gremlin-go/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # 20 | 21 | function usage { 22 | echo -e "\nUsage: $(basename "$0") [OPTIONS]" \ 23 | "\n\nRun the Gremlin-Go test suite in Docker." \ 24 | "\n\nOptions:" \ 25 | "\n\t \t Optional value, if unspecified the test suite will run with the current version 26 | \t\t\t of Gremlin server and Gremlin Neo4j, as specified in the TinkerPop pom.xml file." \ 27 | "\n\t-h, --help \t\t Show this message." \ 28 | "\n\nExamples:" \ 29 | "\n\tRunning the default: ./run.sh" \ 30 | "\n\tThe default requires a SNAPSHOT server image to be built using: 31 | mvn clean install -pl :gremlin-server -DskipTests -DskipIntegrationTests=true -am && mvn install -Pdocker-images -pl :gremlin-server" \ 32 | "\n\tRunning a prebuilt local SNAPSHOT build: ./run.sh 3.x.x-SNAPSHOT" \ 33 | "\n\tRunning a released version: ./run.sh 3.5.3" \ 34 | "\n" 35 | } 36 | 37 | if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then 38 | usage 39 | exit 0 40 | fi 41 | 42 | # Parses current gremlin server version from project pom.xml file using perl regex 43 | GREMLIN_SERVER_VERSION=$(grep tinkerpop -A2 pom.xml | sed -r -n 's/.*(([0-9]+\.?){3})(-SNAPSHOT)?<\/version>/\1\3/p') 44 | export GREMLIN_SERVER="${1:-$GREMLIN_SERVER_VERSION}" 45 | echo "Running server version: $GREMLIN_SERVER" 46 | 47 | ABS_PROJECT_HOME=$(dirname $(realpath "$0"))/.. 48 | export ABS_PROJECT_HOME 49 | 50 | # Passes current gremlin server version into docker compose as environment variable 51 | docker-compose up --build --exit-code-from gremlin-go-integration-tests 52 | EXIT_CODE=$? 53 | # Removes all service containers 54 | docker-compose down 55 | exit $EXIT_CODE 56 | -------------------------------------------------------------------------------- /html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppygraph-ui", 3 | "version": "0.1.0", 4 | "homepage": "./", 5 | "private": true, 6 | "dependencies": { 7 | "@emotion/react": "^11.11.4", 8 | "@emotion/styled": "^11.11.5", 9 | "@fontsource/roboto": "^5.0.13", 10 | "@headlessui/react": "^1.7.17", 11 | "@heroicons/react": "^2.0.18", 12 | "@mui/material": "^5.15.16", 13 | "@pixi/filter-drop-shadow": "^5.2.0", 14 | "@pixi/react": "^7.1.1", 15 | "@tailwindcss/forms": "^0.5.7", 16 | "@uiw/codemirror-extensions-langs": "^4.21.21", 17 | "@uiw/codemirror-themes-all": "^4.21.21", 18 | "@uiw/react-codemirror": "^4.21.21", 19 | "allotment": "^1.20.0", 20 | "d3": "^7.8.5", 21 | "d3-quadtree": "^3.0.1", 22 | "file-saver": "^2.0.5", 23 | "jquery": "^3.7.1", 24 | "material-colors": "^1.2.6", 25 | "pixi.js": "^7.4.0", 26 | "postcss": "^8.4.32", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-hook-form": "^7.49.2", 30 | "react-modal": "^3.16.1", 31 | "react-router-dom": "^6.20.1", 32 | "react-scripts": "^5.0.1", 33 | "tailwindcss": "^3.3.6", 34 | "use-debounce": "^10.0.0", 35 | "web-vitals": "^2.1.4", 36 | "webpack": "^5.89.0" 37 | }, 38 | "scripts": { 39 | "start": "react-scripts start", 40 | "build": "react-scripts build", 41 | "test": "react-scripts test", 42 | "eject": "react-scripts eject", 43 | "pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json}\"" 44 | }, 45 | "eslintConfig": { 46 | "extends": [ 47 | "react-app", 48 | "react-app/jest" 49 | ] 50 | }, 51 | "browserslist": { 52 | "production": [ 53 | ">0.2%", 54 | "not dead", 55 | "not op_mini all" 56 | ], 57 | "development": [ 58 | "last 1 chrome version", 59 | "last 1 firefox version", 60 | "last 1 safari version" 61 | ] 62 | }, 63 | "devDependencies": { 64 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 65 | "generate-license-file": "^3.4.0", 66 | "http-proxy-middleware": "^2.0.6", 67 | "prettier": "^3.1.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /html/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /html/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | PuppyGraph 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /html/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppygraph/puppygraph-query/55919429e8be72b744eb3f7e7eecb526dcb990e6/html/public/logo.png -------------------------------------------------------------------------------- /html/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "logo.png", 7 | "type": "image/png" 8 | } 9 | ], 10 | "start_url": ".", 11 | "display": "standalone", 12 | "theme_color": "#000000", 13 | "background_color": "#ffffff" 14 | } 15 | -------------------------------------------------------------------------------- /html/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: / 4 | -------------------------------------------------------------------------------- /html/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /html/src/App.js: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { Routes, Route, Navigate } from "react-router-dom"; 3 | import Login from "./screens/Login"; 4 | import SidebarLayout from "./components/navigation/SidebarLayout"; 5 | import { AuthContext } from "./AuthContext"; 6 | import { useContext } from "react"; 7 | import ProtectedRoute from "./ProtectedRoute"; 8 | import QueryWidgetScreen from "./screens/QueryWidgetScreen"; 9 | 10 | function App() { 11 | const { loggedIn } = useContext(AuthContext); 12 | return ( 13 |
14 | 15 | 16 | : } 19 | /> 20 | : } 23 | /> 24 | 28 | 29 | 30 | } 31 | /> 32 | 33 |
34 | ); 35 | } 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /html/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import App from "./App"; 3 | 4 | test("renders learn react link", () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /html/src/AuthContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useCallback, useState } from "react"; 2 | import { useNavigate } from 'react-router-dom'; 3 | 4 | export const AuthContext = createContext(); 5 | 6 | export const AuthProvider = ({ children }) => { 7 | const navigate = useNavigate(); 8 | 9 | const [loggedIn, setLoggedIn] = useState(true); 10 | 11 | const login = useCallback((data) => { 12 | return fetch(window.location.origin + window.location.pathname + 'login', { 13 | method: "POST", 14 | headers: { 15 | "Content-Type": "application/json", 16 | }, 17 | body: JSON.stringify(data), 18 | }).then(response => { 19 | if (response.status !== 200) { 20 | console.log("login error: %s", response.status); 21 | } 22 | return response; 23 | }) 24 | .then(response => response.json()) 25 | .then(json => { 26 | if (json.code === 200) { 27 | setLoggedIn(true); 28 | navigate("/"); 29 | } 30 | return json; 31 | }) 32 | }, [setLoggedIn, navigate]); 33 | 34 | const logout = useCallback(() => { 35 | fetch(window.location.origin + window.location.pathname + 'logout', { 36 | method: "POST", 37 | }).then(response => response.json()) 38 | .then(json => { 39 | if (json.code === 200) { 40 | setLoggedIn(false); 41 | navigate("/login"); 42 | } 43 | return json; 44 | }) 45 | }, [navigate, setLoggedIn]); 46 | 47 | const fetchWithAuth = useCallback((path, options) => { 48 | const optionsWithAuth = { 49 | ...options, 50 | headers: { 51 | ...options?.headers, 52 | "Puppy-Authentication": "JWT", 53 | } 54 | } 55 | let baseUri = window.location.origin + window.location.pathname; 56 | if (path.startsWith("/")) { 57 | baseUri = baseUri.replace(/\/+$/, ""); 58 | } 59 | return fetch(baseUri + path, optionsWithAuth).then(response => { 60 | if (response.status === 401 || response.status === 403) { 61 | console.log("auth error: %s", response.status); 62 | setLoggedIn(false); 63 | navigate("/login"); 64 | throw new Error("login required"); 65 | } 66 | return response; 67 | }).catch((err) => { 68 | if (err.message !== "login required") { 69 | console.error(err); 70 | } 71 | }); 72 | }, [navigate]); 73 | 74 | const refreshToken = useCallback(() => { 75 | fetch(window.location.origin + window.location.pathname + 'refresh_token').catch(console.error); 76 | }, []) 77 | 78 | return ( 79 | 80 | {children} 81 | 82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /html/src/ProtectedRoute.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Navigate } from "react-router-dom"; 3 | import { AuthContext } from "./AuthContext"; 4 | 5 | const ProtectedRoute = ({ children }) => { 6 | const { loggedIn } = useContext(AuthContext); 7 | 8 | return loggedIn ? children : ; 9 | }; 10 | 11 | export default ProtectedRoute; 12 | -------------------------------------------------------------------------------- /html/src/StatusContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useEffect, useContext, useCallback } from "react"; 2 | import { AuthContext } from "./AuthContext"; 3 | 4 | export const StatusContext = createContext(); 5 | export const GRAPH_MODE_BIGGRAPH = "big_graph"; 6 | 7 | export const StatusProvider = ({ children }) => { 8 | const { loggedIn, fetchWithAuth, refreshToken } = useContext(AuthContext); 9 | const [status, setStatus] = useState(null); 10 | 11 | // Function to fetch data 12 | const fetchStatus = useCallback(() => { 13 | if (loggedIn) { 14 | fetchWithAuth("/status") 15 | .then(response => { 16 | if (!response) { 17 | throw new Error("No response") 18 | } 19 | if (response.status !== 200) { 20 | throw new Error(response.status); 21 | } 22 | return response; 23 | }) 24 | .then(response => response.json()) 25 | .then(json => setStatus(json)) 26 | .then(json => { 27 | setTimeout(() => refreshToken(), 1); 28 | return json; 29 | }) 30 | .catch(error => { 31 | if (error.message !== "No response") { 32 | console.error("Error fetching status:", error); 33 | } 34 | }); 35 | } 36 | }, [loggedIn, fetchWithAuth, refreshToken]); 37 | 38 | useEffect(() => { 39 | fetchStatus(); 40 | }, [fetchStatus]); 41 | 42 | const contextValue = { 43 | status: status, 44 | refreshStatus: fetchStatus 45 | }; 46 | 47 | return ( 48 | 49 | {children} 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /html/src/components/common/connection/Gremlin.js: -------------------------------------------------------------------------------- 1 | import { createContext, useCallback, useContext, useEffect, useState } from "react"; 2 | import { AuthContext } from "../../../AuthContext"; 3 | 4 | export const GremlinContext = createContext(); 5 | 6 | export const GremlinProvider = ({ children }) => { 7 | const {fetchWithAuth} = useContext(AuthContext); 8 | const [rpcState, setRpcState] = useState("waiting"); 9 | const [queue, setQueue] = useState([]); 10 | 11 | const execute = useCallback(async (query) => { 12 | setRpcState("running"); 13 | console.info(`gremlin query: ${query}`); 14 | try { 15 | const response = await fetchWithAuth("/submit", { 16 | method: "POST", 17 | body: JSON.stringify({ 18 | query, 19 | }), 20 | }); 21 | let json = await response.json(); 22 | if (json["@type"] === "g:List") { 23 | json = json["@value"]; 24 | } 25 | if (response.status !== 200) { 26 | throw json; 27 | } 28 | return json; 29 | } catch (error) { 30 | console.log(JSON.stringify(error)); 31 | throw error; 32 | } finally { 33 | setRpcState("waiting"); 34 | } 35 | }, [fetchWithAuth]); 36 | 37 | const submit = useCallback((query) => { 38 | return new Promise(async (resolve, reject) => { 39 | setQueue((queue) => [...queue, { query, resolve, reject }]); 40 | }); 41 | }, []); 42 | 43 | const processQueue = useCallback(async () => { 44 | if (rpcState !== "waiting" || queue.length === 0) return; 45 | 46 | const { query, resolve, reject } = queue.shift(); 47 | setQueue(queue); 48 | 49 | try { 50 | const response = await execute(query); 51 | resolve(response); 52 | } catch (error) { 53 | reject(error); 54 | } 55 | }, [rpcState, queue, execute]); 56 | 57 | useEffect(() => { 58 | if (rpcState === "waiting" && queue.length > 0) { 59 | processQueue(); 60 | } 61 | }, [queue, rpcState, processQueue]); 62 | 63 | const queryElementData = useCallback(async (type, ids) => { 64 | try { 65 | const response = await fetchWithAuth('/ui-api/props', { 66 | method: 'POST', 67 | headers: { 68 | 'Content-Type': 'application/json', 69 | }, 70 | body: JSON.stringify({ type, ids }), 71 | }); 72 | let json = await response.json(); 73 | if (json["@type"] === "g:List") { 74 | json = json["@value"]; 75 | } 76 | if (response.status !== 200) { 77 | throw json; 78 | } 79 | return json; 80 | } catch (error) { 81 | console.error('Error querying props data:', error); 82 | throw error; 83 | } 84 | }, [fetchWithAuth]); 85 | 86 | return ( 87 | 88 | {children} 89 | 90 | ); 91 | }; 92 | -------------------------------------------------------------------------------- /html/src/components/common/vis/ForceGraph.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | import { manyBodyFast } from "./ManyBodyFast"; 3 | 4 | export const forceSimulate = ( 5 | nodes, 6 | links, 7 | options = {}, 8 | onTick = () => {} 9 | ) => { 10 | const gap = options.gap 11 | ? options.gap 12 | : options.layout === "radial" 13 | ? getRadialGap(nodes) 14 | : options.layout === "vertical" 15 | ? getVerticalGap(nodes) 16 | : 100; 17 | let maxPathIndex = 0; 18 | for (const node of nodes) { 19 | if (node.pathIndex > maxPathIndex) { 20 | maxPathIndex = node.pathIndex; 21 | } 22 | } 23 | 24 | const simulation = d3 25 | .forceSimulation(nodes) 26 | .alphaMin(0.05) 27 | .alphaDecay(0.03) 28 | .force( 29 | "charge", 30 | options.layout === "force" 31 | ? manyBodyFast().strength(-150) 32 | : options.layout === "radial" 33 | ? manyBodyFast().strength(-250) 34 | : null 35 | ) 36 | .force( 37 | "collide", 38 | options.layout === "vertical" ? d3.forceCollide(() => 15) : null 39 | ) 40 | .force( 41 | "link", 42 | options.layout === "vertical" 43 | ? d3 44 | .forceLink() 45 | .links(links) 46 | .distance(() => gap) 47 | .strength(() => 0.01) 48 | : d3 49 | .forceLink() 50 | .links(links) 51 | .distance(() => gap) 52 | ) 53 | .force( 54 | "center", 55 | options.layout === "force" ? d3.forceCenter(0, 0).strength(0.01) : null 56 | ) 57 | .force( 58 | "radial", 59 | options.layout === "radial" 60 | ? d3 61 | .forceRadial() 62 | .radius((d) => (d.pathIndex || 0) * gap) 63 | .strength((d) => 0.8) 64 | : null 65 | ) 66 | .force( 67 | "vertical", 68 | options.layout === "vertical" 69 | ? d3 70 | .forceX() 71 | .x( 72 | (d) => 73 | (d.pathIndex === null || d.pathIndex === undefined 74 | ? 0 75 | : d.pathIndex - maxPathIndex / 2) * gap 76 | ) 77 | .strength((d) => 0.8) 78 | : null 79 | ); 80 | if (onTick) { 81 | simulation.on("tick", () => onTick()); 82 | } 83 | return new Promise((resolve, _) => { 84 | simulation.on("end", () => { 85 | resolve(); 86 | }); 87 | }); 88 | }; 89 | 90 | const getRadialGap = (nodes) => { 91 | let radialGap = 0; 92 | const indexCnt = Array(100).fill(0); 93 | for (let node of nodes) { 94 | const i = node.pathIndex || 0; 95 | if (i >= 0 && i < 99) { 96 | indexCnt[i]++; 97 | } 98 | } 99 | for (let i = 0; i < 100; i++) { 100 | if (indexCnt[i] / (i + 1) > radialGap) { 101 | radialGap = indexCnt[i] / (i + 1); 102 | } 103 | } 104 | if (radialGap < 150) { 105 | radialGap = 150; 106 | } 107 | return radialGap; 108 | }; 109 | 110 | const getVerticalGap = (nodes) => { 111 | let verticalGap = 0; 112 | const indexCnt = Array(100).fill(0); 113 | for (let node of nodes) { 114 | const i = node.pathIndex || 0; 115 | if (i >= 0 && i < 99) { 116 | indexCnt[i]++; 117 | } 118 | } 119 | for (let i = 0; i < 100; i++) { 120 | if (indexCnt[i] / 2 > verticalGap) { 121 | verticalGap = indexCnt[i] / 2; 122 | } 123 | } 124 | if (verticalGap < 200) { 125 | verticalGap = 200; 126 | } 127 | return verticalGap; 128 | }; 129 | -------------------------------------------------------------------------------- /html/src/components/common/vis/ManyBodyFast.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import { quadtree } from "d3-quadtree"; 4 | 5 | const jiggle = (random) => { 6 | return (random() - 0.5) * 1e-6; 7 | }; 8 | 9 | export const manyBodyFast = function () { 10 | var nodes, 11 | node, 12 | random, 13 | alpha, 14 | strength = () => 30, 15 | strengths, 16 | distanceMin2 = 1, 17 | distanceMax2 = Infinity, 18 | theta2 = 0.81; 19 | 20 | function force(_) { 21 | var i, 22 | n = nodes.length, 23 | tree = quadtree( 24 | nodes, 25 | (d) => d.x, 26 | (d) => d.y 27 | ).visitAfter(accumulate); 28 | for (alpha = _, i = 0; i < n; ++i) { 29 | if (nodes[i].fx && nodes[i].fy) continue; 30 | (node = nodes[i]), tree.visit(apply); 31 | } 32 | } 33 | 34 | function initialize() { 35 | if (!nodes) return; 36 | var i, 37 | n = nodes.length, 38 | node; 39 | strengths = new Array(n); 40 | for (i = 0; i < n; ++i) 41 | (node = nodes[i]), (strengths[node.index] = +strength(node, i, nodes)); 42 | } 43 | 44 | function accumulate(quad) { 45 | var strength = 0, 46 | q, 47 | c, 48 | weight = 0, 49 | x, 50 | y, 51 | i; 52 | 53 | // For internal nodes, accumulate forces from child quadrants. 54 | if (quad.length) { 55 | for (x = y = i = 0; i < 4; ++i) { 56 | if ((q = quad[i]) && (c = Math.abs(q.value))) { 57 | (strength += q.value), (weight += c), (x += c * q.x), (y += c * q.y); 58 | } 59 | } 60 | quad.x = x / weight; 61 | quad.y = y / weight; 62 | } 63 | 64 | // For leaf nodes, accumulate forces from coincident quadrants. 65 | else { 66 | q = quad; 67 | q.x = q.data.x; 68 | q.y = q.data.y; 69 | do strength += strengths[q.data.index]; 70 | while ((q = q.next)); 71 | } 72 | 73 | quad.value = strength; 74 | } 75 | 76 | function apply(quad, x1, _, x2) { 77 | if (!quad.value) return true; 78 | 79 | var x = quad.x - node.x, 80 | y = quad.y - node.y, 81 | w = x2 - x1, 82 | l = x * x + y * y; 83 | 84 | // Apply the Barnes-Hut approximation if possible. 85 | // Limit forces for very close nodes; randomize direction if coincident. 86 | if ((w * w) / theta2 < l) { 87 | if (l < distanceMax2) { 88 | if (x === 0) (x = jiggle(random)), (l += x * x); 89 | if (y === 0) (y = jiggle(random)), (l += y * y); 90 | if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); 91 | node.vx += (x * quad.value * alpha) / l; 92 | node.vy += (y * quad.value * alpha) / l; 93 | } 94 | return true; 95 | } 96 | 97 | // Otherwise, process points directly. 98 | else if (quad.length || l >= distanceMax2) return; 99 | 100 | // Limit forces for very close nodes; randomize direction if coincident. 101 | if (quad.data !== node || quad.next) { 102 | if (x === 0) (x = jiggle(random)), (l += x * x); 103 | if (y === 0) (y = jiggle(random)), (l += y * y); 104 | if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); 105 | } 106 | 107 | do 108 | if (quad.data !== node) { 109 | w = (strengths[quad.data.index] * alpha) / l; 110 | node.vx += x * w; 111 | node.vy += y * w; 112 | } 113 | while ((quad = quad.next)); 114 | } 115 | 116 | force.initialize = function (_nodes, _random) { 117 | nodes = _nodes; 118 | random = _random; 119 | initialize(); 120 | }; 121 | 122 | force.strength = function (_) { 123 | return arguments.length 124 | ? ((strength = typeof _ === "function" ? _ : () => +_), 125 | initialize(), 126 | force) 127 | : strength; 128 | }; 129 | 130 | force.distanceMin = function (_) { 131 | return arguments.length 132 | ? ((distanceMin2 = _ * _), force) 133 | : Math.sqrt(distanceMin2); 134 | }; 135 | 136 | force.distanceMax = function (_) { 137 | return arguments.length 138 | ? ((distanceMax2 = _ * _), force) 139 | : Math.sqrt(distanceMax2); 140 | }; 141 | 142 | force.theta = function (_) { 143 | return arguments.length ? ((theta2 = _ * _), force) : Math.sqrt(theta2); 144 | }; 145 | 146 | return force; 147 | }; 148 | -------------------------------------------------------------------------------- /html/src/components/common/vis/canvas/DetailsPanel.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | 3 | export const DetailsPanel = ({ position, containerRef, children }) => { 4 | const [contentSize, setContentSize] = useState({ width: 0, height: 0 }); 5 | const dropdownRef = useRef(); 6 | 7 | useEffect(() => { 8 | if (dropdownRef.current) { 9 | const { offsetWidth, offsetHeight } = dropdownRef.current; 10 | setContentSize({ width: offsetWidth, height: offsetHeight }); 11 | } 12 | }, []); 13 | 14 | const calcPosition = (pos) => { 15 | const containerRect = containerRef.current.getBoundingClientRect(); 16 | let { x, y } = pos; 17 | if (x + contentSize.width > containerRect.width) { 18 | x = containerRect.width - contentSize.width; 19 | } 20 | if (y + contentSize.height > containerRect.height) { 21 | y = containerRect.height - contentSize.height; 22 | } 23 | 24 | return { top: y, left: x }; 25 | }; 26 | 27 | const positionWithinBoundary = calcPosition(position); 28 | const dropdownStyle = { 29 | top: `${positionWithinBoundary.top}px`, 30 | left: `${positionWithinBoundary.left}px`, 31 | }; 32 | 33 | return ( 34 |
39 | {children} 40 |
41 | ); 42 | }; 43 | 44 | export const DetailsList = ({ content }) => { 45 | if (content instanceof Map) { 46 | content = Object.fromEntries(content); 47 | } 48 | 49 | const renderValue = (value) => { 50 | if (value instanceof Map) { 51 | value = Object.fromEntries(value); 52 | } 53 | if (typeof value === "string" || typeof value === "number") { 54 | return value; 55 | } 56 | return JSON.stringify(value); 57 | }; 58 | 59 | return ( 60 |
61 | {Object.entries(content).map(([key, value], i) => ( 62 | 63 | 64 | {key} 65 | 66 | 67 | {renderValue(value)} 68 | 69 | 70 | ))} 71 |
72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /html/src/components/common/vis/canvas/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | 3 | const SearchBar = ({ loading, itemMap, onSearch }) => { 4 | const [searchTerm, setSearchTerm] = useState(""); 5 | const [suggestions, setSuggestions] = useState([]); 6 | const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1); 7 | const inputRef = useRef(null); 8 | 9 | const handleInputChange = (event) => { 10 | const value = event.target.value; 11 | setSearchTerm(value); 12 | }; 13 | 14 | useEffect(() => { 15 | // Generate suggestions based on the current search term 16 | const [property, propertyValue] = searchTerm.split("="); 17 | const filteredSuggestions = Array.from(itemMap.values()) 18 | .flatMap((item) => { 19 | const { id, label, properties: otherProps } = item; 20 | const primitiveProps = otherProps 21 | .entries() 22 | .filter( 23 | ([key, value]) => 24 | typeof key === "string" && typeof value !== "object" 25 | ); 26 | 27 | const propertySuggestions = [ 28 | { property: "id", value: id }, 29 | { property: "label", value: label }, 30 | ...primitiveProps.map(([key, value]) => ({ property: key, value })), 31 | ]; 32 | 33 | if (propertyValue === undefined) { 34 | // Suggest only property names if property is not provided 35 | return propertySuggestions 36 | .map(({ property }) => `${property}=`) 37 | .filter((prop) => prop.startsWith(property)); 38 | } else { 39 | return propertySuggestions 40 | .filter( 41 | ({ property: prop, value: val }) => 42 | prop === property && val.toString().startsWith(propertyValue) 43 | ) 44 | .map(({ value: val }) => `${property}=${val}`); 45 | } 46 | }) 47 | .sort((a, b) => { 48 | if (a.startsWith("id=") ^ b.startsWith("id=")) { 49 | if (a.startsWith("id=")) return -1; 50 | if (b.startsWith("id=")) return 1; 51 | } 52 | if (a.startsWith("label=") ^ b.startsWith("label=")) { 53 | if (a.startsWith("label=")) return -1; 54 | if (b.startsWith("label=")) return 1; 55 | } 56 | return a.localeCompare(b); 57 | }) 58 | .filter( 59 | (suggestion, index, self) => 60 | index === 0 || self[index - 1] !== suggestion 61 | ) // Remove duplicates 62 | .slice(0, 10); 63 | setSuggestions(filteredSuggestions); 64 | setSelectedSuggestionIndex(-1); 65 | }, [searchTerm, itemMap]); 66 | 67 | const doSearch = (term) => { 68 | const [property, propertyValue] = term.split("="); 69 | const matchedIds = []; 70 | if (propertyValue) { 71 | Array.from(itemMap.values()).forEach((item) => { 72 | const { id, label, properties: props } = item; 73 | props.set("id", id); 74 | props.set("label", label); 75 | if (props.get(property) === propertyValue) { 76 | matchedIds.push(id); 77 | } 78 | }); 79 | } 80 | onSearch && onSearch(matchedIds); 81 | }; 82 | 83 | const handleSuggestionClick = (suggestion) => { 84 | doSearch(suggestion); 85 | setSearchTerm(suggestion); 86 | }; 87 | 88 | const handleKeyDown = (event) => { 89 | if (event.key === "ArrowDown") { 90 | event.preventDefault(); 91 | setSelectedSuggestionIndex((prevIndex) => 92 | prevIndex === suggestions.length - 1 ? 0 : prevIndex + 1 93 | ); 94 | } else if (event.key === "ArrowUp") { 95 | event.preventDefault(); 96 | setSelectedSuggestionIndex((prevIndex) => 97 | prevIndex <= 0 ? suggestions.length - 1 : prevIndex - 1 98 | ); 99 | } else if (event.key === "Enter") { 100 | event.preventDefault(); 101 | let term = searchTerm; 102 | if (selectedSuggestionIndex !== -1) { 103 | term = suggestions[selectedSuggestionIndex]; 104 | } 105 | doSearch(term); 106 | setSearchTerm(term); 107 | setSuggestions([]); 108 | setSelectedSuggestionIndex(-1); 109 | } 110 | }; 111 | 112 | useEffect(() => { 113 | if (selectedSuggestionIndex !== -1) { 114 | inputRef.current.value = suggestions[selectedSuggestionIndex]; 115 | } 116 | }, [selectedSuggestionIndex, suggestions]); 117 | 118 | return ( 119 |
120 | 131 | {suggestions.length > 0 && ( 132 |
    133 | {suggestions.map((suggestion, index) => ( 134 |
  • handleSuggestionClick(suggestion)} 143 | > 144 | {suggestion} 145 |
  • 146 | ))} 147 |
148 | )} 149 |
150 | ); 151 | }; 152 | 153 | export default SearchBar; 154 | -------------------------------------------------------------------------------- /html/src/components/common/vis/canvas/Watermark.js: -------------------------------------------------------------------------------- 1 | import { Container, Text } from "@pixi/react"; 2 | import colors from "material-colors"; 3 | import { TextStyle } from "pixi.js"; 4 | 5 | const Watermark = ({ text, width, height }) => { 6 | return ( 7 | 8 | {text && ( 9 | 26 | )} 27 | {!text && ( 28 | 45 | )} 46 | {!text && ( 47 | 64 | )} 65 | 66 | ); 67 | }; 68 | 69 | export default Watermark; 70 | -------------------------------------------------------------------------------- /html/src/components/navigation/HeaderDisplay.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ProfileDropdown from './ProfileDropdown'; 3 | import SystemStatusButton from './SystemStatusButton'; 4 | 5 | const HeaderDisplay = ({ loggedIn, userNavigation, handleMenuItemClick }) => { 6 | return ( 7 |
8 | {loggedIn && ( 9 |
10 |
11 |
12 | 13 | {/* Separator */} 14 | 23 |
24 | )} 25 |
26 | ); 27 | } 28 | 29 | export default HeaderDisplay; 30 | -------------------------------------------------------------------------------- /html/src/components/navigation/ProfileDropdown.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Menu, Transition } from '@headlessui/react'; 3 | import { ChevronDownIcon } from "@heroicons/react/20/solid"; 4 | import PGIcon from "../../images/logo.png"; 5 | 6 | function ProfileDropdown({ userNavigation, handleMenuItemClick }) { 7 | function classNames(...classes) { 8 | return classes.filter(Boolean).join(' '); 9 | } 10 | 11 | return ( 12 | 13 | 14 | Open user menu 15 | 20 | 21 | 27 | 32 | 33 | 42 | 43 | {userNavigation.map((item) => ( 44 | 45 | {({ active }) => ( 46 |
handleMenuItemClick(e, item.name, item.href)} 49 | className={classNames(active ? "bg-gray-50" : "", "block cursor-pointer px-3 py-1 text-sm leading-6 text-gray-900")} 50 | > 51 | {item.name} 52 |
53 | )} 54 |
55 | ))} 56 |
57 |
58 |
59 | ); 60 | } 61 | 62 | export default ProfileDropdown; 63 | -------------------------------------------------------------------------------- /html/src/components/navigation/SidebarLayout.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { AuthContext } from "../../AuthContext"; 4 | import HeaderDisplay from "./HeaderDisplay"; 5 | 6 | const userNavigation = [ 7 | { name: "Sign out", href: "#" }, 8 | ]; 9 | 10 | export default function SidebarLayout() { 11 | const { loggedIn, logout } = useContext(AuthContext); 12 | 13 | const navigate = useNavigate(); 14 | 15 | const handleMenuItemClick = (e, itemName, itemHref) => { 16 | e.preventDefault(); 17 | if (itemName === "Sign out") { 18 | logout(); 19 | } 20 | // Check if the href is an absolute URL 21 | else if (/^(?:[a-z]+:)?\/\//i.test(itemHref)) { 22 | // It's an absolute URL, open it in a new tab 23 | window.open(itemHref, "_blank"); 24 | } else { 25 | // It's a relative URL, use navigate 26 | navigate(itemHref); 27 | } 28 | }; 29 | 30 | return ( 31 | <> 32 |
33 |
34 | 39 |
40 |
41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /html/src/components/navigation/SystemStatusButton.js: -------------------------------------------------------------------------------- 1 | import { 2 | ArrowDownIcon, 3 | ArrowUpIcon, 4 | EllipsisHorizontalIcon, 5 | } from "@heroicons/react/20/solid"; 6 | import { useContext, useEffect, useState } from 'react'; 7 | import { StatusContext } from '../../StatusContext'; 8 | 9 | function classNames(...classes) { 10 | return classes.filter(Boolean).join(" "); 11 | } 12 | 13 | const SystemStatusButton = () => { 14 | const { status, refreshStatus } = useContext(StatusContext); 15 | const [ systemStatus, setSystemStatus ] = useState("await"); 16 | const [ message, setMessage ] = useState(""); 17 | 18 | useEffect(() => { 19 | if (!status) { 20 | return; 21 | } 22 | if (!status.GremlinServer) { 23 | setSystemStatus("offline"); 24 | setMessage("Gremlin endpoint not set"); 25 | return; 26 | } 27 | if (status.GremlinHealthy === "Error") { 28 | setSystemStatus("offline"); 29 | setMessage("Gremlin server error"); 30 | return; 31 | } 32 | if (status.GremlinHealthy === "Empty") { 33 | setSystemStatus("await"); 34 | setMessage("Gremlin returns no vertices"); 35 | return; 36 | } 37 | setSystemStatus("online"); 38 | setMessage(status.GremlinServer); 39 | }, [status]); 40 | 41 | const getStatusColor = (status) => { 42 | if (status === "online") return "bg-green-100 text-green-800"; 43 | else if (status === "await") return "bg-yellow-100 text-yellow-800"; 44 | else return "bg-red-100 text-red-800"; 45 | }; 46 | 47 | const getStatusIcon = (status) => { 48 | if (status === "online") { 49 | return ( 50 |