diff --git a/Options.cmake b/Options.cmake index 463472f13e..b193b987b5 100644 --- a/Options.cmake +++ b/Options.cmake @@ -1,6 +1,6 @@ set(LGRAPH_VERSION_MAJOR 4) set(LGRAPH_VERSION_MINOR 0) -set(LGRAPH_VERSION_PATCH 0) +set(LGRAPH_VERSION_PATCH 1) # options option(ENABLE_WALL "Enable all compiler's warning messages." ON) diff --git a/deps/tugraph-db-client-java b/deps/tugraph-db-client-java index bf9a294fd0..fa17baa10c 160000 --- a/deps/tugraph-db-client-java +++ b/deps/tugraph-db-client-java @@ -1 +1 @@ -Subproject commit bf9a294fd0943e27b39faa995dc35b3dfc258c83 +Subproject commit fa17baa10c793e464a0113caa0c0cc9f121a8e78 diff --git a/docs/autogen/TuGraph-CPP-Procedure-API/Doxyfile b/docs/autogen/TuGraph-CPP-Procedure-API/Doxyfile index 7dbd326cc3..8dcea940ed 100644 --- a/docs/autogen/TuGraph-CPP-Procedure-API/Doxyfile +++ b/docs/autogen/TuGraph-CPP-Procedure-API/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "TuGraph Procedure API - C++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.0.0 +PROJECT_NUMBER = 4.0.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/docs/autogen/TuGraph-Python-Procedure-API/index.rst b/docs/autogen/TuGraph-Python-Procedure-API/index.rst index 81e0ec44df..4b8195b23d 100644 --- a/docs/autogen/TuGraph-Python-Procedure-API/index.rst +++ b/docs/autogen/TuGraph-Python-Procedure-API/index.rst @@ -5,7 +5,7 @@ TuGraph Procedure API - Python ==================================== -Version: 4.0.0 +Version: 4.0.1 Copyright (C) 2018-2022 Ant Group. diff --git a/docs/en-US/source/1.guide.md b/docs/en-US/source/1.guide.md index ff17ad72d3..aa01fa3936 100644 --- a/docs/en-US/source/1.guide.md +++ b/docs/en-US/source/1.guide.md @@ -51,22 +51,22 @@ ## TuGraph-Latest-Version | Description | File | Link | -|---------------------|-------------------------- ------------------|-------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ----------| -| CentOS7 installation package | tugraph-4.0.0-1.el7.x86_64.rpm | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph -4.0.0-1.el7.x86_64.rpm) | -| CentOS8 installation package | tugraph-4.0.0-1.el8.x86_64.rpm | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph -4.0.0-1.el8.x86_64.rpm) | -| Ubuntu18.04 installation package | tugraph-4.0.0-1.x86_64.deb | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph -4.0.0-1.x86_64.deb) | -| CentOS7 pre-installation image | tugraph-runtime-centos7-4.0.0.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph- runtime-centos7-4.0.0.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-runtime-centos7) | -| CentOS8 pre-installation image | tugraph-runtime-centos8-4.0.0.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph- runtime-centos8-4.0.0.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-runtime-centos8) | -| Ubuntu18.04 pre-installed image | tugraph-runtime-ubuntu18.04-4.0.0.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0. 0/tugraph-runtime-ubuntu18.04-4.0.0.tar) , [Visit](https://hub.docker.com/r/tugraph/tugraph-runtime-ubuntu18.04) | -| CentOS7 mini installation package | tugraph-mini-4.0.0-1.el7.x86_64.rpm | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0. 0/tugraph-mini-4.0.0-1.el7.x86_64.rpm) | -| CentOS8 mini installation package | tugraph-mini-4.0.0-1.el8.x86_64.rpm | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0. 0/tugraph-mini-4.0.0-1.el8.x86_64.rpm) | -| Ubuntu18.04 mini installation package | tugraph-mini-4.0.0-1.x86_64.deb | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0. 0/tugraph-mini-4.0.0-1.x86_64.deb) | -| CentOS7 mini pre-installation image | tugraph-mini-runtime-centos7-4.0.0.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0 /tugraph-mini-runtime-centos7-4.0.0.tar) , [Visit](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-centos7) | -| CentOS8 mini pre-installation image | tugraph-mini-runtime-centos8-4.0.0.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0 /tugraph-mini-runtime-centos8-4.0.0.tar) , [Visit](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-centos8) | -| Ubuntu18.04 mini pre-installation image | tugraph-mini-runtime-ubuntu18.04-4.0.0.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph -4.0.0/tugraph-mini-runtime-ubuntu18.04-4.0.0.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-ubuntu18.04) | -| CentOS7 compilation image | tugraph-compile-centos7-1.2.7.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile/tugraph-compile -centos7-1.2.7.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-compile-centos7) | -| CentOS8 compilation image | tugraph-compile-centos8-1.2.7.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile/tugraph-compile -centos8-1.2.7.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-compile-centos8) | -| Ubuntu18.04 compilation image | tugraph-compile-ubuntu18.04-1.2.7.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile /tugraph-compile-ubuntu18.04-1.2.7.tar) , [Visit](https://hub.docker.com/r/tugraph/tugraph-compile-ubuntu18.04) | +|---------------------|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| CentOS7 runtime package | tugraph-4.0.1-1.el7.x86_64.rpm | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-4.0.1-1.el7.x86_64.rpm) | +| CentOS8 runtime package | tugraph-4.0.1-1.el8.x86_64.rpm | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-4.0.1-1.el8.x86_64.rpm) | +| Ubuntu18.04 runtime package | tugraph-4.0.1-1.x86_64.deb | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-4.0.1-1.x86_64.deb) | +| CentOS7 runtime image | tugraph-runtime-centos7-4.0.1.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-runtime-centos7-4.0.1.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-runtime-centos7) | +| CentOS8 runtime image | tugraph-runtime-centos8-4.0.1.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-runtime-centos8-4.0.1.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-runtime-centos8) | +| Ubuntu18.04 runtime image | tugraph-runtime-ubuntu18.04-4.0.1.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-runtime-ubuntu18.04-4.0.1.tar) , [Visit](https://hub.docker.com/r/tugraph/tugraph-runtime-ubuntu18.04) | +| CentOS7 mini runtime package | tugraph-mini-4.0.1-1.el7.x86_64.rpm | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-4.0.1-1.el7.x86_64.rpm) | +| CentOS8 mini runtime package | tugraph-mini-4.0.1-1.el8.x86_64.rpm | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-4.0.1-1.el8.x86_64.rpm) | +| Ubuntu18.04 mini runtime package | tugraph-mini-4.0.1-1.x86_64.deb | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-4.0.1-1.x86_64.deb) | +| CentOS7 mini runtime image | tugraph-mini-runtime-centos7-4.0.1.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-runtime-centos7-4.0.1.tar) , [Visit](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-centos7) | +| CentOS8 mini runtime image | tugraph-mini-runtime-centos8-4.0.1.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-runtime-centos8-4.0.1.tar) , [Visit](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-centos8) | +| Ubuntu18.04 mini runtime image | tugraph-mini-runtime-ubuntu18.04-4.0.1.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-runtime-ubuntu18.04-4.0.1.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-ubuntu18.04) | +| CentOS7 compilation image | tugraph-compile-centos7-1.2.7.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile/tugraph-compile-centos7-1.2.7.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-compile-centos7) | +| CentOS8 compilation image | tugraph-compile-centos8-1.2.7.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile/tugraph-compile-centos8-1.2.7.tar), [Visit](https://hub.docker.com/r/tugraph/tugraph-compile-centos8) | +| Ubuntu18.04 compilation image | tugraph-compile-ubuntu18.04-1.2.7.tar | [Download](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile/tugraph-compile-ubuntu18.04-1.2.7.tar) , [Visit](https://hub.docker.com/r/tugraph/tugraph-compile-ubuntu18.04) | For the version update log, see: [Link](https://github.com/TuGraph-family/tugraph-db/blob/master/release/CHANGELOG_CN.md). diff --git a/docs/en-US/source/4.user-guide/1.tugraph-browser.md b/docs/en-US/source/4.user-guide/1.tugraph-browser.md index ff381abb01..c13f4e8b26 100644 --- a/docs/en-US/source/4.user-guide/1.tugraph-browser.md +++ b/docs/en-US/source/4.user-guide/1.tugraph-browser.md @@ -400,6 +400,139 @@ The Browser provides full-screen display for graph query results. Click the `Ful ![query-result-fullscreen](../../../images/browser/query-result-fullscreen.png) ![query-result-fullscreen-close](../../../images/browser/query-result-fullscreen-close.png) +#### 2.4.4.Graph Analysis + +Click the `Graph Analysis` button in the `Graph Project` tab in the `Graph Project` interface to display and analyze graph data on the canvas. The product provides `Query by Statement` and `Query by Configuration` to query and load data from the graph project to the canvas. It supports filtering, layout adjustment, and canvas operations on the canvas data. + +![graphanalysis-button](../../../images/browser/graphanalysis-button.png) + +As shown in the figure below, the graph analysis function mainly includes: 1. Operation bar: the main operation functions of graph analysis, including view switching, query filtering, layout style, and canvas operations; 2. Left sidebar: query, filter, and appearance operation area; 3. Canvas area: the area for displaying graph data, displaying vertex and edge data, supporting vertex expansion query, collapsing vertexs, fixing vertexs, deleting vertexs, and providing canvas graph data statistics; 4. Right sidebar: when a vertex or edge data is selected, the corresponding attribute information will be displayed; 5. Switch graph project: switch to different graph projects for analysis. + +![graphanalysis-operation-area](../../../images/browser/graphanalysis-operation-area.png) + +##### 2.4.4.1.Query by Statement + +In the `Query by Statement` function, users can enter query statements to query graph data and load the data to the canvas area for display. +- Syntax Description: TuGraph's query language and syntax documentation. +- Clear Canvas Data: If this button is not selected, the results of each query will be appended to the canvas area; if this button is selected, the canvas will be cleared before each query. + +![graphanalysis-queryfilter-query](../../../images/browser/graphanalysis-queryfilter-query.png) + +##### 2.4.4.2.Query by Configuration + +In the `Query by Configuration` function, users can select vertex types and enter attribute conditions to query graph data and load the data to the canvas area for display. +- Clear Canvas Data: If this button is not selected, the results of each query will be appended to the canvas area; if this button is selected, the canvas will be cleared before each query. + +![graphanalysis-queryfilter-configurequery](../../../images/browser/graphanalysis-queryfilter-configurequery.png) + +##### 2.4.4.3.Canvas Analysis + +In the `Canvas Analysis` function, users can perform operations and analysis on the vertex or edge data on the canvas, including: expanding queries by selecting vertexs, collapsing/expanding vertexs, fixing vertexs, clearing the canvas, lassoing, vertex/edge retrieval, and canvas legend. The most basic operation on the canvas is dragging vertex data. By selecting a vertex with the left mouse button and moving the mouse, the position of the vertex data can be changed. + +###### a.Expand Query + +Right-click on a vertex data in the `Canvas` area, a pop-up operation window will appear, move the mouse to `Expand Query`, and a secondary window will pop up. Click on the corresponding expansion degree to perform the query. +- First-degree Query: Bidirectional expansion of first-degree relationships. +- Second-degree Query: Bidirectional expansion of second-degree relationships. +- Third-degree Query: Bidirectional expansion of third-degree relationships. +- Custom Query: Expand based on selected conditions. + - Edge Direction: Set the direction of the expansion relationship, Bidirectional: includes outgoing edges and incoming edges, Outgoing: edges pointing outward from the selected starting vertex, Incoming: edges pointing to the selected starting vertex. + - Degree of Diffusion: The degree of expansion. + - Custom Return vertex Count: Configure the number of vertexs returned to the front end, default is 100, to prevent exceptions on the front end caused by super vertexs. + - Add Filter Group: Set the relationship type and attribute conditions of the expansion, multiple relationship types can be set. + +![graphanalysis-canvas-expand-before](../../../images/browser/graphanalysis-canvas-expand-before.png) + +![graphanalysis-canvas-expand-after](../../../images/browser/graphanalysis-canvas-expand-after.png) + +![graphanalysis-canvas-expand-custom](../../../images/browser/graphanalysis-canvas-expand-custom.png) + +###### b.Collapse/Expand Vertexs + +Right-click on a vertex data in the `Canvas` area, a pop-up operation window will appear, click on `Collapse Vertexs` to hide the first-degree relationship Vertexs of the selected Vertexs; right-click on the collapsed Vertex data again to perform Expand Vertexs operation to show the hidden first-degree relationship Vertexs. + +![graphanalysis-canvas-collapse](../../../images/browser/graphanalysis-canvas-collapse.png) + +![graphanalysis-canvas-expand](../../../images/browser/graphanalysis-canvas-expand.png) + +###### c.Fix Vertexs + +Right-click on a vertex data in the `Canvas` area, a pop-up operation window will appear, click on `Fix vertexs` to fix the selected vertexs in place. The fixed vertexs cannot be moved (including layout adjustment); right-click on the fixed vertex data again to perform `Unfix vertexs` operation to restore the normal operation of the selected. + +![graphanalysis-canvas-fixed](../../../images/browser/graphanalysis-canvas-fixed.png) + +![graphanalysis-canvas-unfix](../../../images/browser/graphanalysis-canvas-unfix.png) + +###### d.Delete Vertex + +Right-click on a vertex in the `Canvas` area to open the operation window. Click on `Delete Vertex` to remove the selected vertex from the canvas. + +![graphanalysis-canvas-delete](../../../images/browser/graphanalysis-canvas-delete.png) + +###### e.Clear Canvas + +Click on the `Clear Canvas` button in the `Toolbar` area to clear all data on the canvas. + +![图分析-画布分析-清空画布](../../../images/browser/graphanalysis-canvas-clear.png) + +###### f.Vertex/Edge Retrieval + +Select a vertex or edge in the `Vertex/Edge Retrieval` window and enter a keyword to perform a fuzzy search for attribute data on the canvas. After the search, you can locate the data position. + +![graphanalysis-canvas-retrieval](../../../images/browser/graphanalysis-canvas-retrieval.png) + +###### g.Canvas Legend + +In the legend area of the `Canvas` area, the vertex types in the canvas will be displayed. Click on a vertex type to select the corresponding vertex data. Click on the "More" button to display the statistics, which can be displayed in a list or chart format to show the number of vertexs or edges. + +![graphanalysis-canvas-legend-list](../../../images/browser/graphanalysis-canvas-legend-list.png) +![graphanalysis-canvas-legend-chart](../../../images/browser/graphanalysis-canvas-legend-chart.png) + +##### 2.4.4.4.Attribute Filter + +Click on the `Filter` button in the `Toolbar` area, and click on `Attribute Filter` in the `Sidebar` to perform filter and filtering. Users can select the vertex or edge types to be filtered, and set the corresponding attribute values. After retrieving the filtered group conditions, the corresponding vertex or edge data will be highlighted on the canvas. +- Please select the vertex/edge type: Select the vertex type or edge type to be searched. +- Attribute condition: Set the attribute conditions to be searched. Multiple groups can be set to take the union of the filtering results. +- Add filter group: Multiple filter conditions can be set to take the union of the filtering results. +- Reset: Clear the filter conditions. + +![graphanalysis-queryfilter-attributefilter](../../../images/browser/graphanalysis-queryfilter-attributefilter.png) + +##### 2.4.4.5.Statistical Filter + +Click on the Filter button in the Toolbar area, and click on Statistical Filter in the Sidebar to perform statistical filtering of canvas data. Users can select the vertex or edge types to be counted, and set the corresponding attribute values. The system will automatically group and count based on the selected vertex/edge types and attributes. The results can be displayed in chart and list formats. Clicking on the values in the chart or list area will highlight the corresponding data on the canvas. + +![graphanalysis-queryfilter-statisticalfilter](../../../images/browser/graphanalysis-queryfilter-statisticalfilter.png) +![graphanalysis-queryfilter-statisticalfilter-chartswitch](../../../images/browser/graphanalysis-queryfilter-statisticalfilter-chartswitch.png) + +##### 2.4.4.7.Layout Style + +Click on the `Layout` button in the `Toolbar` area, and select the corresponding layout method to rearrange the data on the canvas. It supports force-directed layout, concentric circle layout, circular layout, radial layout, Dagre layout, and grid layout. Each layout method has different layout parameters. After adjusting the parameters, the data on the canvas will be rearranged. + +![graphanalysis-style-layout-button](../../../images/browser/graphanalysis-style-layout-button.png) +![graphanalysis-style-layout-parameters](../../../images/browser/graphanalysis-style-layout-parameters.png) + +For detailed layout parameters, please refer to[AntV-G6](https://g6.antv.antgroup.com/api/graph-layout/guide). + +##### 2.4.4.6.Appearance Style + +Click on the `Appearance` button in the `Toolbar` area, and click on `Vertex Style` or `Edge Style` in the `Sidebar` to configure the appearance style. +- Vertex Style + - Apply to vertex type: Set the display style for the corresponding vertex type. Multiple vertex types can be configured simultaneously. + - Size: The display size for the corresponding vertex type. + - Color: The display color for the corresponding vertex type. + - Icon: The icon style for the corresponding vertex type. + - Display Text: The text content displayed for the corresponding vertex type, defaulting to id. + - Advanced Configuration: Mark the corresponding vertex data based on the set conditions. +- Edge Style + - Apply to Edge Type: Set the display style for the corresponding edge type, supports configuring the appearance of multiple edge types simultaneously. + - Color: The display color for the corresponding edge type. + - Width: The display width for the corresponding edge type. + - Display Text: The text content displayed for the corresponding edge type, not displayed by default. + - Advanced Configuration: Display the corresponding edge data based on the set conditions. + +![graphanalysis-style-appearance](../../../images/browser/graphanalysis-style-appearance.png) + ### 2.5.Console The `Console` provides a visualized account management and database information viewing function. It provides users with comprehensive account and role management functions, including adding, deleting, modifying, and disabling accounts and roles. In addition, it also provides users with convenient database information viewing function, allowing users to easily view the basic information and configuration information of the graph database. Basic information mainly includes version number, running time, CPP compilation version number, etc., while database configuration information includes port number, system function parameter configuration, etc. @@ -408,7 +541,7 @@ The `Console` provides a visualized account management and database information ##### 2.5.1.1.Account Management -##### 2.5.1.1.1.Add Account +##### a.Add Account Click the `Add` button in the `Account Management` interface to create a new account. Users need to enter the account name, account description, account password, and related roles. @@ -418,20 +551,20 @@ Click the `Add` button in the `Account Management` interface to create a new acc ![account-add-button](../../../images/browser/account-add-button.png) ![account-add](../../../images/browser/account-add.png) -##### 2.5.1.1.2.Edit Account +##### b.Edit Account Click the `Edit` button in the `Account Management` interface to edit the account description, account password, and related roles. ![account-edit](../../../images/browser/account-edit.png) -##### 2.5.1.1.3.Disable Account +##### c.Disable Account Click the `Disable` button in the `Account Management` interface to prevent the corresponding account from logging in and accessing. Click the `Enable` button to enable the corresponding account login and access permissions. ![account-disable](../../../images/browser/account-disable.png) ![account-enable](../../../images/browser/account-enable.png) -##### 2.5.1.1.4.Delete Account +##### d.Delete Account Click the `Delete` button in the `Account Management` interface to delete the corresponding account. @@ -439,7 +572,7 @@ Click the `Delete` button in the `Account Management` interface to delete the co #### 2.5.1.2.Role Management -##### 2.5.1.1.1.Add Role +##### a.Add Role Click the `Add` button in the `Role Management` interface to create a new role. Users need to enter the role name, role description, and graph permissions. @@ -455,13 +588,13 @@ Click the `Add` button in the `Role Management` interface to create a new role. ![role-add](../../../images/browser/role-add.png) -##### 2.5.1.1.2.Edit Role +##### b.Edit Role Click the `Edit` button in the `Role Management` interface to edit an existing role. Users can edit the role description and graph permissions. ![role-edit](../../../images/browser/role-edit.png) -##### 2.5.1.1.3.Disable Role +##### c.Disable Role Click the `Disable` button in the `Role Management` interface to disable the corresponding role. Click the `Enable` button to enable the corresponding role. After the role is disabled, the corresponding graph permissions of the role become invalid. @@ -471,7 +604,7 @@ Click the `Disable` button in the `Role Management` interface to disable the cor ![role-disable](../../../images/browser/role-disable.png) ![role-enable](../../../images/browser/role-enable.png) -##### 2.5.1.1.4.Delete Role +##### d.Delete Role Click the `Delete` button in the `Role Management` interface to delete the corresponding role. diff --git a/docs/en-US/source/5.developer-manual/3.server-tools/1.data-import.md b/docs/en-US/source/5.developer-manual/3.server-tools/1.data-import.md index 110735815f..e3dbf145f6 100644 --- a/docs/en-US/source/5.developer-manual/3.server-tools/1.data-import.md +++ b/docs/en-US/source/5.developer-manual/3.server-tools/1.data-import.md @@ -71,9 +71,11 @@ The configuration file consists of two parts: schema and files. The 'schema' par - type (required,,BOOL,INT8,INT16,INT32,INT64,DATE,DATETIME,FLOAT,DOUBLE,STRING,BLOB) - optional(optional,Indicates that the field can be configured or not configured) - index(optional,Whether the field needs to be indexed) - - unique(Optional, whether the field indexed and is of type unique) - - primary (Specify a property that uniquely identifies a point in the primary key field) - - constraints (Edge-only configuration, optional, array form, start and end labels, unconfigured or empty means unrestricted) + - unique(optional, whether the field indexed and is of type unique) + - primary (vertex-only configuration, specify a property that uniquely identifies a point in the primary key field) + - temproal (edge-only configuration, optional, specifies the timestamp field for storage tier sorting) + - temporal_field_order (edge-only configuration, optional, the default value is ASC, indicating the ascending order, or DESC, indicating the descending order) + - constraints (edge-only configuration, optional, array form, start and end labels, unconfigured or empty means unrestricted) - detach_property (optional, both Edge and Vertex can config,default is `false`. `true` means the property data is stored in detached model, which can reduce io read amplification in cases with less memory and much property data.) - files (Array) - path(required,string,The value can be a file path or a directory path. If it is a directory, all files in the directory will be imported. Ensure that they have the same schema) diff --git a/docs/en-US/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst b/docs/en-US/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst index ec80419e6a..c19a0d32b5 100644 --- a/docs/en-US/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst +++ b/docs/en-US/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst @@ -5,7 +5,7 @@ Python stored procedure API ==================================== -Version: 4.0.0 +Version: 4.0.1 Copyright (C) 2018-2022 Ant Group. diff --git a/docs/en-US/source/5.developer-manual/6.interface/3.procedure/Doxyfile b/docs/en-US/source/5.developer-manual/6.interface/3.procedure/Doxyfile index c8eaed1921..0ef611594e 100644 --- a/docs/en-US/source/5.developer-manual/6.interface/3.procedure/Doxyfile +++ b/docs/en-US/source/5.developer-manual/6.interface/3.procedure/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "TuGraph Procedure API - C++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.0.0 +PROJECT_NUMBER = 4.0.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/docs/images/browser/graphanalysis-button.png b/docs/images/browser/graphanalysis-button.png new file mode 100644 index 0000000000..dd8966d0d3 Binary files /dev/null and b/docs/images/browser/graphanalysis-button.png differ diff --git a/docs/images/browser/graphanalysis-canvas-clear.png b/docs/images/browser/graphanalysis-canvas-clear.png new file mode 100644 index 0000000000..ee5d3c92a9 Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-clear.png differ diff --git a/docs/images/browser/graphanalysis-canvas-collapse.png b/docs/images/browser/graphanalysis-canvas-collapse.png new file mode 100644 index 0000000000..e053061834 Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-collapse.png differ diff --git a/docs/images/browser/graphanalysis-canvas-delete.png b/docs/images/browser/graphanalysis-canvas-delete.png new file mode 100644 index 0000000000..5aefa20c43 Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-delete.png differ diff --git a/docs/images/browser/graphanalysis-canvas-expand-after.png b/docs/images/browser/graphanalysis-canvas-expand-after.png new file mode 100644 index 0000000000..371efb6e6b Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-expand-after.png differ diff --git a/docs/images/browser/graphanalysis-canvas-expand-before.png b/docs/images/browser/graphanalysis-canvas-expand-before.png new file mode 100644 index 0000000000..80e74174af Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-expand-before.png differ diff --git a/docs/images/browser/graphanalysis-canvas-expand-custom.png b/docs/images/browser/graphanalysis-canvas-expand-custom.png new file mode 100644 index 0000000000..bed0a73705 Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-expand-custom.png differ diff --git a/docs/images/browser/graphanalysis-canvas-expand.png b/docs/images/browser/graphanalysis-canvas-expand.png new file mode 100644 index 0000000000..75b0caf2b9 Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-expand.png differ diff --git a/docs/images/browser/graphanalysis-canvas-fixed.png b/docs/images/browser/graphanalysis-canvas-fixed.png new file mode 100644 index 0000000000..aac4a18573 Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-fixed.png differ diff --git a/docs/images/browser/graphanalysis-canvas-lasso.png b/docs/images/browser/graphanalysis-canvas-lasso.png new file mode 100644 index 0000000000..53f743925b Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-lasso.png differ diff --git a/docs/images/browser/graphanalysis-canvas-legend-chart.png b/docs/images/browser/graphanalysis-canvas-legend-chart.png new file mode 100644 index 0000000000..0ddb63b926 Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-legend-chart.png differ diff --git a/docs/images/browser/graphanalysis-canvas-legend-list.png b/docs/images/browser/graphanalysis-canvas-legend-list.png new file mode 100644 index 0000000000..07d0bf0a4c Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-legend-list.png differ diff --git a/docs/images/browser/graphanalysis-canvas-retrieval.png b/docs/images/browser/graphanalysis-canvas-retrieval.png new file mode 100644 index 0000000000..57eafece0e Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-retrieval.png differ diff --git a/docs/images/browser/graphanalysis-canvas-unfix.png b/docs/images/browser/graphanalysis-canvas-unfix.png new file mode 100644 index 0000000000..80fa2c8b72 Binary files /dev/null and b/docs/images/browser/graphanalysis-canvas-unfix.png differ diff --git a/docs/images/browser/graphanalysis-operation-area.png b/docs/images/browser/graphanalysis-operation-area.png new file mode 100644 index 0000000000..b251837e26 Binary files /dev/null and b/docs/images/browser/graphanalysis-operation-area.png differ diff --git a/docs/images/browser/graphanalysis-queryfilter-attributefilter.png b/docs/images/browser/graphanalysis-queryfilter-attributefilter.png new file mode 100644 index 0000000000..8883301d09 Binary files /dev/null and b/docs/images/browser/graphanalysis-queryfilter-attributefilter.png differ diff --git a/docs/images/browser/graphanalysis-queryfilter-configurequery.png b/docs/images/browser/graphanalysis-queryfilter-configurequery.png new file mode 100644 index 0000000000..d294a8616b Binary files /dev/null and b/docs/images/browser/graphanalysis-queryfilter-configurequery.png differ diff --git a/docs/images/browser/graphanalysis-queryfilter-query.png b/docs/images/browser/graphanalysis-queryfilter-query.png new file mode 100644 index 0000000000..b745e4d5c3 Binary files /dev/null and b/docs/images/browser/graphanalysis-queryfilter-query.png differ diff --git a/docs/images/browser/graphanalysis-queryfilter-statisticalfilter-chartswitch.png b/docs/images/browser/graphanalysis-queryfilter-statisticalfilter-chartswitch.png new file mode 100644 index 0000000000..afbaf3bfd5 Binary files /dev/null and b/docs/images/browser/graphanalysis-queryfilter-statisticalfilter-chartswitch.png differ diff --git a/docs/images/browser/graphanalysis-queryfilter-statisticalfilter.png b/docs/images/browser/graphanalysis-queryfilter-statisticalfilter.png new file mode 100644 index 0000000000..7c2e3b635e Binary files /dev/null and b/docs/images/browser/graphanalysis-queryfilter-statisticalfilter.png differ diff --git a/docs/images/browser/graphanalysis-style-appearance-Button.png b/docs/images/browser/graphanalysis-style-appearance-Button.png new file mode 100644 index 0000000000..dab00330b7 Binary files /dev/null and b/docs/images/browser/graphanalysis-style-appearance-Button.png differ diff --git a/docs/images/browser/graphanalysis-style-appearance.png b/docs/images/browser/graphanalysis-style-appearance.png new file mode 100644 index 0000000000..330ad3128e Binary files /dev/null and b/docs/images/browser/graphanalysis-style-appearance.png differ diff --git a/docs/images/browser/graphanalysis-style-layout-button.png b/docs/images/browser/graphanalysis-style-layout-button.png new file mode 100644 index 0000000000..49516d6427 Binary files /dev/null and b/docs/images/browser/graphanalysis-style-layout-button.png differ diff --git a/docs/images/browser/graphanalysis-style-layout-parameters.png b/docs/images/browser/graphanalysis-style-layout-parameters.png new file mode 100644 index 0000000000..d02772bfd9 Binary files /dev/null and b/docs/images/browser/graphanalysis-style-layout-parameters.png differ diff --git a/docs/zh-CN/source/1.guide.md b/docs/zh-CN/source/1.guide.md index ef410f2395..0716864fcd 100644 --- a/docs/zh-CN/source/1.guide.md +++ b/docs/zh-CN/source/1.guide.md @@ -54,18 +54,18 @@ | 描述 | 文件 | 链接 | |---------------------|--------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| CentOS7 安装包 | tugraph-4.0.0-1.el7.x86_64.rpm | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-4.0.0-1.el7.x86_64.rpm) | -| CentOS8 安装包 | tugraph-4.0.0-1.el8.x86_64.rpm | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-4.0.0-1.el8.x86_64.rpm) | -| Ubuntu18.04 安装包 | tugraph-4.0.0-1.x86_64.deb | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-4.0.0-1.x86_64.deb) | -| CentOS7 预安装镜像 | tugraph-runtime-centos7-4.0.0.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-runtime-centos7-4.0.0.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-runtime-centos7) | -| CentOS8 预安装镜像 | tugraph-runtime-centos8-4.0.0.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-runtime-centos8-4.0.0.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-runtime-centos8) | -| Ubuntu18.04 预安装镜像 | tugraph-runtime-ubuntu18.04-4.0.0.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-runtime-ubuntu18.04-4.0.0.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-runtime-ubuntu18.04) | -| CentOS7 精简安装包 | tugraph-mini-4.0.0-1.el7.x86_64.rpm | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-mini-4.0.0-1.el7.x86_64.rpm) | -| CentOS8 精简安装包 | tugraph-mini-4.0.0-1.el8.x86_64.rpm | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-mini-4.0.0-1.el8.x86_64.rpm) | -| Ubuntu18.04 精简安装包 | tugraph-mini-4.0.0-1.x86_64.deb | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-mini-4.0.0-1.x86_64.deb) | -| CentOS7 精简预安装镜像 | tugraph-mini-runtime-centos7-4.0.0.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-mini-runtime-centos7-4.0.0.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-centos7) | -| CentOS8 精简预安装镜像 | tugraph-mini-runtime-centos8-4.0.0.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-mini-runtime-centos8-4.0.0.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-centos8) | -| Ubuntu18.04 精简预安装镜像 | tugraph-mini-runtime-ubuntu18.04-4.0.0.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.0/tugraph-mini-runtime-ubuntu18.04-4.0.0.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-ubuntu18.04) | +| CentOS7 安装包 | tugraph-4.0.1-1.el7.x86_64.rpm | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-4.0.1-1.el7.x86_64.rpm) | +| CentOS8 安装包 | tugraph-4.0.1-1.el8.x86_64.rpm | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-4.0.1-1.el8.x86_64.rpm) | +| Ubuntu18.04 安装包 | tugraph-4.0.1-1.x86_64.deb | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-4.0.1-1.x86_64.deb) | +| CentOS7 预安装镜像 | tugraph-runtime-centos7-4.0.1.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-runtime-centos7-4.0.1.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-runtime-centos7) | +| CentOS8 预安装镜像 | tugraph-runtime-centos8-4.0.1.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-runtime-centos8-4.0.1.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-runtime-centos8) | +| Ubuntu18.04 预安装镜像 | tugraph-runtime-ubuntu18.04-4.0.1.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-runtime-ubuntu18.04-4.0.1.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-runtime-ubuntu18.04) | +| CentOS7 精简安装包 | tugraph-mini-4.0.1-1.el7.x86_64.rpm | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-4.0.1-1.el7.x86_64.rpm) | +| CentOS8 精简安装包 | tugraph-mini-4.0.1-1.el8.x86_64.rpm | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-4.0.1-1.el8.x86_64.rpm) | +| Ubuntu18.04 精简安装包 | tugraph-mini-4.0.1-1.x86_64.deb | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-4.0.1-1.x86_64.deb) | +| CentOS7 精简预安装镜像 | tugraph-mini-runtime-centos7-4.0.1.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-runtime-centos7-4.0.1.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-centos7) | +| CentOS8 精简预安装镜像 | tugraph-mini-runtime-centos8-4.0.1.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-runtime-centos8-4.0.1.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-centos8) | +| Ubuntu18.04 精简预安装镜像 | tugraph-mini-runtime-ubuntu18.04-4.0.1.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-4.0.1/tugraph-mini-runtime-ubuntu18.04-4.0.1.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-mini-runtime-ubuntu18.04) | | CentOS7 编译镜像 | tugraph-compile-centos7-1.2.7.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile/tugraph-compile-centos7-1.2.7.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-compile-centos7) | | CentOS8 编译镜像 | tugraph-compile-centos8-1.2.7.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile/tugraph-compile-centos8-1.2.7.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-compile-centos8) | | Ubuntu18.04 编译镜像 | tugraph-compile-ubuntu18.04-1.2.7.tar | [下载](https://tugraph-web.oss-cn-beijing.aliyuncs.com/tugraph/tugraph-docker-compile/tugraph-compile-ubuntu18.04-1.2.7.tar) 、[访问](https://hub.docker.com/r/tugraph/tugraph-compile-ubuntu18.04) | diff --git a/docs/zh-CN/source/4.user-guide/1.tugraph-browser.md b/docs/zh-CN/source/4.user-guide/1.tugraph-browser.md index 37ef7b8781..4b6ec8c40e 100644 --- a/docs/zh-CN/source/4.user-guide/1.tugraph-browser.md +++ b/docs/zh-CN/source/4.user-guide/1.tugraph-browser.md @@ -399,6 +399,139 @@ Browser提供全屏方式展示图查询结果,点击`全屏显示`按钮全 ![全屏展示](../../../images/browser/query-result-fullscreen.png) ![全屏展示-按钮](../../../images/browser/query-result-fullscreen-close.png) +#### 2.4.4.图分析 + +在`图项目`界面点击图项目选项卡中的`图分析`按钮,可以在画布中展示和分析图数据,产品提供`语句查询`和`配置查询`将图项目中的数据查询并加载至画布,支持画布数据的筛选、布局样式调整以及画布操作。 + +![图分析-按钮](../../../images/browser/graphanalysis-button.png) + +如下图所示,图分析功能主要包括:1、操作栏:图分析主要操作功能,包括视图切换、查询过滤、布局样式以及画布操作;2、左边栏:查询、筛选、外观功能操作区域;3、画布区域:展示图数据的区域,展示点和边数据,支持点扩展查询、收起节点、固定节点、删除节点,提供画布图数据统计等功能;4:右边栏:选中一个点数据或边数据后,会展示对应的属性信息;5、切换图项目:切换不同的图项目进行分析。 + +![图分析-操作区域](../../../images/browser/graphanalysis-operation-area.png) + +##### 2.4.4.1.语句查询 + +在`语句查询`功能中,用户可以输入查询语句来查询图数据,并加载数据至画布区域进行展示。 +- 语法说明:TuGraph的查询语言及语法说明文档。 +- 清空画布数据:未选择此按钮,每次执行查询的结果会追加至画布区域;选择此按钮,每次执行查询前会先清空画布。 + +![图分析-语句查询](../../../images/browser/graphanalysis-queryfilter-query.png) + +##### 2.4.4.2.模板查询 + +在`模板查询`功能中,用户可以选择节点类型和输入属性条件来查询图数据,并加载数据至画布区域进行展示。 +- 清空画布数据:未选择此按钮,每次执行查询的结果会追加至画布区域;选择此按钮,每次执行查询前会先清空画布。 + +![图分析-模板查询](../../../images/browser/graphanalysis-queryfilter-configurequery.png) + +##### 2.4.4.3.画布分析 + +在`画布分析`功能中,用户可以对画布中的节点或边数据进行操作和分析,主要包括:选中节点进行扩展查询、收起/展开节点、固定节点,清空画布,套索,点/边检索,画布图例等。画布上的最基础操作是拖拽点数据,鼠标左键选住一个节点并移动鼠标,可以完成点数据位置的移动。 + +###### a.扩展查询 + +在`画布`区域右键点击一个节点数据,弹出操作悬窗,鼠标移至`扩展查询`处弹出二级悬窗,点击对应的扩展度数进行查询。 +- 一度查询:双向扩展一度关系。 +- 二度查询:双向扩展二度关系。 +- 三度查询:双向扩展三度关系。 +- 自定义查询:根据选择的条件进行扩展。 + -边的方向:设置扩展关系的方向,双向:包含出边和入边,出边:选中起点向外指向的边,入边:指向选中起点的边。 + -扩散度数:扩展的度数。 + -自定义返回节点数量:配置返回至前端的节点数量,默认为100,防止遇到超级节点导致前端异常的情况。 + -添加筛选组:设置扩展的关系类型和关系的属性条件,可以设置多组关系类型。 + +![图分析-画布分析-扩展查询-按钮](../../../images/browser/graphanalysis-canvas-expand-before.png) + +![图分析-画布分析-扩展查询-查询后](../../../images/browser/graphanalysis-canvas-expand-after.png) + +![图分析-画布分析-扩展查询-自定义查询](../../../images/browser/graphanalysis-canvas-expand-custom.png) + +###### b.收起/展开节点 + +在`画布`区域右键点击一个节点数据,弹出操作悬窗,点击`收起节点`会隐藏所选节点的一度关系节点;再次右键已`收起节点`的点数据可以进行`展开节点`操作,展示已隐藏的一度关系节点。 + +![图分析-画布分析-收起节点](../../../images/browser/graphanalysis-canvas-collapse.png) + +![图分析-画布分析-展开节点](../../../images/browser/graphanalysis-canvas-expand.png) + +###### c.固定节点 + +在`画布`区域右键点击一个节点数据,弹出操作悬窗,点击`固定节点`会将所选节点固定在原处,固定后的节点不可移动(包括重新布局);再次右键已`固定节点`的点数据可以进行`解除固定`操作,恢复所选节点的正常操作。 + +![图分析-画布分析-固定节点](../../../images/browser/graphanalysis-canvas-fixed.png) + +![图分析-画布分析-解除固定](../../../images/browser/graphanalysis-canvas-unfix.png) + +###### d.删除节点 + +在`画布`区域右键点击一个节点数据,弹出操作悬窗,点击`删除节点`会将所选节点移出画布。 + +![图分析-画布分析-删除节点](../../../images/browser/graphanalysis-canvas-delete.png) + +###### e.清空画布 + +在`操作栏`区域点击`清空画布`按钮,会清除所有画布中的数据。 + +![图分析-画布分析-清空画布](../../../images/browser/graphanalysis-canvas-clear.png) + +###### f.点/边检索 + +在`点/边检索`窗口选择点或边,并输入关键字,会模糊检索画布中的属性数据,检索后可定位至数据位置。 + +![图分析-画布分析-检索](../../../images/browser/graphanalysis-canvas-retrieval.png) + +###### g.画布图例 + +在`画布`区域的图例位置,会展示画布中的点类型,点击点类型可以选中对应的点数据,点击更多按钮可以展示统计情况,支持列表或图表方式展示点或边的数量。 + +![图分析-画布分析-列表](../../../images/browser/graphanalysis-canvas-legend-list.png) +![图分析-画布分析-图表](../../../images/browser/graphanalysis-canvas-legend-chart.png) + +##### 2.4.4.4.属性筛选 + +在`操作栏`区域点击`筛选`按钮,在`左边栏`点击`属性筛选`进行筛选过滤。用户可以选择要筛选的点或边类型,以及对应的属性值进行设置,检索到筛选组条件的数据后会在画布上高亮选中对应的点或边数据。 +- 请选择点/边类型:选择需要检索的点类型或边类型。 +- 属性条件:设置需要检索的属性条件,可以设置多组,取并集筛选结果。 +- 添加筛选组:可以多组筛选条件,取并集筛选结果。 +- 重置:可以清空筛选条件。 + +![图分析-筛选-属性筛选](../../../images/browser/graphanalysis-queryfilter-attributefilter.png) + +##### 2.4.4.5.统计筛选 + +在`操作栏`区域点击`筛选`按钮,在`左边栏`点击`统计筛选`进行画布数据统计。用户可以选择要统计的点或边类型,以及对应的属性值进行设置,系统会自动根据用户选择的点/边类型和属性进行分组统计,支持按照图表和列表两种方式展示结果,同时点击图表或列表区域的数值可以高亮选中画布中的数据。 + +![图分析-筛选-统计筛选](../../../images/browser/graphanalysis-queryfilter-statisticalfilter.png) +![图分析-统计筛选-图表切换](../../../images/browser/graphanalysis-queryfilter-statisticalfilter-chartswitch.png) + +##### 2.4.4.7.布局样式 + +在`操作栏`区域点击`布局`按钮,选择对应的布局方式会将画布中的数据进行重新排布,支持力导向布局、同心圆布局、圆形布局、辐射布局、Dagre布局以及网格布局,每种布局方式均有不同布局参数,调整参数后画布中的数据会进行重新排布。 + +![图分析-布局样式-按钮](../../../images/browser/graphanalysis-style-layout-button.png) +![图分析-布局样式-布局参数](../../../images/browser/graphanalysis-style-layout-parameters.png) + +详细布局参数可参考[AntV-G6](https://g6.antv.antgroup.com/api/graph-layout/guide)。 + +##### 2.4.4.6.外观样式 + +在`操作栏`区域点击`外观`按钮,在`左边栏`点击`点样式`或`边样式`进行外观样式配置。 +- 点样式 + - 应用点类型:设置对应点类型的展示样式,支持同时配置多个点类型外观。 + - 大小:对应点类型的展示大小。 + - 颜色:对应点类型的展示颜色。 + - 图标:对应点类型的图标样式。 + - 显示文本:对应点类型显示的文本内容,默认为id。 + - 高级配置:根据设置的条件标记对应的点数据。 +- 边样式 + - 应用边类型:设置对应边类型的展示样式,支持同时配置多个边类型外观。 + - 颜色:对应点类型的展示颜色。 + - 边宽:对应边类型的展示宽度。 + - 显示文本:对应边类型显示的文本内容,默认不显示。 + - 高级配置:根据设置的条件按颜色展示对应的边数据。 + +![图分析-外观](../../../images/browser/graphanalysis-style-appearance.png) + ### 2.5.控制台 `控制台`提供可视化的的账户管理和数据库信息查看功能,它为用户提供了全面的账户和角色管理功能,包括账户的增删改查以及禁用,角色的增删改查以及禁用。此外,它也为用户提供了便捷的数据库信息查看功能,让用户可以轻松地查看图数据库的基础信息和配置信息。其中,基础信息主要包括版本号、运行时间、CPP编译版本号等,而数据库配置信息则包括端口号、系统功能参数配置等。 @@ -407,7 +540,7 @@ Browser提供全屏方式展示图查询结果,点击`全屏显示`按钮全 ##### 2.5.1.1.账户管理 -##### 2.5.1.1.1.添加账户 +###### a.添加账户 在`账户管理`界面点击`添加`按钮创建新的账户,用户需要输入账户名称、账户描述、账户密码以及相关角色。 @@ -418,28 +551,28 @@ Browser提供全屏方式展示图查询结果,点击`全屏显示`按钮全 ![账户管理-添加账户](../../../images/browser/account-add.png) -##### 2.5.1.1.2.编辑账户 +###### b.编辑账户 在`账户管理`界面点击`添加`按钮创建新的账户,用户可以编辑账户描述、账户密码以及相关角色。 ![账户管理-编辑账户](../../../images/browser/account-edit.png) -##### 2.5.1.1.3.禁用账户 +###### c.禁用账户 在`账户管理`界面点击`禁用`按钮禁止对应的账户登录和访问,点击`启用`按钮开启对应的账户登录和访问权限。 ![账户管理-禁用](../../../images/browser/account-disable.png) ![账户管理-启用](../../../images/browser/account-enable.png) -##### 2.5.1.1.4.删除账户 +###### d.删除账户 在`账户管理`界面点击`删除`按钮删除对应的账户。 ![账户管理-删除](../../../images/browser/account-delete.png) -#### 2.5.1.2.角色管理 +##### 2.5.1.2.角色管理 -##### 2.5.1.1.1.添加角色 +###### a.添加角色 在`角色管理`界面点击`添加`按钮创建新的角色,用户需要输入角色名称、角色描述以及图权限。 @@ -455,13 +588,13 @@ Browser提供全屏方式展示图查询结果,点击`全屏显示`按钮全 ![角色管理-添加角色](../../../images/browser/role-add.png) -##### 2.5.1.1.2.编辑角色 +###### b.编辑角色 在`角色管理`界面点击`编辑`按钮编辑已有角色,用户可以编辑角色描述以及图权限。 ![角色管理-编辑角色](../../../images/browser/role-edit.png) -##### 2.5.1.1.3.禁用角色 +###### c.禁用角色 在`角色管理`界面点击`禁用`按钮禁止对应的角色,点击`启用`按钮开启对应的角色。禁用角色后,对应角色图访问权限失效。 @@ -471,7 +604,7 @@ Browser提供全屏方式展示图查询结果,点击`全屏显示`按钮全 ![角色管理-禁用](../../../images/browser/role-disable.png) ![角色管理-启用](../../../images/browser/role-enable.png) -##### 2.5.1.1.4.删除角色 +###### d.删除角色 在`角色管理`界面点击`删除`按钮删除对应的角色。 diff --git a/docs/zh-CN/source/5.developer-manual/3.server-tools/1.data-import.md b/docs/zh-CN/source/5.developer-manual/3.server-tools/1.data-import.md index fc8337c9c7..da0e0705f4 100644 --- a/docs/zh-CN/source/5.developer-manual/3.server-tools/1.data-import.md +++ b/docs/zh-CN/source/5.developer-manual/3.server-tools/1.data-import.md @@ -71,6 +71,8 @@ $ ./lgraph_import -c ./import.config --delimiter "\001\002" - index(可选,该字段是否需要建索引) - unique(可选,该字段是否建索引,并且是 unique 类型的) - primary (仅点配置,必选,主键字段,需指定一个 property,用来唯一确定一个点) + - temproal (仅边配置,可选,指定时间戳属性用于存储层排序) + - temporal_field_order (仅边配置,可选,默认为"ASC",表示升序,也可配置为"DESC",表示降序) - constraints (仅边配置,可选,数组形式,起点和终点的 label,不配置或者为空代表不限制) - detach_property (点边都可配置,可选,默认是`false`。`true` 代表属性数据单独存放,在内存不够,属性数据比较多的场景下可以减少io读放大) - files (数组形式) diff --git a/docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst b/docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst index 4eae3634f0..6cb685c909 100644 --- a/docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst +++ b/docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst @@ -5,7 +5,7 @@ Python存储过程接口 ==================================== -Version: 4.0.0 +Version: 4.0.1 Copyright (C) 2018-2022 Ant Group. diff --git a/docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/Doxyfile b/docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/Doxyfile index 1d34ea82bc..08229fb0f1 100644 --- a/docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/Doxyfile +++ b/docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "TuGraph Procedure API - C++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.0.0 +PROJECT_NUMBER = 4.0.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/include/lgraph/lgraph_rpc_client.h b/include/lgraph/lgraph_rpc_client.h index 388fd0146e..f42a69505e 100644 --- a/include/lgraph/lgraph_rpc_client.h +++ b/include/lgraph/lgraph_rpc_client.h @@ -330,6 +330,21 @@ class RpcClient { const std::string& graph = "default", bool json_format = true, double timeout = 0, const std::string& url = ""); + /** + * @brief Execute a cypher query to leader + * + * @param [out] result The result. + * @param [in] cypher inquire statement. + * @param [in] graph (Optional) the graph to query. + * @param [in] json_format (Optional) Returns the format, true is json,Otherwise, binary + * format. + * @param [in] timeout (Optional) Maximum execution time, overruns will be interrupted. + * @returns True if it succeeds, false if it fails. + */ + bool CallCypherToLeader(std::string& result, const std::string& cypher, + const std::string& graph = "default", bool json_format = true, + double timeout = 0); + /** * @brief Execute a gql query * @@ -347,7 +362,22 @@ class RpcClient { double timeout = 0, const std::string& url = ""); /** - * @brief Execute a built-in procedure + * @brief Execute a gql query to leader + * + * @param [out] result The result. + * @param [in] gql inquire statement. + * @param [in] graph (Optional) the graph to query. + * @param [in] json_format (Optional) Returns the format, true is json,Otherwise, binary + * format. + * @param [in] timeout (Optional) Maximum execution time, overruns will be interrupted. + * @returns True if it succeeds, false if it fails. + */ + bool CallGqlToLeader(std::string& result, const std::string& gql, + const std::string& graph = "default", bool json_format = true, + double timeout = 0); + + /** + * @brief Execute a user-defined procedure * * @param [out] result The result. * @param [in] procedure_type the procedure type, currently supported CPP and PY. @@ -358,7 +388,7 @@ class RpcClient { * @param [in] in_process (Optional) support in future. * @param [in] graph (Optional) the graph to query. * @param [in] json_format (Optional) Returns the format, true is json,Otherwise, - * binary format. + * binary format. * @param [in] url (Optional) Node address of calling procedure. * @returns True if it succeeds, false if it fails. */ @@ -368,6 +398,26 @@ class RpcClient { const std::string& graph = "default", bool json_format = true, const std::string& url = ""); + /** + * @brief Execute a user-defined procedure to leader + * + * @param [out] result The result. + * @param [in] procedure_type the procedure type, currently supported CPP and PY. + * @param [in] procedure_name procedure name. + * @param [in] param the execution parameters. + * @param [in] procedure_time_out (Optional) Maximum execution time, overruns will be + * interrupted. + * @param [in] in_process (Optional) support in future. + * @param [in] graph (Optional) the graph to query. + * @param [in] json_format (Optional) Returns the format, true is json,Otherwise, + * binary format. + * @returns True if it succeeds, false if it fails. + */ + bool CallProcedureToLeader(std::string& result, const std::string& procedure_type, + const std::string& procedure_name, const std::string& param, + double procedure_time_out = 0.0, bool in_process = false, + const std::string& graph = "default", bool json_format = true); + /** * @brief Load a built-in procedure * diff --git a/release/CHANGELOG.md b/release/CHANGELOG.md index 86cc50c159..d3409e1464 100644 --- a/release/CHANGELOG.md +++ b/release/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log for TuGraph +# 4.0.1 (2023-9-28) + +**Breaking Changes:** + +1. Support Temporal order + +2. Add 5 algorithms + +**Improvements And Bug Fixes:** + +1. Python procedure can be killed immediately + +2. Extend label name length to 255 bytes + +3. Other bug fix + +**Interfaces Modification:** + +1. Fix temporal keyword in import.conf + # 4.0.0 (2023-9-6) **Breaking Changes:** diff --git a/release/CHANGELOG_CN.md b/release/CHANGELOG_CN.md index f4dce0cd67..afbc30748f 100644 --- a/release/CHANGELOG_CN.md +++ b/release/CHANGELOG_CN.md @@ -1,5 +1,25 @@ # TuGraph 更新日志 +# 4.0.1 (2023-9-28) + +**重大变更:** + +1. 支持 Temporal 属性排序的升序和降序 + +2. 添加 5 个图算法 + +**优化和错误修复:** + +1. 修复Python存储过程不能立马kill的问题 + +2. 类型名称支持 255 字节 + +3. 其他错误修复 + +**接口更变:** + +1. 修复 import.conf 中的 temporal 关键字 + # 4.0.0 (2023-9-6) **重大变更:** @@ -18,7 +38,7 @@ 4. 其他错误修复 -**接口更变** +**接口更变:** 1. Proto版本更新至1.2.0 diff --git a/release/det_ver.py b/release/det_ver.py index 4ed2a128b4..b3212a30fe 100644 --- a/release/det_ver.py +++ b/release/det_ver.py @@ -19,7 +19,7 @@ def replace_ver(file_name, pattern, curr_ver): lines = f.readlines() f.seek(0,0) for line in lines: - if re.match(pattern, line) is not None: + if pattern in line: print("updating %s" % file_name) new_line = re.sub(r'[0-9]+\.[0-9]+\.[0-9]+', curr_ver, line) print(new_line) @@ -36,6 +36,8 @@ def replace_ver(file_name, pattern, curr_ver): replace_ver('docs/en-US/source/5.developer-manual/6.interface/3.procedure/Doxyfile', 'PROJECT_NUMBER = ', curr_ver) replace_ver('docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/4.Python-procedure.rst', 'Version: ', curr_ver) replace_ver('docs/zh-CN/source/5.developer-manual/6.interface/3.procedure/Doxyfile', 'PROJECT_NUMBER = ', curr_ver) +replace_ver('docs/zh-CN/source/1.guide.md', '安装', curr_ver) +replace_ver('docs/en-US/source/1.guide.md', 'install', curr_ver) dockerfiles = [ "tugraph-mini-runtime-centos7-Dockerfile", @@ -48,4 +50,4 @@ def replace_ver(file_name, pattern, curr_ver): for file in dockerfiles: replace_ver('ci/images/' + file, 'COPY', curr_ver) replace_ver('ci/images/' + file, 'RUN dpkg', curr_ver) - replace_ver('ci/images/' + file, 'RUN rpm', curr_ver) \ No newline at end of file + replace_ver('ci/images/' + file, 'RUN rpm', curr_ver) diff --git a/src/client/cpp/rpc/lgraph_rpc_client.cpp b/src/client/cpp/rpc/lgraph_rpc_client.cpp index 39374ed260..17e97da3e4 100644 --- a/src/client/cpp/rpc/lgraph_rpc_client.cpp +++ b/src/client/cpp/rpc/lgraph_rpc_client.cpp @@ -601,6 +601,17 @@ bool RpcClient::CallCypher(std::string &result, const std::string &cypher, return DoubleCheckQuery(fun); } +bool RpcClient::CallCypherToLeader(std::string& result, const std::string& cypher, + const std::string& graph, bool json_format, double timeout) { + if (client_type == SINGLE_CONNECTION) { + return base_client->CallCypher(result, cypher, graph, json_format, timeout); + } else { + return DoubleCheckQuery([&] { + return leader_client->CallCypher(result, cypher, graph, json_format, timeout); + }); + } +} + bool RpcClient::CallGql(std::string &result, const std::string &gql, const std::string &graph, bool json_format, double timeout, const std::string& url) { @@ -616,6 +627,17 @@ bool RpcClient::CallGql(std::string &result, const std::string &gql, return DoubleCheckQuery(fun); } +bool RpcClient::CallGqlToLeader(std::string& result, const std::string& gql, + const std::string& graph, bool json_format, double timeout) { + if (client_type == SINGLE_CONNECTION) { + return base_client->CallGql(result, gql, graph, json_format, timeout); + } else { + return DoubleCheckQuery([&] { + return leader_client->CallGql(result, gql, graph, json_format, timeout); + }); + } +} + bool RpcClient::CallProcedure(std::string &result, const std::string &procedure_type, const std::string &procedure_name, const std::string ¶m, double procedure_time_out, bool in_process, const std::string &graph, @@ -627,7 +649,7 @@ bool RpcClient::CallProcedure(std::string &result, const std::string &procedure_ bool is_read_procedure = false; for (auto &op : user_defined_procedures) { if (op["plugins"]["name"] == procedure_name) { - is_read_procedure = op["plugins"]["read_only"]; + is_read_procedure = op["plugins"]["read_only"]; break; } } auto fun = [&]{ @@ -642,6 +664,21 @@ bool RpcClient::CallProcedure(std::string &result, const std::string &procedure_ return DoubleCheckQuery(fun); } +bool RpcClient::CallProcedureToLeader(std::string& result, const std::string& procedure_type, + const std::string& procedure_name, const std::string& param, + double procedure_time_out, bool in_process, + const std::string& graph, bool json_format) { + if (client_type == SINGLE_CONNECTION) { + return base_client->CallProcedure(result, procedure_type, procedure_name, param, + procedure_time_out, in_process, graph, json_format); + } else { + return DoubleCheckQuery([&] { + return leader_client->CallProcedure(result, procedure_type, procedure_name, param, + procedure_time_out, in_process, graph, json_format); + }); + } +} + bool RpcClient::LoadProcedure(std::string &result, const std::string &source_file, const std::string &procedure_type, const std::string &procedure_name, const std::string &code_type, @@ -799,18 +836,22 @@ void RpcClient::RefreshClientPool() { bool RpcClient::IsReadQuery(lgraph::ProtoGraphQueryType type, const std::string &query, const std::string &graph) { + bool isReadQuery = true; if (boost::to_upper_copy(query).find("CALL ") != std::string::npos) { for (auto &op : user_defined_procedures) { if (op["graph"] == graph && query.find(op["plugins"]["name"]) != std::string::npos) { - return op["plugins"]["read_only"]; + isReadQuery = isReadQuery && op["plugins"]["read_only"]; + break; } } for (auto &procedure : built_in_procedures) { if (query.find(procedure["name"]) != std::string::npos) { - return procedure["read_only"]; + isReadQuery = isReadQuery && procedure["read_only"]; + break; } } + return isReadQuery; } std::string tmp = query; std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::tolower); diff --git a/src/client/python/rpc/client.cpp b/src/client/python/rpc/client.cpp index ef4f1e15c7..1d80ed529f 100644 --- a/src/client/python/rpc/client.cpp +++ b/src/client/python/rpc/client.cpp @@ -53,6 +53,16 @@ void register_liblgraph_client_python(pybind11::module& m) { pybind11::arg("url") = "", pybind11::return_value_policy::move); + c.def("callCypherToLeader", &LGraphPythonClient::CallCypherToLeader, + "Execute a cypher query\n" + "cypher [in] inquire statement.\n" + "graph [in] the graph to query.\n" + "json_format [in] Returns the format, true is json,Otherwise, binary format\n" + "timeout [in] Maximum execution time, overruns will be interrupted\n", + pybind11::arg("cypher"), pybind11::arg("graph") = "default", + pybind11::arg("json_format") = true, pybind11::arg("timeout") = 0, + pybind11::return_value_policy::move); + c.def("callGql", &LGraphPythonClient::CallGql, "Execute a cypher query\n" "gql [in] inquire statement.\n" @@ -65,6 +75,16 @@ void register_liblgraph_client_python(pybind11::module& m) { pybind11::arg("url") = "", pybind11::return_value_policy::move); + c.def("callGqlToLeader", &LGraphPythonClient::CallGqlToLeader, + "Execute a cypher query\n" + "gql [in] inquire statement.\n" + "graph [in] the graph to query.\n" + "json_format [in] Returns the format, true is json,Otherwise, binary format\n" + "timeout [in] Maximum execution time, overruns will be interrupted\n", + pybind11::arg("gql"), pybind11::arg("graph") = "default", + pybind11::arg("json_format") = true, pybind11::arg("timeout") = 0, + pybind11::return_value_policy::move); + c.def("loadProcedure", &LGraphPythonClient::LoadProcedure, "Load a user-defined procedure\n" "source_file [in] the source_file contain procedure code\n" @@ -99,6 +119,21 @@ void register_liblgraph_client_python(pybind11::module& m) { pybind11::arg("graph") = "default", pybind11::arg("json_format") = true, pybind11::arg("url") = "", pybind11::return_value_policy::move); + c.def("callProcedureToLeader", &LGraphPythonClient::CallProcedureToLeader, + "Execute a user-defined procedure\n" + "procedure_type [in] the procedure type, currently supported CPP and PY\n" + "procedure_name [in] procedure name\n" + "param [in] the execution parameters\n" + "procedure_time_out [in] Maximum execution time, overruns will be interrupted\n" + "in_process [in] support in future\n" + "graph [in] the graph to query.\n" + "json_format [in] Returns the format, true is json,Otherwise, " + " binary format\n", + pybind11::arg("procedure_type"), pybind11::arg("procedure_name"), pybind11::arg("param"), + pybind11::arg("procedure_time_out") = 0.0, pybind11::arg("in_process") = false, + pybind11::arg("graph") = "default", pybind11::arg("json_format") = true, + pybind11::return_value_policy::move); + c.def("listProcedures", &LGraphPythonClient::ListProcedures, "Execute built-in procedures\n" "procedure_type [in] the procedure type, currently supported CPP and PY\n" diff --git a/src/client/python/rpc/lgraph_python_client.h b/src/client/python/rpc/lgraph_python_client.h index 82a5208afb..a1ceae52e1 100644 --- a/src/client/python/rpc/lgraph_python_client.h +++ b/src/client/python/rpc/lgraph_python_client.h @@ -69,6 +69,26 @@ class LGraphPythonClient { return {ret, result}; } + std::pair CallProcedureToLeader(const std::string& procedure_type, + const std::string& procedure_name, + const std::string& param, + double procedure_time_out = 0.0, + bool in_process = false, + const std::string& graph = "default", + bool json_format = true) { + std::string result; + bool ret; + try { + ret = client->CallProcedureToLeader(result, procedure_type, procedure_name, + param, procedure_time_out, + in_process, graph, json_format); + } catch (lgraph::RpcException &e) { + ret = false; + result = e.what(); + } + return {ret, result}; + } + std::pair ListProcedures(const std::string& procedure_type, const std::string& version = "any", const std::string& graph = "default", @@ -177,6 +197,20 @@ class LGraphPythonClient { return {ret, result}; } + std::pair CallCypherToLeader(const std::string& cypher, + const std::string& graph = "default", + bool json_format = true, double timeout = 0) { + std::string result; + bool ret; + try { + ret = client->CallCypherToLeader(result, cypher, graph, json_format, timeout); + } catch (lgraph::RpcException &e) { + ret = false; + result = e.what(); + } + return {ret, result}; + } + std::pair CallGql(const std::string& gql, const std::string& graph = "default", bool json_format = true, double timeout = 0, @@ -192,6 +226,20 @@ class LGraphPythonClient { return {ret, result}; } + std::pair CallGqlToLeader(const std::string& gql, + const std::string& graph = "default", + bool json_format = true, double timeout = 0) { + std::string result; + bool ret; + try { + ret = client->CallGqlToLeader(result, gql, graph, json_format, timeout); + } catch (lgraph::RpcException &e) { + ret = false; + result = e.what(); + } + return {ret, result}; + } + void Logout() { client->Logout(); client.reset(); diff --git a/src/cypher/procedure/procedure.h b/src/cypher/procedure/procedure.h index da914d375c..1d9c2f883d 100644 --- a/src/cypher/procedure/procedure.h +++ b/src/cypher/procedure/procedure.h @@ -883,7 +883,7 @@ static std::vector global_procedures = { Procedure("db.plugin.getPluginInfo", BuiltinProcedure::DbPluginGetPluginInfo, Procedure::SIG_SPEC{{"plugin_type", {0, lgraph_api::LGraphType::STRING}}, {"plugin_name", {1, lgraph_api::LGraphType::STRING}}}, - Procedure::SIG_SPEC{{"plugin_description", {0, lgraph_api::LGraphType::MAP}}}, false, + Procedure::SIG_SPEC{{"plugin_description", {0, lgraph_api::LGraphType::MAP}}}, true, true), Procedure("db.plugin.listPlugin", BuiltinProcedure::DbPluginListPlugin, diff --git a/src/plugin/python_plugin.cpp b/src/plugin/python_plugin.cpp index 04895cffda..e14842b6f5 100644 --- a/src/plugin/python_plugin.cpp +++ b/src/plugin/python_plugin.cpp @@ -48,9 +48,37 @@ PythonPluginManagerImpl::PythonPluginManagerImpl(LightningGraph* db, const std:: : max_idle_seconds) { fma_common::FilePath p(db->GetConfig().dir); db_dir_ = p.Dir(); + auto& scheduler = fma_common::TimedTaskScheduler::GetInstance(); + _kill_task = scheduler.ScheduleReccurringTask( + max_idle_seconds * 1000, [this](fma_common::TimedTask*) { + std::lock_guard l(_mtx); + CleanUpIdleProcessesNoLock(); + auto it = _marked_processes.begin(); + while (it != _marked_processes.end()) { + if ((*it)->IsAlive()) { + (*it)->Kill(); + it++; + } else { + it = _marked_processes.erase(it); + } + } + }); } -PythonPluginManagerImpl::~PythonPluginManagerImpl() { KillAllProcesses(); } +PythonPluginManagerImpl::~PythonPluginManagerImpl() { + _kill_task->Cancel(); + KillAllProcesses(); + std::lock_guard l(_mtx); + auto it = _marked_processes.begin(); + while (it != _marked_processes.end()) { + if ((*it)->IsAlive()) { + // kill process force + (*it)->Kill(true); + } + it++; + } + _marked_processes.clear(); +} void PythonPluginManagerImpl::LoadPlugin(const std::string& user, const std::string& name, PluginInfoBase* pinfo) { @@ -156,6 +184,8 @@ python_plugin::TaskOutput::ErrorCode PythonPluginManagerImpl::CallInternal( // if master thread is killing this process if (TaskTracker::GetInstance().ShouldKillCurrentTask() || proc->ShouldKill()) { proc->Kill(); + std::lock_guard l(_mtx); + _marked_processes.insert(std::move(proc)); throw lgraph_api::TaskKilledException(); } // if process failed, break @@ -175,29 +205,21 @@ python_plugin::TaskOutput::ErrorCode PythonPluginManagerImpl::CallInternal( if (fma_common::GetTime() - start_time >= timeout) { // timeout, kill process proc->Kill(); + std::lock_guard l(_mtx); output = proc->Stderr(); + _marked_processes.insert(std::move(proc)); throw TimeoutException(timeout); } } rollback.Cancel(); - // now, check if we need to kill processes and put the process back to pool + // now, check if we need to put the process back to pool { std::lock_guard l(_mtx); _busy_processes.erase(proc.get()); CleanUpIdleProcessesNoLock(); if (!proc->Killed()) { - if (std::chrono::duration_cast(std::chrono::steady_clock::now() - - _last_request_time) - .count() >= max_idle_seconds_) { - // kill my process if the last request is long long ago - proc->Kill(); - } else { - // add proc to free list - _free_processes.emplace_front(proc.release()); - } + _free_processes.emplace_front(proc.release()); } - // update last_used time - _last_request_time = std::chrono::steady_clock::now(); } return ec; } @@ -207,6 +229,7 @@ void PythonPluginManagerImpl::KillAllProcesses() { std::lock_guard l(_mtx); for (auto& p : _free_processes) { p->Kill(); + _marked_processes.insert(std::move(p)); } _free_processes.clear(); for (auto& p : _busy_processes) { @@ -219,8 +242,8 @@ void PythonPluginManagerImpl::CleanUpIdleProcessesNoLock() { auto it = _free_processes.rbegin(); while (it != _free_processes.rend() && (*it)->GetIdleTimeInSeconds() >= (size_t)max_idle_seconds_) { - FMA_DBG() << "Killing old python process"; (*it)->Kill(); + _marked_processes.insert(std::move(*it)); it++; } _free_processes.erase(it.base(), _free_processes.end()); diff --git a/src/plugin/python_plugin.h b/src/plugin/python_plugin.h index 4201c98436..06fb2e0586 100644 --- a/src/plugin/python_plugin.h +++ b/src/plugin/python_plugin.h @@ -116,13 +116,17 @@ class PythonWorkerProcess { // The following three methods all try to modify the process handle. // So only one successful invocation is allowed. Otherwise, later // invocation results in undefined behavior. - void Kill() { + void Kill(bool force = false) { if (killed_) return; - while (!process_->try_get_exit_status(exit_code_)) { - process_->kill(true); - fma_common::SleepS(0.1); + if (force) { + process_->kill(false); + while (!process_->try_get_exit_status(exit_code_)) { + fma_common::SleepS(0.1); + process_->kill(true); + } + } else { + process_->kill(false); } - killed_ = true; } int Wait() { @@ -181,7 +185,11 @@ class PythonPluginManagerImpl : public PluginManagerImplBase { // Before deleting the process, worker thread is required to remove // the prointer from busy_processes from this set first. std::unordered_set _busy_processes; - std::chrono::steady_clock::time_point _last_request_time; + fma_common::TimedTaskScheduler::TaskPtr _kill_task; + // marked processes are owned by plugin manager. + // After trying to kill a process, worker thread need to move it + // into this queue. + std::unordered_set> _marked_processes; // just for test PythonPluginManagerImpl(const std::string& name, const std::string& db_dir, size_t db_size, @@ -189,8 +197,23 @@ class PythonPluginManagerImpl : public PluginManagerImplBase { : graph_name_(name), db_dir_(db_dir), plugin_dir_(plugin_dir), - max_idle_seconds_(max_idle_seconds), - _last_request_time(std::chrono::steady_clock::now()) {} + max_idle_seconds_(max_idle_seconds) { + auto& scheduler = fma_common::TimedTaskScheduler::GetInstance(); + _kill_task = scheduler.ScheduleReccurringTask( + max_idle_seconds * 1000, [this](fma_common::TimedTask*) { + std::lock_guard l(_mtx); + CleanUpIdleProcessesNoLock(); + auto it = _marked_processes.begin(); + while (it != _marked_processes.end()) { + if ((*it)->IsAlive()) { + (*it)->Kill(); + it++; + } else { + it = _marked_processes.erase(it); + } + } + }); + } public: PythonPluginManagerImpl(LightningGraph* db, const std::string& graph_name, diff --git a/src/python/lgraph_task_runner.py b/src/python/lgraph_task_runner.py index 899660a2c3..cbf5f2e460 100644 --- a/src/python/lgraph_task_runner.py +++ b/src/python/lgraph_task_runner.py @@ -121,6 +121,9 @@ def InvokeFunction(self, db, function, input, read_only): success = False try: (success, output) = self.functions[function](db, input) + except (KeyboardInterrupt, SystemExit): + logging.warn('process being killed') + raise except Exception as e: success = False output = "Exception occured in plugin: {}".format(e) diff --git a/src/python/python_api.cpp b/src/python/python_api.cpp index 562f99f28f..5bf0ecaaa0 100644 --- a/src/python/python_api.cpp +++ b/src/python/python_api.cpp @@ -43,6 +43,15 @@ namespace lgraph_api { namespace python { + +struct SignalsGuard { + SignalsGuard() { + if (PyErr_CheckSignals() != 0) { + throw pybind11::error_already_set(); + } + } +}; + inline FieldData ObjectToFieldData(const pybind11::object& o) { if (pybind11::isinstance(o)) { return o.cast(); @@ -126,7 +135,8 @@ void register_python_api(pybind11::module& m) { .def(pybind11::init(), "Defines a FieldSpec with its name (type:str), type " "(type:FieldType) and nullable (type:bool).", - pybind11::arg("name"), pybind11::arg("type"), pybind11::arg("nullable")) + pybind11::arg("name"), pybind11::arg("type"), pybind11::arg("nullable"), + pybind11::call_guard()) .def_readwrite("name", &FieldSpec::name, "Name of this field.") .def_readwrite("type", &FieldSpec::type, "Type of this field, INT8, INT16, ..., FLOAT, DOUBLE, STRING.") @@ -137,11 +147,13 @@ void register_python_api(pybind11::module& m) { }); pybind11::class_(m, "EdgeUid", "Edge identifier.") - .def(pybind11::init<>()) + .def(pybind11::init<>(), + pybind11::call_guard()) .def(pybind11::init(), "Defines a EdgeUid with (src_id, dst_id, label_id, primary_id, edge_id).", pybind11::arg("src_id"), pybind11::arg("dst_id"), pybind11::arg("label_id"), - pybind11::arg("primary_id"), pybind11::arg("edge_id")) + pybind11::arg("primary_id"), pybind11::arg("edge_id"), + pybind11::call_guard()) .def_readwrite("src", &EdgeUid::src, "Source vertex ID.") .def_readwrite("dst", &EdgeUid::dst, "Destination vertex ID.") .def_readwrite("lid", &EdgeUid::lid, "Label ID of the edge.") @@ -159,11 +171,13 @@ void register_python_api(pybind11::module& m) { }); pybind11::class_(m, "IndexSpec", "Index specification.") - .def(pybind11::init<>()) + .def(pybind11::init<>(), + pybind11::call_guard()) .def(pybind11::init(), "Defines an IndexSpec with its label_name:str, field_name:str and " "is_unique:bool.", - pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("is_unique")) + pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("is_unique"), + pybind11::call_guard()) .def_readwrite("label", &IndexSpec::label, "Name of the label.") .def_readwrite("field", &IndexSpec::field, "Name of the field") .def_readwrite("unique", &IndexSpec::unique, "Whether the indexed values are unique.") @@ -173,10 +187,12 @@ void register_python_api(pybind11::module& m) { }); pybind11::class_(m, "EdgeOptions", "Edge options.") - .def(pybind11::init<>()) + .def(pybind11::init<>(), + pybind11::call_guard()) .def(pybind11::init>&>(), "Define EdgeOptions with edge constraints", - pybind11::arg("edge_constraints")) + pybind11::arg("edge_constraints"), + pybind11::call_guard()) .def_readwrite("edge_constraints", &EdgeOptions::edge_constraints, "Edge constraints.") .def_readwrite("temporal_field", &EdgeOptions::temporal_field, "Edge temporal field.") .def_readwrite("detach_property", &EdgeOptions::detach_property, @@ -186,10 +202,12 @@ void register_python_api(pybind11::module& m) { }); pybind11::class_(m, "VertexOptions", "Vertex options.") - .def(pybind11::init<>()) + .def(pybind11::init<>(), + pybind11::call_guard()) .def(pybind11::init(), "Define VertexOptions with primary field", - pybind11::arg("primary_field")) + pybind11::arg("primary_field"), + pybind11::call_guard()) .def_readwrite("primary_field", &VertexOptions::primary_field, "Vertex primary field.") .def_readwrite("detach_property", &VertexOptions::detach_property, @@ -199,18 +217,30 @@ void register_python_api(pybind11::module& m) { }); pybind11::class_ data(m, "FieldData", "FieldData is the data type of field value."); - data.def(pybind11::init<>(), "Constructs an empty FieldData (is_null() == true).") - .def(pybind11::init(), "Constructs an bool type FieldData.") - .def(pybind11::init(), "Constructs an int8 type FieldData.") - .def(pybind11::init(), "Constructs an int16 type FieldData.") - .def(pybind11::init(), "Constructs an int32 type FieldData.") - .def(pybind11::init(), "Constructs an int64 type FieldData.") - .def(pybind11::init(), "Constructs an float type FieldData.") - .def(pybind11::init(), "Constructs a double type FieldData.") - .def(pybind11::init(), "Constructs a FielData contains binary bytes.") - .def("isNull", [](const FieldData& a) { return a.IsNull(); }) - .def("get", [](const FieldData& a) -> pybind11::object { return FieldDataToPyObj(a); }) - .def("set", [](FieldData& a, const pybind11::object& o) { a = ObjectToFieldData(o); }) + data.def(pybind11::init<>(), "Constructs an empty FieldData (is_null() == true).", + pybind11::call_guard()) + .def(pybind11::init(), "Constructs an bool type FieldData.", + pybind11::call_guard()) + .def(pybind11::init(), "Constructs an int8 type FieldData.", + pybind11::call_guard()) + .def(pybind11::init(), "Constructs an int16 type FieldData.", + pybind11::call_guard()) + .def(pybind11::init(), "Constructs an int32 type FieldData.", + pybind11::call_guard()) + .def(pybind11::init(), "Constructs an int64 type FieldData.", + pybind11::call_guard()) + .def(pybind11::init(), "Constructs an float type FieldData.", + pybind11::call_guard()) + .def(pybind11::init(), "Constructs a double type FieldData.", + pybind11::call_guard()) + .def(pybind11::init(), "Constructs a FielData contains binary bytes.", + pybind11::call_guard()) + .def("isNull", [](const FieldData& a) { return a.IsNull(); }, + pybind11::call_guard()) + .def("get", [](const FieldData& a) -> pybind11::object { return FieldDataToPyObj(a); }, + pybind11::call_guard()) + .def("set", [](FieldData& a, const pybind11::object& o) { a = ObjectToFieldData(o); }, + pybind11::call_guard()) .def_static("Bool", &FieldData::Bool, "Make a BOOL value") .def_static("Int8", &FieldData::Int8, "Make a INT8 value") .def_static("Int16", &FieldData::Int16, "Make a INT16 value") @@ -243,70 +273,101 @@ void register_python_api(pybind11::module& m) { .def_static( "Blob", [](const pybind11::bytes& str) { return FieldData::Blob(str); }, "Make a BLOB value") - .def("AsBool", &FieldData::AsBool, "Get value as bool, throws exception on type mismatch") - .def("AsInt8", &FieldData::AsInt8, "Get value as int8, throws exception on type mismatch") + .def("AsBool", &FieldData::AsBool, "Get value as bool, throws exception on type mismatch", + pybind11::call_guard()) + .def("AsInt8", &FieldData::AsInt8, "Get value as int8, throws exception on type mismatch", + pybind11::call_guard()) .def("AsInt16", &FieldData::AsInt16, - "Get value as int16, throws exception on type mismatch") + "Get value as int16, throws exception on type mismatch", + pybind11::call_guard()) .def("AsInt32", &FieldData::AsInt32, - "Get value as int32, throws exception on type mismatch") + "Get value as int32, throws exception on type mismatch", + pybind11::call_guard()) .def("AsInt64", &FieldData::AsInt64, - "Get value as int64, throws exception on type mismatch") + "Get value as int64, throws exception on type mismatch", + pybind11::call_guard()) .def("AsFloat", &FieldData::AsFloat, - "Get value as float, throws exception on type mismatch") + "Get value as float, throws exception on type mismatch", + pybind11::call_guard()) .def("AsDouble", &FieldData::AsDouble, - "Get value as double, throws exception on type mismatch") + "Get value as double, throws exception on type mismatch", + pybind11::call_guard()) .def( "AsDate", [](const FieldData& a) { return a.AsDate().operator lgraph_api::DateTime().ConvertToUTC().TimePoint(); }, - "Get value as date, throws exception on type mismatch") + "Get value as date, throws exception on type mismatch", + pybind11::call_guard()) .def( "AsDateTime", // python sees time_point as utc time and will always convert it to local time [](const FieldData& a) { return a.AsDateTime().ConvertToUTC().TimePoint(); }, - "Get value as datetime, throws exception on type mismatch") + "Get value as datetime, throws exception on type mismatch", + pybind11::call_guard()) .def("AsString", &FieldData::AsString, - "Get value as string, throws exception on type mismatch") + "Get value as string, throws exception on type mismatch", + pybind11::call_guard()) .def( "AsBlob", [](const FieldData& a) { return pybind11::bytes(*a.data.buf); }, - "Get value as double, throws exception on type mismatch") + "Get value as double, throws exception on type mismatch", + pybind11::call_guard()) .def( "ToPython", [](const FieldData& fd) { return FieldDataToPyObj(fd); }, - "Convert to corresponding Python type.") + "Convert to corresponding Python type.", + pybind11::call_guard()) .def_readonly("type", &FieldData::type) - .def("__repr__", [](const FieldData& a) { return a.ToString(); }) - .def("__eq__", [](const FieldData& a, const FieldData& b) { return a == b; }) + .def("__repr__", [](const FieldData& a) { return a.ToString(); }, + pybind11::call_guard()) + .def("__eq__", [](const FieldData& a, const FieldData& b) { return a == b; }, + pybind11::call_guard()) .def("__eq__", [](const FieldData& a, - const pybind11::object& b) { return a == ObjectToFieldData(b); }) + const pybind11::object& b) { return a == ObjectToFieldData(b); }, + pybind11::call_guard()) .def("__eq__", [](const pybind11::object& a, - const FieldData& b) { return ObjectToFieldData(a) == b; }) - .def("__gt__", [](const FieldData& a, const FieldData& b) { return a > b; }) + const FieldData& b) { return ObjectToFieldData(a) == b; }, + pybind11::call_guard()) + .def("__gt__", [](const FieldData& a, const FieldData& b) { return a > b; }, + pybind11::call_guard()) .def("__gt__", - [](const FieldData& a, const pybind11::object& b) { return a > ObjectToFieldData(b); }) + [](const FieldData& a, const pybind11::object& b) { return a > ObjectToFieldData(b); }, + pybind11::call_guard()) .def("__gt__", - [](const pybind11::object& a, const FieldData& b) { return ObjectToFieldData(a) > b; }) - .def("__lt__", [](const FieldData& a, const FieldData& b) { return a < b; }) + [](const pybind11::object& a, const FieldData& b) { return ObjectToFieldData(a) > b; }, + pybind11::call_guard()) + .def("__lt__", [](const FieldData& a, const FieldData& b) { return a < b; }, + pybind11::call_guard()) .def("__lt__", - [](const FieldData& a, const pybind11::object& b) { return a < ObjectToFieldData(b); }) + [](const FieldData& a, const pybind11::object& b) { return a < ObjectToFieldData(b); }, + pybind11::call_guard()) .def("__lt__", - [](const pybind11::object& a, const FieldData& b) { return ObjectToFieldData(a) < b; }) - .def("__le__", [](const FieldData& a, const FieldData& b) { return a <= b; }) + [](const pybind11::object& a, const FieldData& b) { return ObjectToFieldData(a) < b; }, + pybind11::call_guard()) + .def("__le__", [](const FieldData& a, const FieldData& b) { return a <= b; }, + pybind11::call_guard()) .def("__le__", [](const FieldData& a, - const pybind11::object& b) { return a <= ObjectToFieldData(b); }) + const pybind11::object& b) { return a <= ObjectToFieldData(b); }, + pybind11::call_guard()) .def("__le__", [](const pybind11::object& a, - const FieldData& b) { return ObjectToFieldData(a) <= b; }) - .def("__ge__", [](const FieldData& a, const FieldData& b) { return a >= b; }) + const FieldData& b) { return ObjectToFieldData(a) <= b; }, + pybind11::call_guard()) + .def("__ge__", [](const FieldData& a, const FieldData& b) { return a >= b; }, + pybind11::call_guard()) .def("__ge__", [](const FieldData& a, - const pybind11::object& b) { return a >= ObjectToFieldData(b); }) + const pybind11::object& b) { return a >= ObjectToFieldData(b); }, + pybind11::call_guard()) .def("__ge__", [](const pybind11::object& a, - const FieldData& b) { return ObjectToFieldData(a) >= b; }) - .def("__neq__", [](const FieldData& a, const FieldData& b) { return a != b; }) + const FieldData& b) { return ObjectToFieldData(a) >= b; }, + pybind11::call_guard()) + .def("__neq__", [](const FieldData& a, const FieldData& b) { return a != b; }, + pybind11::call_guard()) .def("__neq__", [](const FieldData& a, - const pybind11::object& b) { return a != ObjectToFieldData(b); }) + const pybind11::object& b) { return a != ObjectToFieldData(b); }, + pybind11::call_guard()) .def("__neq__", [](const pybind11::object& a, const FieldData& b) { return ObjectToFieldData(a) != b; - }); + }, + pybind11::call_guard()); pybind11::enum_(m, "FieldType", pybind11::arithmetic(), "Data type of FieldData.") .value("NUL", FieldType::NUL) @@ -352,12 +413,14 @@ void register_python_api(pybind11::module& m) { "close the galaxy with Galaxy.Close() when you are done with it."); galaxy .def( - "__enter__", [&](Galaxy& r) -> Galaxy& { return r; }, "Init galaxy.") + "__enter__", [&](Galaxy& r) -> Galaxy& { return r; }, "Init galaxy.", + pybind11::call_guard()) .def( "__exit__", [&](Galaxy& g, pybind11::object exc_type, pybind11::object exc_value, pybind11::object traceback) { g.Close(); }, - "Release memory of this galaxy."); + "Release memory of this galaxy.", + pybind11::call_guard()); galaxy.def(pybind11::init(), "Initializes a galaxy instance stored in dir.\n" "dir: directory of the database\n" @@ -366,15 +429,18 @@ void register_python_api(pybind11::module& m) { "create_if_not_exist: whether to create the database if dir does not exist", pybind11::arg("dir"), pybind11::arg("durable") = false, pybind11::arg("create_if_not_exist") = false, - pybind11::return_value_policy::move); + pybind11::return_value_policy::move, + pybind11::call_guard()); galaxy.def("SetCurrentUser", &Galaxy::SetCurrentUser, "Validate user password and set current user.\n" "user: user name\n" "password: password of the user", - pybind11::arg("user"), pybind11::arg("password")); + pybind11::arg("user"), pybind11::arg("password"), + pybind11::call_guard()); galaxy.def("SetUser", &Galaxy::SetUser, "Validate the given user and set current user given in the user.", - pybind11::arg("user")); + pybind11::arg("user"), + pybind11::call_guard()); galaxy.def("Close", &Galaxy::Close, "Closes this galaxy") .def("CreateGraph", &Galaxy::CreateGraph, "Creates a graph.\n" @@ -382,10 +448,13 @@ void register_python_api(pybind11::module& m) { "description: description of the graph\n" "max_size: maximum size of the graph, default 1TB", pybind11::arg("name"), pybind11::arg("description") = "", - pybind11::arg("max_size") = lgraph::_detail::DEFAULT_GRAPH_SIZE) - .def("DeleteGraph", &Galaxy::DeleteGraph, "Deletes a graph") + pybind11::arg("max_size") = lgraph::_detail::DEFAULT_GRAPH_SIZE, + pybind11::call_guard()) + .def("DeleteGraph", &Galaxy::DeleteGraph, "Deletes a graph", + pybind11::call_guard()) .def("ListGraphs", &Galaxy::ListGraphs, - "Lists graphs and returns a dictionary of {name:(desc, max_size)}") + "Lists graphs and returns a dictionary of {name:(desc, max_size)}", + pybind11::call_guard()) .def("ModGraph", &Galaxy::ModGraph, "Modifies the information of the graph, returns true if successful, false if no such " "graph.\n" @@ -395,56 +464,72 @@ void register_python_api(pybind11::module& m) { "mod_size: whether to modify max graph size\n" "new_max_size: new maximum size of the graph, in bytes", pybind11::arg("graph_name"), pybind11::arg("mod_desc"), pybind11::arg("description"), - pybind11::arg("mod_size"), pybind11::arg("new_max_size")) + pybind11::arg("mod_size"), pybind11::arg("new_max_size"), + pybind11::call_guard()) .def("CreateUser", &Galaxy::CreateUser, "Creates a new user account.\n" "name: name of the user\n" "password: password for the user\n" "desc: description of this user", - pybind11::arg("name"), pybind11::arg("password"), pybind11::arg("desc")) - .def("DeleteUser", &Galaxy::DeleteUser, "Deletes a user account") + pybind11::arg("name"), pybind11::arg("password"), pybind11::arg("desc"), + pybind11::call_guard()) + .def("DeleteUser", &Galaxy::DeleteUser, "Deletes a user account", + pybind11::call_guard()) .def("SetUserPass", &Galaxy::SetPassword, "Modifies user password.\n" "name: name of the user\n" "old_password: current password, not needed when modifying another user\n" "new_password: new password for the user", pybind11::arg("name"), pybind11::arg("old_password") = "", - pybind11::arg("new_password")) + pybind11::arg("new_password"), + pybind11::call_guard()) .def("SetUserRoles", &Galaxy::SetUserRoles, "Set the roles for the specified user.\n" "name: name of the user\n" "roles: list of roles for this user", - pybind11::arg("name"), pybind11::arg("roles")) + pybind11::arg("name"), pybind11::arg("roles"), + pybind11::call_guard()) .def("SetUserGraphAccess", &Galaxy::SetUserGraphAccess, "Set the access level of the specified user on the graph.\n" "user: name of the user\n" "graph: name of the graph\n" "access: access level of the user on that graph", - pybind11::arg("user"), pybind11::arg("graph"), pybind11::arg("access")) - .def("DisableUser", &Galaxy::DisableUser, "Disables a user") - .def("EnableUser", &Galaxy::EnableUser, "Enables a user") - .def("ListUsers", &Galaxy::ListUsers, "Lists all users and whether they are admin") - .def("GetUserInfo", &Galaxy::GetUserInfo, "Get information of the specified user") + pybind11::arg("user"), pybind11::arg("graph"), pybind11::arg("access"), + pybind11::call_guard()) + .def("DisableUser", &Galaxy::DisableUser, "Disables a user", + pybind11::call_guard()) + .def("EnableUser", &Galaxy::EnableUser, "Enables a user", + pybind11::call_guard()) + .def("ListUsers", &Galaxy::ListUsers, "Lists all users and whether they are admin", + pybind11::call_guard()) + .def("GetUserInfo", &Galaxy::GetUserInfo, "Get information of the specified user", + pybind11::call_guard()) .def("CreateRole", &Galaxy::CreateRole, "Create a role.\n" "name: name of the role\n" "desc: description of the role", - pybind11::arg("name"), pybind11::arg("desc")) - .def("DeleteRole", &Galaxy::DeleteRole, "Deletes the specified role") + pybind11::arg("name"), pybind11::arg("desc"), + pybind11::call_guard()) + .def("DeleteRole", &Galaxy::DeleteRole, "Deletes the specified role", + pybind11::call_guard()) .def("SetRoleDesc", &Galaxy::SetRoleDesc, "Set description of the specified role.\n" "name: name of the role\n" "desc: description of the role", - pybind11::arg("name"), pybind11::arg("desc")) + pybind11::arg("name"), pybind11::arg("desc"), + pybind11::call_guard()) .def("SetRoleAccessRights", &Galaxy::SetRoleAccessRights, - "Set access rights for the specified role") + "Set access rights for the specified role", + pybind11::call_guard()) .def("SetRoleAccessRightsIncremental", &Galaxy::SetRoleAccessRightsIncremental, - "Set access rights for the specified role, only affects the specified graphs") + "Set access rights for the specified role, only affects the specified graphs", + pybind11::call_guard()) .def("OpenGraph", &Galaxy::OpenGraph, "Opens a graph and returns a GraphDB instance.\n" "graph: name of the graph\n" "read_only: whether to open the graph in read-only mode", - pybind11::arg("graph"), pybind11::arg("read_only") = false); + pybind11::arg("graph"), pybind11::arg("read_only") = false, + pybind11::call_guard()); pybind11::class_ graph_db( m, "GraphDB", @@ -457,32 +542,42 @@ void register_python_api(pybind11::module& m) { "is using the DB before you close the DB."); graph_db .def( - "__enter__", [&](GraphDB& r) -> GraphDB& { return r; }, "Init GraphDB.") + "__enter__", [&](GraphDB& r) -> GraphDB& { return r; }, "Init GraphDB.", + pybind11::call_guard()) .def( "__exit__", [&](GraphDB& g, pybind11::object exc_type, pybind11::object exc_value, pybind11::object traceback) { g.Close(); }, - "Release memory of this GraphDB."); - graph_db.def("Close", &GraphDB::Close, "Closes the DB.") + "Release memory of this GraphDB.", + pybind11::call_guard()); + graph_db.def("Close", &GraphDB::Close, "Closes the DB.", + pybind11::call_guard()) .def("CreateWriteTxn", &GraphDB::CreateWriteTxn, "Create a write transaction.\n", pybind11::arg("optimistic") = false, - pybind11::return_value_policy::move) - .def("CreateReadTxn", &GraphDB::CreateReadTxn, pybind11::return_value_policy::move) - .def("Flush", &GraphDB::Flush, "Flushes written data into disk.") + pybind11::return_value_policy::move, + pybind11::call_guard()) + .def("CreateReadTxn", &GraphDB::CreateReadTxn, pybind11::return_value_policy::move, + pybind11::call_guard()) + .def("Flush", &GraphDB::Flush, "Flushes written data into disk.", + pybind11::call_guard()) .def("DropAllData", &GraphDB::DropAllData, "Drop all the data in this DB.\n" - "All vertices, edges, labels and indexes will be dropped.") + "All vertices, edges, labels and indexes will be dropped.", + pybind11::call_guard()) .def("DropAllVertex", &GraphDB::DropAllVertex, "Drops all the vertices and edges in this DB.\n" "Labels and indexes (though index contents will be cleared due to " - "deletion of vertices) will be preserved.") + "deletion of vertices) will be preserved.", + pybind11::call_guard()) .def("EstimateNumVertices", &GraphDB::EstimateNumVertices, "Gets an estimation of the number of vertices.\n" - "This can be inaccurate if there were vertex removals.") + "This can be inaccurate if there were vertex removals.", + pybind11::call_guard()) .def("AddVertexLabel", &GraphDB::AddVertexLabel, "Add a vertex label.", pybind11::arg("label_name"), pybind11::arg("field_specs"), - pybind11::arg("options")) + pybind11::arg("options"), + pybind11::call_guard()) .def( "DeleteVertexLabel", [](GraphDB& db, const std::string& label) { @@ -490,9 +585,11 @@ void register_python_api(pybind11::module& m) { if (db.DeleteVertexLabel(label, &n)) return n; throw lgraph::InputError("No such label."); }, - "Deletes a vertex label", pybind11::arg("label_name")) + "Deletes a vertex label", pybind11::arg("label_name"), + pybind11::call_guard()) .def("AlterEdgeLabelModifyConstraints", &GraphDB::AlterLabelModEdgeConstraints, - "Modify edge constraints", pybind11::arg("label_name"), pybind11::arg("constraints")) + "Modify edge constraints", pybind11::arg("label_name"), pybind11::arg("constraints"), + pybind11::call_guard()) .def( "AlterVertexLabelDelFields", [](GraphDB& db, const std::string& label, const std::vector& del_fields) { @@ -503,7 +600,8 @@ void register_python_api(pybind11::module& m) { "Delete fields from a vertex label\n" "label: name of the label\n" "del_fields: list of field names", - pybind11::arg("label"), pybind11::arg("del_fields")) + pybind11::arg("label"), pybind11::arg("del_fields"), + pybind11::call_guard()) .def( "AlterVertexLabelAddFields", [](GraphDB& db, const std::string& label, const std::vector& add_fields, @@ -516,7 +614,8 @@ void register_python_api(pybind11::module& m) { "label: name of the label\n" "add_fields: list of FieldSpec for the newly added fields\n" "default_values: default values of the added fields", - pybind11::arg("label"), pybind11::arg("add_fields"), pybind11::arg("default_values")) + pybind11::arg("label"), pybind11::arg("add_fields"), pybind11::arg("default_values"), + pybind11::call_guard()) .def( "AlterVertexLabelModFields", [](GraphDB& db, const std::string& label, const std::vector& mod_fields) { @@ -527,10 +626,12 @@ void register_python_api(pybind11::module& m) { "Modify fields in a vertex label\n" "label: name of the label\n" "mod_fields: list of FieldSpec for the modified fields", - pybind11::arg("label"), pybind11::arg("mod_fields")) + pybind11::arg("label"), pybind11::arg("mod_fields"), + pybind11::call_guard()) .def("AddEdgeLabel", &GraphDB::AddEdgeLabel, "Adds an edge label.", pybind11::arg("label_name"), pybind11::arg("field_specs"), - pybind11::arg("options")) + pybind11::arg("options"), + pybind11::call_guard()) .def( "DeleteEdgeLabel", [](GraphDB& db, const std::string& label) { @@ -538,7 +639,8 @@ void register_python_api(pybind11::module& m) { if (db.DeleteEdgeLabel(label, &n)) return n; throw lgraph::InputError("No such label."); }, - "Deletes an edge label", pybind11::arg("label_name")) + "Deletes an edge label", pybind11::arg("label_name"), + pybind11::call_guard()) .def( "AlterEdgeLabelDelFields", [](GraphDB& db, const std::string& label, const std::vector& del_fields) { @@ -549,7 +651,8 @@ void register_python_api(pybind11::module& m) { "Delete fields from an edge label\n" "label: name of the label\n" "del_fields: list of field names", - pybind11::arg("label"), pybind11::arg("del_fields")) + pybind11::arg("label"), pybind11::arg("del_fields"), + pybind11::call_guard()) .def( "AlterEdgeLabelAddFields", [](GraphDB& db, const std::string& label, const std::vector& add_fields, @@ -562,7 +665,8 @@ void register_python_api(pybind11::module& m) { "label: name of the label\n" "add_fields: list of FieldSpec for the newly added fields\n" "default_values: default values of the added fields", - pybind11::arg("label"), pybind11::arg("add_fields"), pybind11::arg("default_values")) + pybind11::arg("label"), pybind11::arg("add_fields"), pybind11::arg("default_values"), + pybind11::call_guard()) .def( "AlterEdgeLabelModFields", [](GraphDB& db, const std::string& label, const std::vector& mod_fields) { @@ -573,16 +677,22 @@ void register_python_api(pybind11::module& m) { "Modify fields in an edge label\n" "label: name of the label\n" "mod_fields: list of FieldSpec for the modified fields", - pybind11::arg("label"), pybind11::arg("mod_fields")) + pybind11::arg("label"), pybind11::arg("mod_fields"), + pybind11::call_guard()) .def("AddVertexIndex", &GraphDB::AddVertexIndex, "Adds an index.", - pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("is_unique")) + pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("is_unique"), + pybind11::call_guard()) .def("IsVertexIndexed", &GraphDB::IsVertexIndexed, "Tells whether the specified field is indexed.", pybind11::arg("label_name"), - pybind11::arg("field_name")) + pybind11::arg("field_name"), + pybind11::call_guard()) .def("DeleteVertexIndex", &GraphDB::DeleteVertexIndex, "Deletes the specified index.", - pybind11::arg("label_name"), pybind11::arg("field_name")) - .def("GetDescription", &GraphDB::GetDescription, "Gets description of the graph.") - .def("GetMaxSize", &GraphDB::GetMaxSize, "Gets maximum size of the graph."); + pybind11::arg("label_name"), pybind11::arg("field_name"), + pybind11::call_guard()) + .def("GetDescription", &GraphDB::GetDescription, "Gets description of the graph.", + pybind11::call_guard()) + .def("GetMaxSize", &GraphDB::GetMaxSize, "Gets maximum size of the graph.", + pybind11::call_guard()); pybind11::class_( m, "Transaction", @@ -598,12 +708,14 @@ void register_python_api(pybind11::module& m) { "Transactions also track the created iterators and releases all the " "iterators during destruction.") .def( - "__enter__", [&](Transaction& r) -> Transaction& { return r; }, "Init Transaction.") + "__enter__", [&](Transaction& r) -> Transaction& { return r; }, "Init Transaction.", + pybind11::call_guard()) .def( "__exit__", [&](Transaction& t, pybind11::object exc_type, pybind11::object exc_value, pybind11::object traceback) { t.Abort(); }, - "Aborts this transaction if it has not been committed.") + "Aborts this transaction if it has not been committed.", + pybind11::call_guard()) .def( "VertexToString", [](Transaction& a, int64_t vid) { @@ -611,7 +723,8 @@ void register_python_api(pybind11::module& m) { return vit.ToString(); }, "Returns the string representation of the vertex specified by vid.", - pybind11::arg("vid")) + pybind11::arg("vid"), + pybind11::call_guard()) .def( "DumpGraph", [](Transaction& a) { @@ -621,19 +734,26 @@ void register_python_api(pybind11::module& m) { } std::cout << "}\n"; }, - "Prints the string representation of the WHOLE graph to stdout.") - .def("Commit", &Transaction::Commit) - .def("Abort", &Transaction::Abort) - .def("IsValid", &Transaction::IsValid) - .def("IsReadOnly", &Transaction::IsReadOnly) + "Prints the string representation of the WHOLE graph to stdout.", + pybind11::call_guard()) + .def("Commit", &Transaction::Commit, + pybind11::call_guard()) + .def("Abort", &Transaction::Abort, + pybind11::call_guard()) + .def("IsValid", &Transaction::IsValid, + pybind11::call_guard()) + .def("IsReadOnly", &Transaction::IsReadOnly, + pybind11::call_guard()) .def( "GetVertexIterator", [](Transaction& a) { return a.GetVertexIterator(); }, "Returns a VertexIterator pointing to the first vertex in the graph.", - pybind11::return_value_policy::move) + pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetVertexIterator", [](Transaction& a, int64_t id) { return a.GetVertexIterator(id); }, "Returns a VertexIterator pointing to the vertex specified by vid.", - pybind11::arg("vid"), pybind11::return_value_policy::move) + pybind11::arg("vid"), pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetVertexIterator", [](Transaction& a, int64_t id, bool nearest) { @@ -641,14 +761,16 @@ void register_python_api(pybind11::module& m) { }, "Gets VertexIterator with vertex id.\n" "If nearest==true, go to the first vertex with id >= vid.", - pybind11::arg("vid"), pybind11::arg("nearest"), pybind11::return_value_policy::move) + pybind11::arg("vid"), pybind11::arg("nearest"), pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetOutEdgeIterator", [](Transaction& txn, EdgeUid euid, bool nearest) { return txn.GetOutEdgeIterator(euid, nearest); }, "Gets an OutEdgeIterator pointing to the edge identified by euid.", - pybind11::arg("euid"), pybind11::arg("nearest"), pybind11::return_value_policy::move) + pybind11::arg("euid"), pybind11::arg("nearest"), pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetOutEdgeIterator", [](Transaction& txn, int64_t src, int64_t dst, int16_t label_id) { @@ -656,7 +778,8 @@ void register_python_api(pybind11::module& m) { }, "Gets an OutEdgeIterator from src to dst with label specified by label_id.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_id"), - pybind11::return_value_policy::move) + pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetInEdgeIterator", [](Transaction& txn, EdgeUid euid, bool nearest) { @@ -664,7 +787,8 @@ void register_python_api(pybind11::module& m) { }, "Gets an InEdgeIterator pointing to the in-edge of vertex dst with " "EdgeUid==euid.", - pybind11::arg("euid"), pybind11::arg("nearest"), pybind11::return_value_policy::move) + pybind11::arg("euid"), pybind11::arg("nearest"), pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetInEdgeIterator", [](Transaction& txn, int64_t src, int64_t dst, int16_t label_id) { @@ -672,45 +796,58 @@ void register_python_api(pybind11::module& m) { }, "Gets an InEdgeIterator from src to dst with label specified by label_id.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_id"), - pybind11::return_value_policy::move) - .def("GetNumVertexLabels", &Transaction::GetNumVertexLabels) - .def("GetNumEdgeLabels", &Transaction::GetNumEdgeLabels) - .def("ListVertexLabels", &Transaction::ListVertexLabels) - .def("ListEdgeLabels", &Transaction::ListEdgeLabels) + pybind11::return_value_policy::move, + pybind11::call_guard()) + .def("GetNumVertexLabels", &Transaction::GetNumVertexLabels, + pybind11::call_guard()) + .def("GetNumEdgeLabels", &Transaction::GetNumEdgeLabels, + pybind11::call_guard()) + .def("ListVertexLabels", &Transaction::ListVertexLabels, + pybind11::call_guard()) + .def("ListEdgeLabels", &Transaction::ListEdgeLabels, + pybind11::call_guard()) .def("GetVertexLabelId", &Transaction::GetVertexLabelId, "Gets the vertex label id associated with this label.\n" "GraphDB assigns integer ids to each label. " "Using label id instead of label name can have performance benefits.", - pybind11::arg("label_name")) + pybind11::arg("label_name"), + pybind11::call_guard()) .def("GetEdgeLabelId", &Transaction::GetEdgeLabelId, "Gets the edge label id associated with this label.\n" "GraphDB assigns integer ids to each label. " "Using label id instead of label name can have performance benefits.", - pybind11::arg("label_name")) + pybind11::arg("label_name"), + pybind11::call_guard()) .def("GetVertexSchema", &Transaction::GetVertexSchema, - "Gets the schema specification of the vertex label.", pybind11::arg("label_name")) + "Gets the schema specification of the vertex label.", pybind11::arg("label_name"), + pybind11::call_guard()) .def("GetEdgeSchema", &Transaction::GetEdgeSchema, - "Gets the schema specification of the edge label.", pybind11::arg("label_name")) + "Gets the schema specification of the edge label.", pybind11::arg("label_name"), + pybind11::call_guard()) .def("GetVertexFieldId", &Transaction::GetVertexFieldId, "Gets the vertex field id associated with this (label_id, field_name).\n" "GraphDB assigns integer ids to each field of the same label. " "Using field id instead of field name can have performance benefits.", - pybind11::arg("label_id"), pybind11::arg("field_name")) + pybind11::arg("label_id"), pybind11::arg("field_name"), + pybind11::call_guard()) .def("GetVertexFieldIds", &Transaction::GetVertexFieldIds, "Gets the vertex field ids associated with this (label_id, [field_names]).\n" "GraphDB assigns integer ids to each field of the same label. " "Using field id instead of field name can have performance benefits.", - pybind11::arg("label_id"), pybind11::arg("field_names")) + pybind11::arg("label_id"), pybind11::arg("field_names"), + pybind11::call_guard()) .def("GetEdgeFieldId", &Transaction::GetEdgeFieldId, "Gets the edge field id associated with this (label_id, field_name).\n" "GraphDB assigns integer ids to each field of the same label. " "Using field id instead of field name can have performance benefits.", - pybind11::arg("label_id"), pybind11::arg("field_name")) + pybind11::arg("label_id"), pybind11::arg("field_name"), + pybind11::call_guard()) .def("GetEdgeFieldId", &Transaction::GetEdgeFieldIds, "Gets the edge field ids associated with this (label_id, field_names).\n" "GraphDB assigns integer ids to each field of the same label. " "Using field id instead of field name can have performance benefits.", - pybind11::arg("label_id"), pybind11::arg("field_names")) + pybind11::arg("label_id"), pybind11::arg("field_names"), + pybind11::call_guard()) .def( "AddVertex", [](Transaction& a, const std::string& label_name, @@ -723,7 +860,8 @@ void register_python_api(pybind11::module& m) { "Returns the id of the newly added vertex.\n" "Fields that are not in field_names are considered null.", pybind11::arg("label_name"), pybind11::arg("field_names"), - pybind11::arg("field_value_strings")) + pybind11::arg("field_value_strings"), + pybind11::call_guard()) .def( "AddVertex", [](Transaction& a, const std::string& label_name, @@ -736,7 +874,8 @@ void register_python_api(pybind11::module& m) { "Returns the id of the newly added vertex.\n" "Fields that are not in field_names are considered null.", pybind11::arg("label_name"), pybind11::arg("field_names"), - pybind11::arg("field_values")) + pybind11::arg("field_values"), + pybind11::call_guard()) .def( "AddVertex", [](Transaction& a, size_t label_id, const std::vector& field_ids, @@ -747,7 +886,8 @@ void register_python_api(pybind11::module& m) { "values.\n" "Returns the id of the newly added vertex.\n" "Fields that are not in field_ids are considered null.", - pybind11::arg("label_id"), pybind11::arg("field_ids"), pybind11::arg("field_values")) + pybind11::arg("label_id"), pybind11::arg("field_ids"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "AddVertex", [](Transaction& a, const std::string& label_name, const pybind11::dict& value_dict) { @@ -760,7 +900,8 @@ void register_python_api(pybind11::module& m) { "specified in value_dict.\n" "Returns the id of the newly added vertex.\n" "Fields that are not specified in the dict are considered null.", - pybind11::arg("label_name"), pybind11::arg("value_dict")) + pybind11::arg("label_name"), pybind11::arg("value_dict"), + pybind11::call_guard()) .def( "AddEdge", [](Transaction& a, int64_t src, int64_t dst, const std::string& label_name, @@ -773,7 +914,8 @@ void register_python_api(pybind11::module& m) { "Returns the id of the newly added edge.\n" "Fields that are not in field_names are considered null.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_name"), - pybind11::arg("field_names"), pybind11::arg("field_value_strings")) + pybind11::arg("field_names"), pybind11::arg("field_value_strings"), + pybind11::call_guard()) .def( "AddEdge", [](Transaction& a, int64_t src, int64_t dst, const std::string& label_name, @@ -786,7 +928,8 @@ void register_python_api(pybind11::module& m) { "Returns the id of the newly added edge.\n" "Fields that are not in field_names are considered null.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_name"), - pybind11::arg("field_names"), pybind11::arg("field_values")) + pybind11::arg("field_names"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "AddEdge", [](Transaction& a, int64_t src, int64_t dst, size_t label_id, @@ -798,7 +941,8 @@ void register_python_api(pybind11::module& m) { "Returns the id of the newly added edge.\n" "Fields that are not in field_names are considered null.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_id"), - pybind11::arg("field_ids"), pybind11::arg("field_values")) + pybind11::arg("field_ids"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "AddEdge", [](Transaction& a, int64_t src, int64_t dst, std::string& label, @@ -813,7 +957,8 @@ void register_python_api(pybind11::module& m) { "Returns the id of the newly added edge.\n" "Fields that are not in value_dict are considered null.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_name"), - pybind11::arg("value_dict")) + pybind11::arg("value_dict"), + pybind11::call_guard()) .def( "UpsertEdge", [](Transaction& a, int64_t src, int64_t dst, const std::string& label_name, @@ -828,7 +973,8 @@ void register_python_api(pybind11::module& m) { "Returns True if the edge is created, False if the edge is updated.\n" "Fields that are not in field_names are considered null.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_name"), - pybind11::arg("field_names"), pybind11::arg("field_value_strings")) + pybind11::arg("field_names"), pybind11::arg("field_value_strings"), + pybind11::call_guard()) .def( "UpsertEdge", [](Transaction& a, int64_t src, int64_t dst, size_t label_id, @@ -842,7 +988,8 @@ void register_python_api(pybind11::module& m) { "Returns True if the edge is created, False if the edge is updated.\n" "Fields that are not in field_names are considered null.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_id"), - pybind11::arg("field_ids"), pybind11::arg("field_values")) + pybind11::arg("field_ids"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "UpsertEdge", [](Transaction& a, int64_t src, int64_t dst, const std::string& label_name, @@ -857,7 +1004,8 @@ void register_python_api(pybind11::module& m) { "Returns True if the edge is created, False if the edge is updated.\n" "Fields that are not in field_names are considered null.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_name"), - pybind11::arg("field_names"), pybind11::arg("field_values")) + pybind11::arg("field_names"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "UpsertEdge", [](Transaction& a, int64_t src, int64_t dst, std::string& label, @@ -874,9 +1022,11 @@ void register_python_api(pybind11::module& m) { "Returns True if the edge is created, False if the edge is updated.\n" "Fields that are not in value_dict are considered null.", pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("label_name"), - pybind11::arg("value_dict")) + pybind11::arg("value_dict"), + pybind11::call_guard()) .def("ListVertexIndexes", &Transaction::ListVertexIndexes, - "Gets the list of all the vertex indexes in the DB.") + "Gets the list of all the vertex indexes in the DB.", + pybind11::call_guard()) .def( "GetVertexIndexIterator", [](Transaction& a, size_t label_id, size_t field_id, const FieldData& key_start, @@ -892,7 +1042,8 @@ void register_python_api(pybind11::module& m) { "key_start is a FieldData containing the minimum indexed value.\n" "key_end is a FieldData containing the maximum indexed value.", pybind11::arg("label_id"), pybind11::arg("field_id"), pybind11::arg("key_start"), - pybind11::arg("key_end"), pybind11::return_value_policy::move) + pybind11::arg("key_end"), pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetVertexIndexIterator", [](Transaction& a, size_t label_id, size_t field_id, const FieldData& value) { @@ -904,7 +1055,8 @@ void register_python_api(pybind11::module& m) { "field_id specifies the id of the indexed field.\n" "value is a FieldData containing the indexed value.", pybind11::arg("label_id"), pybind11::arg("field_id"), pybind11::arg("value"), - pybind11::return_value_policy::move) + pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetVertexIndexIterator", [](Transaction& a, const std::string& label, const std::string& field, @@ -923,7 +1075,8 @@ void register_python_api(pybind11::module& m) { "value.", pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("key_start_string"), pybind11::arg("key_end_string"), - pybind11::return_value_policy::move) + pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetVertexIndexIterator", [](Transaction& a, const std::string& label, const std::string& field, @@ -939,7 +1092,8 @@ void register_python_api(pybind11::module& m) { "key_start is a FieldData containing the minimum indexed value.\n" "key_end is a FieldData containing the maximum indexed value.", pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("key_start"), - pybind11::arg("key_end"), pybind11::return_value_policy::move) + pybind11::arg("key_end"), pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetVertexIndexIterator", [](Transaction& a, const std::string& label, const std::string& field, @@ -952,7 +1106,8 @@ void register_python_api(pybind11::module& m) { "field_id specifies the name of the indexed field.\n" "value_string is the string representation of the indexed value.", pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("value_string"), - pybind11::return_value_policy::move) + pybind11::return_value_policy::move, + pybind11::call_guard()) .def( "GetVertexIndexIterator", [](Transaction& a, const std::string& label, const std::string& field, @@ -965,10 +1120,12 @@ void register_python_api(pybind11::module& m) { "field_id specifies the name of the indexed field.\n" "value is a FieldData containing the indexed value.", pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("value"), - pybind11::return_value_policy::move) + pybind11::return_value_policy::move, + pybind11::call_guard()) .def("IsVertexIndexed", &Transaction::IsVertexIndexed, "Tells whether the specified field is indexed.", pybind11::arg("label_name"), - pybind11::arg("field_name")) + pybind11::arg("field_name"), + pybind11::call_guard()) .def( "GetVertexByUniqueIndex", [](Transaction& txn, const std::string& label_name, const std::string& field_name, @@ -982,7 +1139,8 @@ void register_python_api(pybind11::module& m) { "field_value_string specifies the string representation of the " "indexed field value.", pybind11::arg("label_name"), pybind11::arg("field_name"), - pybind11::arg("field_value_string")) + pybind11::arg("field_value_string"), + pybind11::call_guard()) .def( "GetVertexByUniqueIndex", [](Transaction& txn, const std::string& label_name, const std::string& field_name, @@ -995,7 +1153,8 @@ void register_python_api(pybind11::module& m) { "label_name specifies the name of the indexed label.\n" "field_name specifies the name of the indexed field.\n" "field_value specifies the indexed field value.", - pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("field_value")) + pybind11::arg("label_name"), pybind11::arg("field_name"), pybind11::arg("field_value"), + pybind11::call_guard()) .def( "GetVertexByUniqueIndex", [](Transaction& txn, size_t label_id, size_t field_id, const FieldData& field_value) { @@ -1006,7 +1165,8 @@ void register_python_api(pybind11::module& m) { "label_id specifies the id of the indexed label.\n" "field_id specifies the id of the indexed field.\n" "field_value is a FieldData specifying the indexed field value.", - pybind11::arg("label_id"), pybind11::arg("field_id"), pybind11::arg("field_value")); + pybind11::arg("label_id"), pybind11::arg("field_id"), pybind11::arg("field_value"), + pybind11::call_guard()); // Vertex iterator pybind11::class_( @@ -1015,90 +1175,109 @@ void register_python_api(pybind11::module& m) { "through multiple vertices.\n" "Vertexes are sorted in ascending order of the their ids.") .def( - "__enter__", [&](VertexIterator& r) -> VertexIterator& { return r; }, "Init iterator.") + "__enter__", [&](VertexIterator& r) -> VertexIterator& { return r; }, "Init iterator.", + pybind11::call_guard()) .def( "__exit__", [&](VertexIterator& r, pybind11::object exc_type, pybind11::object exc_value, pybind11::object traceback) { r.Close(); }, - "Delete iterator") - .def("Next", &VertexIterator::Next, "Goes to the next vertex with id>{current_vid}.") + "Delete iterator", + pybind11::call_guard()) + .def("Next", &VertexIterator::Next, "Goes to the next vertex with id>{current_vid}.", + pybind11::call_guard()) .def("Goto", &VertexIterator::Goto, "Goes to the vertex specified by vid.\n" "If nearest==true, go to the nearest vertex with id>=vid.", - pybind11::arg("vid"), pybind11::arg("nearest")) + pybind11::arg("vid"), pybind11::arg("nearest"), + pybind11::call_guard()) .def("GetId", &VertexIterator::GetId, "Gets the integer id of this vertex.\n" - "GraphDB assigns an integer id for each vertex.") + "GraphDB assigns an integer id for each vertex.", + pybind11::call_guard()) .def( "GetOutEdgeIterator", [](VertexIterator& vit) { return vit.GetOutEdgeIterator(); }, "Gets an OutEgdeIterator pointing to the first out-going edge of " - "this edge.") + "this edge.", + pybind11::call_guard()) .def( "GetOutEdgeIterator", [](VertexIterator& vit, const EdgeUid& euid, bool nearest) { return vit.GetOutEdgeIterator(euid, nearest); }, "Gets an OutEgdeIterator pointing to the out-edge of this vertex " - "with EdgeUid==euid.") + "with EdgeUid==euid.", + pybind11::call_guard()) .def( "GetInEdgeIterator", [](VertexIterator& vit) { return vit.GetInEdgeIterator(); }, "Gets an InEgdeIterator pointing to the first in-coming edge of this " - "edge.") + "edge.", + pybind11::call_guard()) .def( "GetInEdgeIterator", [](VertexIterator& vit, const EdgeUid euid, bool nearest) { return vit.GetInEdgeIterator(euid, nearest); }, "Gets an InEgdeIterator pointing to the in-edge of this vertex with " - "EdgeUid==euid.") - .def("IsValid", &VertexIterator::IsValid) + "EdgeUid==euid.", + pybind11::call_guard()) + .def("IsValid", &VertexIterator::IsValid, + pybind11::call_guard()) .def("GetLabel", &VertexIterator::GetLabel, "Gets the label name of current vertex.", - pybind11::return_value_policy::copy) - .def("GetLabelId", &VertexIterator::GetLabelId, "Gets the label id of current vertex.") + pybind11::return_value_policy::copy, + pybind11::call_guard()) + .def("GetLabelId", &VertexIterator::GetLabelId, "Gets the label id of current vertex.", + pybind11::call_guard()) .def( "GetField", [](VertexIterator& vit, const std::string& field_name) { return FieldDataToPyObj(vit.GetField(field_name)); }, "Gets the field value of the field specified by field_name.", - pybind11::arg("field_name")) + pybind11::arg("field_name"), + pybind11::call_guard()) .def( "__getitem__", [](VertexIterator& vit, const std::string& field_name) { return FieldDataToPyObj(vit.GetField(field_name)); }, "Gets the field value of the field specified by field_name.", - pybind11::arg("field_name")) + pybind11::arg("field_name"), + pybind11::call_guard()) .def( "GetField", [](VertexIterator& vit, size_t field_id) { return FieldDataToPyObj(vit.GetField(field_id)); }, - "Gets the field value of the field specified by field_id.", pybind11::arg("field_id")) + "Gets the field value of the field specified by field_id.", pybind11::arg("field_id"), + pybind11::call_guard()) .def( "__getitem__", [](VertexIterator& vit, size_t field_id) { return FieldDataToPyObj(vit.GetField(field_id)); }, - "Gets the field value of the field specified by field_id.", pybind11::arg("field_id")) + "Gets the field value of the field specified by field_id.", pybind11::arg("field_id"), + pybind11::call_guard()) .def( "GetFields", [](VertexIterator& vit, const std::vector& field_names) { return FieldDataVectorToPyList(vit.GetFields(field_names)); }, "Gets the field values of the fields specified by field_names.", - pybind11::arg("field_names")) + pybind11::arg("field_names"), + pybind11::call_guard()) .def( "GetFields", [](VertexIterator& vit, const std::vector& field_ids) { return FieldDataVectorToPyList(vit.GetFields(field_ids)); }, "Gets the field values of the fields specified by field_ids.", - pybind11::arg("field_ids")) + pybind11::arg("field_ids"), + pybind11::call_guard()) .def( "GetAllFields", [](VertexIterator& vit) { return FieldDataMapToPyDict(vit.GetAllFields()); }, - "Gets all the field values and return as a dict.") + "Gets all the field values and return as a dict.", + pybind11::call_guard()) .def( "SetField", [](VertexIterator& vit, const std::string& field_name, @@ -1107,7 +1286,8 @@ void register_python_api(pybind11::module& m) { return vit.SetField(field_name, field_value); }, "Sets the specified field", pybind11::arg("field_name"), - pybind11::arg("field_value_object")) + pybind11::arg("field_value_object"), + pybind11::call_guard()) .def( "SetFields", [](VertexIterator& vit, const std::vector& field_names, @@ -1118,7 +1298,8 @@ void register_python_api(pybind11::module& m) { "string representation.\n" "field_names specifies the names of the fields to set.\n" "field_value_strings are the field values in string representation.", - pybind11::arg("field_names"), pybind11::arg("field_value_strings")) + pybind11::arg("field_names"), pybind11::arg("field_value_strings"), + pybind11::call_guard()) .def( "SetFields", [](VertexIterator& vit, const std::vector& field_names, @@ -1128,7 +1309,8 @@ void register_python_api(pybind11::module& m) { "Sets the fields specified by field_names with new values.\n" "field_names specifies the names of the fields to set.\n" "field_values are the FieldData containing field values.", - pybind11::arg("field_names"), pybind11::arg("field_values")) + pybind11::arg("field_names"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "SetFields", [](VertexIterator& vit, const pybind11::dict& value_dict) { @@ -1139,7 +1321,8 @@ void register_python_api(pybind11::module& m) { }, "Sets the fields with values as specified in value_dict.\n" "value_dict specifies the field_name:value dict.", - pybind11::arg("value_dict")) + pybind11::arg("value_dict"), + pybind11::call_guard()) .def( "SetFields", [](VertexIterator& vit, const std::vector& field_ids, @@ -1149,7 +1332,8 @@ void register_python_api(pybind11::module& m) { "Sets the fields specified by field_ids with field values.\n" "field_ids specifies the ids of the fields to set.\n" "field_values are the field values to be set.", - pybind11::arg("field_ids"), pybind11::arg("field_values")) + pybind11::arg("field_ids"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "ListSrcVids", [](VertexIterator& vit, size_t n_limit) { @@ -1161,7 +1345,8 @@ void register_python_api(pybind11::module& m) { "n_limit specifies the maximum number of src vids to return.\n" "Returns a tuple containing a list of vids and a bool value " "indicating whether the limit is exceeded.", - pybind11::arg("n_limit") = std::numeric_limits::max()) + pybind11::arg("n_limit") = std::numeric_limits::max(), + pybind11::call_guard()) .def( "ListDstVids", [](VertexIterator& vit, size_t n_limit) { @@ -1173,7 +1358,8 @@ void register_python_api(pybind11::module& m) { "n_limit specifies the maximum number of vids to return.\n" "Returns a tuple containing a list of vids and a bool value " "indicating whether the limit is exceeded.", - pybind11::arg("n_limit") = std::numeric_limits::max()) + pybind11::arg("n_limit") = std::numeric_limits::max(), + pybind11::call_guard()) .def( "GetNumInEdges", [](VertexIterator& vit, size_t n_limit) { @@ -1185,7 +1371,8 @@ void register_python_api(pybind11::module& m) { "n_limit specifies the maximum number of edges to scan.\n" "Returns a tuple containing the number of in-edges and a bool value " "indicating whether the limit is exceeded.", - pybind11::arg("n_limit") = std::numeric_limits::max()) + pybind11::arg("n_limit") = std::numeric_limits::max(), + pybind11::call_guard()) .def( "GetNumOutEdges", [](VertexIterator& vit, size_t n_limit) { @@ -1197,7 +1384,8 @@ void register_python_api(pybind11::module& m) { "n_limit specifies the maximum number of vids to scan." "Returns a tuple containing the number of out-edges and a bool value " "indicating whether the limit is exceeded.", - pybind11::arg("n_limit") = std::numeric_limits::max()) + pybind11::arg("n_limit") = std::numeric_limits::max(), + pybind11::call_guard()) .def( "Delete", [](::lgraph_api::VertexIterator& vit) { @@ -1206,10 +1394,12 @@ void register_python_api(pybind11::module& m) { return std::make_tuple(ni, no); }, "Deletes current vertex.\n" - "The iterator will point to the next vertex if there is any.") + "The iterator will point to the next vertex if there is any.", + pybind11::call_guard()) .def("ToString", &VertexIterator::ToString, "Returns the string representation of current vertex, including " - "properties and edges."); + "properties and edges.", + pybind11::call_guard()); // OutEdgeIterator pybind11::class_( @@ -1219,70 +1409,88 @@ void register_python_api(pybind11::module& m) { "Out-going edges are sorted in (src, lid, dst, eid) order.") .def( "__enter__", [&](OutEdgeIterator& r) -> OutEdgeIterator& { return r; }, - "Init iterator.") + "Init iterator.", + pybind11::call_guard()) .def( "__exit__", [&](OutEdgeIterator& r, pybind11::object exc_type, pybind11::object exc_value, pybind11::object traceback) { r.Close(); }, - "Delete iterator") + "Delete iterator", + pybind11::call_guard()) .def("Goto", &OutEdgeIterator::Goto, "Goes to the out edge specified by euid.\n", - pybind11::arg("euid"), pybind11::arg("nearest") = false) + pybind11::arg("euid"), pybind11::arg("nearest") = false, + pybind11::call_guard()) .def("Next", &OutEdgeIterator::Next, "Goes to the next out edge from current source vertex.\n" - "If there is no more out edge left, the iterator becomes invalid.") - .def("IsValid", &OutEdgeIterator::IsValid, "Tells whether the iterator is valid.") - .def("GetUid", &OutEdgeIterator::GetUid, "Returns the EdgeUid of the edge.") - .def("GetSrc", &OutEdgeIterator::GetSrc, "Returns the id of the source vertex.") - .def("GetDst", &OutEdgeIterator::GetDst, "Returns the id of the destination vertex.") + "If there is no more out edge left, the iterator becomes invalid.", + pybind11::call_guard()) + .def("IsValid", &OutEdgeIterator::IsValid, "Tells whether the iterator is valid.", + pybind11::call_guard()) + .def("GetUid", &OutEdgeIterator::GetUid, "Returns the EdgeUid of the edge.", + pybind11::call_guard()) + .def("GetSrc", &OutEdgeIterator::GetSrc, "Returns the id of the source vertex.", + pybind11::call_guard()) + .def("GetDst", &OutEdgeIterator::GetDst, "Returns the id of the destination vertex.", + pybind11::call_guard()) .def("GetEdgeId", &OutEdgeIterator::GetEdgeId, "Returns the id of current edge. Edge id is unique across the same " - "(src, dst) set.") + "(src, dst) set.", + pybind11::call_guard()) .def("GetLabel", &OutEdgeIterator::GetLabel, "Returns the name of the edge label.", - pybind11::return_value_policy::copy) - .def("GetLabelId", &OutEdgeIterator::GetLabelId, "Returns the id of the edge label.") + pybind11::return_value_policy::copy, + pybind11::call_guard()) + .def("GetLabelId", &OutEdgeIterator::GetLabelId, "Returns the id of the edge label.", + pybind11::call_guard()) .def( "GetField", [](OutEdgeIterator& eit, const std::string& field_name) { return FieldDataToPyObj(eit.GetField(field_name)); }, "Gets the field value of the field specified by field_name.", - pybind11::arg("field_name")) + pybind11::arg("field_name"), + pybind11::call_guard()) .def( "__getitem__", [](OutEdgeIterator& eit, const std::string& field_name) { return FieldDataToPyObj(eit.GetField(field_name)); }, "Gets the field value of the field specified by field_name.", - pybind11::arg("field_name")) + pybind11::arg("field_name"), + pybind11::call_guard()) .def( "GetField", [](OutEdgeIterator& eit, size_t field_id) { return FieldDataToPyObj(eit.GetField(field_id)); }, - "Gets the field value of the field specified by field_id.", pybind11::arg("field_id")) + "Gets the field value of the field specified by field_id.", pybind11::arg("field_id"), + pybind11::call_guard()) .def( "__getitem__", [](OutEdgeIterator& eit, size_t field_id) { return FieldDataToPyObj(eit.GetField(field_id)); }, - "Gets the field value of the field specified by field_id.", pybind11::arg("field_id")) + "Gets the field value of the field specified by field_id.", pybind11::arg("field_id"), + pybind11::call_guard()) .def( "GetFields", [](OutEdgeIterator& eit, const std::vector& field_names) { return FieldDataVectorToPyList(eit.GetFields(field_names)); }, "Gets field values of the fields specified by field_names.", - pybind11::arg("field_names")) + pybind11::arg("field_names"), + pybind11::call_guard()) .def( "GetFields", [](OutEdgeIterator& eit, const std::vector& field_ids) { return FieldDataVectorToPyList(eit.GetFields(field_ids)); }, - "Gets field values of the fields specified by field_ids.", pybind11::arg("field_ids")) + "Gets field values of the fields specified by field_ids.", pybind11::arg("field_ids"), + pybind11::call_guard()) .def( "GetAllFields", [](OutEdgeIterator& vit) { return FieldDataMapToPyDict(vit.GetAllFields()); }, - "Gets all the field values and return as a dict.") + "Gets all the field values and return as a dict.", + pybind11::call_guard()) .def( "SetField", [](OutEdgeIterator& eit, const std::string& field_name, @@ -1291,7 +1499,8 @@ void register_python_api(pybind11::module& m) { return eit.SetField(field_name, field_value); }, "Sets the specified field", pybind11::arg("field_name"), - pybind11::arg("field_value_object")) + pybind11::arg("field_value_object"), + pybind11::call_guard()) .def( "SetFields", [](OutEdgeIterator& eit, const std::vector& field_names, @@ -1302,7 +1511,8 @@ void register_python_api(pybind11::module& m) { "string representation.\n" "field_names specifies the names of the fields to set.\n" "field_value_strings are the field values in string representation.", - pybind11::arg("field_names"), pybind11::arg("field_value_strings")) + pybind11::arg("field_names"), pybind11::arg("field_value_strings"), + pybind11::call_guard()) .def( "SetFields", [](OutEdgeIterator& eit, const std::vector& field_names, @@ -1313,7 +1523,8 @@ void register_python_api(pybind11::module& m) { "string representation.\n" "field_names specifies the names of the fields to set.\n" "field_values are FieldData containing the field values.", - pybind11::arg("field_names"), pybind11::arg("field_values")) + pybind11::arg("field_names"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "SetFields", [](OutEdgeIterator& eit, const pybind11::dict& value_dict) { @@ -1324,7 +1535,8 @@ void register_python_api(pybind11::module& m) { }, "Sets the field values as specified in value_dict.\n" "value_dict specifies the field_name:value dictionary.", - pybind11::arg("value_dict")) + pybind11::arg("value_dict"), + pybind11::call_guard()) .def( "SetFields", [](OutEdgeIterator& eit, const std::vector& field_ids, @@ -1334,12 +1546,15 @@ void register_python_api(pybind11::module& m) { "Sets the fields specified by field_ids with field values.\n" "field_ids specifies the ids of the fields to set.\n" "field_values are the field values to be set.", - pybind11::arg("field_ids"), pybind11::arg("field_values")) + pybind11::arg("field_ids"), pybind11::arg("field_values"), + pybind11::call_guard()) .def("Delete", &OutEdgeIterator::Delete, "Deletes current edge.\n" - "The iterator will point to the next out edge if there is any.") + "The iterator will point to the next out edge if there is any.", + pybind11::call_guard()) .def("ToString", &OutEdgeIterator::ToString, - "Returns the string representation of current edge."); + "Returns the string representation of current edge.", + pybind11::call_guard()); // InEdgeIterator pybind11::class_( @@ -1348,70 +1563,88 @@ void register_python_api(pybind11::module& m) { "the destination vertex.\n" "Incoming edges are sorted in (dst, label, src, eid) order.") .def( - "__enter__", [&](InEdgeIterator& r) -> InEdgeIterator& { return r; }, "Init iterator.") + "__enter__", [&](InEdgeIterator& r) -> InEdgeIterator& { return r; }, "Init iterator.", + pybind11::call_guard()) .def( "__exit__", [&](InEdgeIterator& r, pybind11::object exc_type, pybind11::object exc_value, pybind11::object traceback) { r.Close(); }, - "Delete iterator") + "Delete iterator", + pybind11::call_guard()) .def("Goto", &InEdgeIterator::Goto, "Goes to the in edge specified by euid.\n", - pybind11::arg("euid"), pybind11::arg("nearest")) + pybind11::arg("euid"), pybind11::arg("nearest"), + pybind11::call_guard()) .def("Next", &InEdgeIterator::Next, "Goes to the next in edge to current destination vertex.\n" - "If there is no more in edge left, the iterator becomes invalid.") - .def("IsValid", &InEdgeIterator::IsValid, "Tells whether the iterator is valid.") - .def("GetUid", &InEdgeIterator::GetUid, "Returns the EdgeUid of the edge.") - .def("GetSrc", &InEdgeIterator::GetSrc, "Returns the id of the source vertex.") - .def("GetDst", &InEdgeIterator::GetDst, "Returns the id of the destination vertex.") + "If there is no more in edge left, the iterator becomes invalid.", + pybind11::call_guard()) + .def("IsValid", &InEdgeIterator::IsValid, "Tells whether the iterator is valid.", + pybind11::call_guard()) + .def("GetUid", &InEdgeIterator::GetUid, "Returns the EdgeUid of the edge.", + pybind11::call_guard()) + .def("GetSrc", &InEdgeIterator::GetSrc, "Returns the id of the source vertex.", + pybind11::call_guard()) + .def("GetDst", &InEdgeIterator::GetDst, "Returns the id of the destination vertex.", + pybind11::call_guard()) .def("GetEdgeId", &InEdgeIterator::GetEdgeId, "Returns the id of current edge. Edge id is unique across the same " - "(src, dst) set.") + "(src, dst) set.", + pybind11::call_guard()) .def("GetLabel", &InEdgeIterator::GetLabel, "Returns the name of the edge label.", - pybind11::return_value_policy::copy) - .def("GetLabelId", &InEdgeIterator::GetLabelId, "Returns the id of the edge label.") + pybind11::return_value_policy::copy, + pybind11::call_guard()) + .def("GetLabelId", &InEdgeIterator::GetLabelId, "Returns the id of the edge label.", + pybind11::call_guard()) .def( "GetField", [](InEdgeIterator& eit, const std::string& field_name) { return FieldDataToPyObj(eit.GetField(field_name)); }, "Gets the field value of the field specified by field_name.", - pybind11::arg("field_name")) + pybind11::arg("field_name"), + pybind11::call_guard()) .def( "__getitem__", [](InEdgeIterator& eit, const std::string& field_name) { return FieldDataToPyObj(eit.GetField(field_name)); }, "Gets the field value of the field specified by field_name.", - pybind11::arg("field_name")) + pybind11::arg("field_name"), + pybind11::call_guard()) .def( "GetField", [](InEdgeIterator& eit, size_t field_id) { return FieldDataToPyObj(eit.GetField(field_id)); }, - "Gets the field value of the field specified by field_id.", pybind11::arg("field_id")) + "Gets the field value of the field specified by field_id.", pybind11::arg("field_id"), + pybind11::call_guard()) .def( "__getitem__", [](InEdgeIterator& eit, size_t field_id) { return FieldDataToPyObj(eit.GetField(field_id)); }, - "Gets the field value of the field specified by field_id.", pybind11::arg("field_id")) + "Gets the field value of the field specified by field_id.", pybind11::arg("field_id"), + pybind11::call_guard()) .def( "GetFields", [](InEdgeIterator& eit, const std::vector& field_names) { return FieldDataVectorToPyList(eit.GetFields(field_names)); }, "Gets field values of the fields specified by field_names.", - pybind11::arg("field_names")) + pybind11::arg("field_names"), + pybind11::call_guard()) .def( "GetFields", [](InEdgeIterator& eit, const std::vector& field_ids) { return FieldDataVectorToPyList(eit.GetFields(field_ids)); }, - "Gets field values of the fields specified by field_ids.", pybind11::arg("field_ids")) + "Gets field values of the fields specified by field_ids.", pybind11::arg("field_ids"), + pybind11::call_guard()) .def( "GetAllFields", [](InEdgeIterator& vit) { return FieldDataMapToPyDict(vit.GetAllFields()); }, - "Gets all the field values and return as a dict.") + "Gets all the field values and return as a dict.", + pybind11::call_guard()) .def( "SetField", [](InEdgeIterator& eit, const std::string& field_name, @@ -1420,7 +1653,8 @@ void register_python_api(pybind11::module& m) { return eit.SetField(field_name, field_value); }, "Sets the specified field", pybind11::arg("field_name"), - pybind11::arg("field_value_object")) + pybind11::arg("field_value_object"), + pybind11::call_guard()) .def( "SetFields", [](InEdgeIterator& eit, const std::vector& field_names, @@ -1431,7 +1665,8 @@ void register_python_api(pybind11::module& m) { "string representation.\n" "field_names specifies the names of the fields to set.\n" "field_value_strings are the field values in string representation.", - pybind11::arg("field_names"), pybind11::arg("field_value_strings")) + pybind11::arg("field_names"), pybind11::arg("field_value_strings"), + pybind11::call_guard()) .def( "SetFields", [](InEdgeIterator& vit, const std::vector& field_names, @@ -1441,7 +1676,8 @@ void register_python_api(pybind11::module& m) { "Sets the fields specified by field_names with new values.\n" "field_names specifies the names of the fields to set.\n" "field_values are the FieldData containing field values.", - pybind11::arg("field_names"), pybind11::arg("field_values")) + pybind11::arg("field_names"), pybind11::arg("field_values"), + pybind11::call_guard()) .def( "SetFields", [](InEdgeIterator& vit, const pybind11::dict& value_dict) { @@ -1452,7 +1688,8 @@ void register_python_api(pybind11::module& m) { }, "Sets the fields with values as specified in value_dict.\n" "value_dict specifies the field_name:value dict.", - pybind11::arg("value_dict")) + pybind11::arg("value_dict"), + pybind11::call_guard()) .def( "SetFields", [](InEdgeIterator& eit, const std::vector& field_ids, @@ -1462,12 +1699,15 @@ void register_python_api(pybind11::module& m) { "Sets the fields specified by field_ids with field values.\n" "field_ids specifies the ids of the fields to set.\n" "field_values are the field values to be set.", - pybind11::arg("field_ids"), pybind11::arg("field_values")) + pybind11::arg("field_ids"), pybind11::arg("field_values"), + pybind11::call_guard()) .def("Delete", &InEdgeIterator::Delete, "Deletes current edge.\n" - "The iterator will point to the next out edge if there is any.") + "The iterator will point to the next out edge if there is any.", + pybind11::call_guard()) .def("ToString", &InEdgeIterator::ToString, - "Returns the string representation of current edge."); + "Returns the string representation of current edge.", + pybind11::call_guard()); // VertexIndexIterator pybind11::class_( @@ -1476,23 +1716,29 @@ void register_python_api(pybind11::module& m) { "Vertex ids are sorted in ascending order of (index_value, vertex_id).") .def( "__enter__", [&](VertexIndexIterator& r) -> VertexIndexIterator& { return r; }, - "Init iterator.") + "Init iterator.", + pybind11::call_guard()) .def( "__exit__", [&](VertexIndexIterator& r, pybind11::object exc_type, pybind11::object exc_value, pybind11::object traceback) { r.Close(); }, - "Delete iterator") + "Delete iterator", + pybind11::call_guard()) .def("Next", &VertexIndexIterator::Next, "Goes to the next indexed vid.\n" "If there is no more vertex within the specified key range, the " - "iterator becomes invalid.") - .def("IsValid", &VertexIndexIterator::IsValid, "Tells whether this iterator is valid.") + "iterator becomes invalid.", + pybind11::call_guard()) + .def("IsValid", &VertexIndexIterator::IsValid, "Tells whether this iterator is valid.", + pybind11::call_guard()) .def("GetIndexValue", &VertexIndexIterator::GetIndexValue, "Gets the indexed value.\n" "Since vertex ids are sorted in (index_value, vertex_id) order, " - "calling Next() may change the current indexed value.") + "calling Next() may change the current indexed value.", + pybind11::call_guard()) .def("GetVid", &VertexIndexIterator::GetVid, - "Gets the id of the vertex currently pointed to."); + "Gets the id of the vertex currently pointed to.", + pybind11::call_guard()); //==================================== // Register SigSpec //==================================== @@ -1519,7 +1765,8 @@ void register_python_api(pybind11::module& m) { .def_readwrite("type", &lgraph_api::Parameter::type, "type of the parameter"); pybind11::class_(m, "SigSpec") - .def(pybind11::init&, const std::vector&>()) + .def(pybind11::init&, const std::vector&>(), + pybind11::call_guard()) .def( "serialize", [](lgraph_api::SigSpec& sig_spec) -> std::string { @@ -1531,7 +1778,8 @@ void register_python_api(pybind11::module& m) { buffer.DetachBuf((void **)&owned_buffer, &size); return {owned_buffer, size }; }, - "Serialize SigSpec into string") + "Serialize SigSpec into string", + pybind11::call_guard()) .def_readwrite("input_list", &lgraph_api::SigSpec::input_list, "input parameter list of the signature") .def_readwrite("result_list", &lgraph_api::SigSpec::result_list, @@ -1549,10 +1797,13 @@ void register_lgraph_plugin(pybind11::module& m) { boost::interprocess::message_queue mq(boost::interprocess::open_only, pipename.c_str()); lgraph::python_plugin::TaskInput input; - input.ReadFromMessageQueue(mq, 0); + while (!input.ReadFromMessageQueue(mq, 100)) { + SignalsGuard signalsGuard; + } return input; }, - "Read TaskInput from message queue") + "Read TaskInput from message queue", + pybind11::call_guard()) .def_readonly("user", &lgraph::python_plugin::TaskInput::user, "user to be used") .def_readonly("graph", &lgraph::python_plugin::TaskInput::graph, "Graph to be used") .def_readonly("plugin_dir", &lgraph::python_plugin::TaskInput::plugin_dir, @@ -1561,7 +1812,8 @@ void register_lgraph_plugin(pybind11::module& m) { .def( "get_input", [](const lgraph::python_plugin::TaskInput& in) { return pybind11::bytes(in.input); }, - "The input byte array") + "The input byte array", + pybind11::call_guard()) .def_readonly("read_only", &lgraph::python_plugin::TaskInput::read_only); pybind11::class_(m, "TaskOutput") @@ -1576,7 +1828,8 @@ void register_lgraph_plugin(pybind11::module& m) { out.output = output; out.WriteToMessageQueue(mq); }, - "Write TaskOutput to stdout"); + "Write TaskOutput to stdout", + pybind11::call_guard()); } class EdgeListWriter { @@ -1620,9 +1873,11 @@ void register_gemini_adapter(pybind11::module& m) { "Open a new file for writing out a list of edges.\n" "`binary` denotes whether the output should be in binary (true) or " "text (false) format.", - pybind11::arg("path"), pybind11::arg("binary") = true) + pybind11::arg("path"), pybind11::arg("binary") = true, + pybind11::call_guard()) .def("EmitEdge", &EdgeListWriter::EmitEdge, "Append an edge to the opened file.", - pybind11::arg("src"), pybind11::arg("dst")) + pybind11::arg("src"), pybind11::arg("dst"), + pybind11::call_guard()) .def( "EmitWeightedEdge", [](EdgeListWriter& self, size_t src, size_t dst, const pybind11::object& weight) { @@ -1638,8 +1893,10 @@ void register_gemini_adapter(pybind11::module& m) { }, "Append a weighted edge to the opened file.\n" "Only {bool, int, float} weights are supported.", - pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("weight")) - .def("Close", &EdgeListWriter::Close, "Close the edge list file."); + pybind11::arg("src"), pybind11::arg("dst"), pybind11::arg("weight"), + pybind11::call_guard()) + .def("Close", &EdgeListWriter::Close, "Close the edge list file.", + pybind11::call_guard()); } // Declare the python api with pybind11 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6aeba1133d..3201abb88d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -114,9 +114,9 @@ add_executable(unit_test test_olap_vertex_traversal.cpp test_lgraph_log.cpp test_detach_property.cpp - test_ha.cpp + test_ha_base.cpp test_global_ha_config.cpp - test_ha_consistency.cpp + test_ha.cpp ${LGRAPH_ROOT_DIR}/src/client/cpp/rpc/lgraph_rpc_client.cpp ${LGRAPH_ROOT_DIR}/src/client/cpp/restful/rest_client.cpp ) diff --git a/test/integration/ha_client_python_util.py b/test/integration/ha_client_python_util.py index dda628ddca..a7460864ce 100644 --- a/test/integration/ha_client_python_util.py +++ b/test/integration/ha_client_python_util.py @@ -7,6 +7,61 @@ log = "" DEFAULT_ADMIN_NAME = "admin" DEFAULT_ADMIN_PASS = "73@TuGraph" +IMPORT_SCHEMA = '''{"schema" : + [ + { + "label" : "Person", + "type" : "VERTEX", + "primary" : "name", + "properties" : [ + {"name" : "name", "type":"STRING"}, + {"name" : "birthyear", "type":"INT16", "optional":true}, + {"name" : "phone", "type":"INT16","unique":true, "index":true} + ] + }, + { + "label" : "Film", + "type" : "VERTEX", + "primary" : "title", + "properties" : [ + {"name" : "title", "type":"STRING"} + ] + }, + { + "label": "PLAY_IN", + "type": "EDGE", + "properties": [ + {"name": "role", "type": "STRING", "optional": true} + ], + "constraints": [ ["Person", "Film"] ] + } + ] + } + ''' +IMPORT_DATA_PERSON_DESC = '''{"files": [ + { + "columns": ["name", "birthyear", "phone"], + "format": "CSV", + "header": 0, + "label": "Person" + }] + } + ''' +IMPORT_DATA_PERSON = ''' + Rachel Kempson,1910,10086 + Michael Redgrave,1908,10087 + Vanessa Redgrave,1937,10088 + Corin Redgrave,1939,10089 + Liam Neeson,1952,10090 + Natasha Richardson,1963,10091 + Richard Harris,1930,10092 + Dennis Quaid,1954,10093 + Lindsay Lohan,1986,10094 + Jemma Redgrave,1965,10095 + Roy Redgrave,1873,10096 + John Williams,1932,10097 + Christopher Nolan,1970,10098 + ''' def importer(host, import_ort): client = start_ha_client(host, "29092") diff --git a/test/integration/test_ha_python_client.py b/test/integration/test_ha_python_client.py index 8402037999..6d30109d4c 100644 --- a/test/integration/test_ha_python_client.py +++ b/test/integration/test_ha_python_client.py @@ -45,38 +45,7 @@ def edge_result_check(res): assert "PLAY_IN" == json_object["label"] log.info("----------------testImportSchemaFromContent--------------------") self.client.callCypher("CALL db.dropDB()", "default", timeout=10) - schema = '''{"schema" : - [ - { - "label" : "Person", - "type" : "VERTEX", - "primary" : "name", - "properties" : [ - {"name" : "name", "type":"STRING"}, - {"name" : "birthyear", "type":"INT16", "optional":true}, - {"name" : "phone", "type":"INT16","unique":true, "index":true} - ] - }, - { - "label" : "Film", - "type" : "VERTEX", - "primary" : "title", - "properties" : [ - {"name" : "title", "type":"STRING"} - ] - }, - { - "label": "PLAY_IN", - "type": "EDGE", - "properties": [ - {"name": "role", "type": "STRING", "optional": true} - ], - "constraints": [ ["Person", "Film"] ] - } - ] - } - ''' - ret, result = self.client.importSchemaFromContent(schema, "default", timeout=1000) + ret, result = self.client.importSchemaFromContent(IMPORT_SCHEMA, "default", timeout=1000) log.info("importSchemaFromContent : " + result) time.sleep(5) ret, result = self.client.callCypher("CALL db.vertexLabels()", "default", timeout=10) @@ -101,31 +70,7 @@ def node_result_check(res): assert "COUNT(n)" in json_object.keys() assert json_object["COUNT(n)"] == 13 log.info("----------------test_import_data_from_content--------------------") - person_desc = '''{"files": [ - { - "columns": ["name", "birthyear", "phone"], - "format": "CSV", - "header": 0, - "label": "Person" - }] - } - ''' - person = ''' - Rachel Kempson,1910,10086 - Michael Redgrave,1908,10087 - Vanessa Redgrave,1937,10088 - Corin Redgrave,1939,10089 - Liam Neeson,1952,10090 - Natasha Richardson,1963,10091 - Richard Harris,1930,10092 - Dennis Quaid,1954,10093 - Lindsay Lohan,1986,10094 - Jemma Redgrave,1965,10095 - Roy Redgrave,1873,10096 - John Williams,1932,10097 - Christopher Nolan,1970,10098 - ''' - ret, result = self.client.importDataFromContent(person_desc, person, ",", True, 16, "default", timeout=1000) + ret, result = self.client.importDataFromContent(IMPORT_DATA_PERSON_DESC, IMPORT_DATA_PERSON, ",", True, 16, "default", timeout=1000) log.info("importDataFromContent : " + result) time.sleep(5) ret, result = self.client.callCypher("MATCH (n) RETURN COUNT(n)", "default", timeout=10) @@ -190,6 +135,24 @@ def list_procedure_result_check(res): list_procedure_result_check(result) @pytest.mark.run(order=6) + def test_query_to_leader(self): + log.info("----------------test_query_to_leader--------------------") + self.client.callCypher("CALL db.dropDB()", "default", 10) + self.client.importSchemaFromContent(IMPORT_SCHEMA, "default", 1000) + self.client.importDataFromContent(IMPORT_DATA_PERSON_DESC, IMPORT_DATA_PERSON, ",", True, 16, "default", 1000) + ret, res = self.client.callCypherToLeader("MATCH (n) RETURN COUNT(n)", "default", 10) + json_object = json.loads(res)[0] + assert "COUNT(n)" in json_object.keys() + assert json_object["COUNT(n)"] == 13 + + self.client.loadProcedure("./sortstr.so", "CPP", "sortstr", "SO", "test sortstr", True, "v1", "default") + ret, res = self.client.callProcedureToLeader("CPP", "sortstr", "gecfb", 1000, False, "default") + log.info("testCallProcedure : " + res) + json_object = json.loads(res)[0] + assert "result" in json_object.keys() + assert "bcefg" == json_object["result"] + + @pytest.mark.run(order=7) def test_import_schema_from_file(self): def node_result_check(res): log.info("db.vertexLabels() : " + res) @@ -225,7 +188,7 @@ def edge_result_check(res): "default", timeout=10, url=server) edge_result_check(result) - @pytest.mark.run(order=7) + @pytest.mark.run(order=8) def test_import_data_from_file(self): def node_result_check(res): log.info("MATCH (n) RETURN COUNT(n) : " + res) @@ -255,14 +218,14 @@ def edge_result_check(res): "default", timeout=1000, url=server) edge_result_check(result) - @pytest.mark.run(order=8) + @pytest.mark.run(order=9) def test_cypher_after_import(self): log.info(self.client.callCypher("CREATE (p:Person{name:\"Test1\",birthyear:1988,phone:10000})", "default", timeout=10)) time.sleep(5) execute_cypher_and_assert(self.client, '''MATCH (n:Person) WHERE n.name="Test1" RETURN n''', 21) - @pytest.mark.run(order=9) + @pytest.mark.run(order=10) def test_follower_restart(self): log.info("-------------------------stopping follower-------------------------") self.client.logout() @@ -291,7 +254,7 @@ def test_follower_restart(self): execute_cypher_and_assert(self.client, '''MATCH (n:Person) WHERE n.name="Test2" RETURN n''', 22) self.client.logout() - @pytest.mark.run(order=10) + @pytest.mark.run(order=11) def test_leader_restart(self): log.info("-------------------------stopping leader-------------------------") os.system("kill -2 $(ps -ef | grep 27072 | grep -v grep | awk '{print $2}')") diff --git a/test/test_ha.cpp b/test/test_ha.cpp index 6e28cf32e2..9b5a333ba2 100644 --- a/test/test_ha.cpp +++ b/test/test_ha.cpp @@ -1,26 +1,12 @@ -/* Copyright (c) 2022 AntGroup. All Rights Reserved. */ - -#include -#include -#include -#include +/* Copyright (c) 2022 AntGroup. All Rights Reserved. */ #include "gtest/gtest.h" - -// The 'U' macro can be used to create a string or character literal of the platform type, i.e. -// utility::char_t. If you are using a library causing conflicts with 'U' macro, it can be turned -// off by defining the macro '_TURN_OFF_PLATFORM_STRING' before including the C++ REST SDK header -// files, and e.g. use '_XPLATSTR' instead. -#define _TURN_OFF_PLATFORM_STRING -#include "cpprest/http_client.h" - -#include "fma-common/logger.h" #include "./ut_utils.h" #include "lgraph/lgraph.h" -#include "client/cpp/restful/rest_client.h" #include "lgraph/lgraph_rpc_client.h" -#include "server/lgraph_server.h" +#include "fma-common/configuration.h" +#include -void build_ha_so() { +void build_so(const std::string& so_name, const std::string& so_path) { const std::string INCLUDE_DIR = "../../include"; const std::string DEPS_INCLUDE_DIR = "../../deps/install/include"; const std::string LIBLGRAPH = "./liblgraph.so"; @@ -40,260 +26,240 @@ void build_ha_so() { "-o {} {} {} -shared"; #endif std::string cmd; - cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./sortstr.so", - "../../test/test_procedures/sortstr.cpp", LIBLGRAPH); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./scan_graph.so", - "../../test/test_procedures/scan_graph.cpp", LIBLGRAPH); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./echo_binary.so", - "../../test/test_procedures/echo_binary.cpp", LIBLGRAPH); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./add_label.so", - "../../test/test_procedures/add_label_v.cpp", LIBLGRAPH); + cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, so_name, + so_path, LIBLGRAPH); rt = system(cmd.c_str()); UT_EXPECT_EQ(rt, 0); } -class UnitTestBase { - std::map> tests_; - - public: - UnitTestBase() = default; - virtual ~UnitTestBase() = default; - void Run(const std::string& includes, const std::string& excludes) { - auto incs = fma_common::Split(includes, ","); - std::set included(incs.begin(), incs.end()); - auto excs = fma_common::Split(excludes, ","); - std::set excluded(excs.begin(), excs.end()); - - std::set tests; - if (included.empty()) - for (auto& kv : tests_) tests.insert(kv.first); - else - tests = included; - for (auto& t : excluded) { - if (tests.erase(t) == 0) { - FMA_WARN() << "Excluded test [" << t << "] is not in the test list."; +bool HasElement(const nlohmann::json& val, const std::string& value, const std::string& field) { + if (val.is_array()) { + for (const auto & i : val) { + if (i.contains(field)) { + if (i.at(field).get() == value) { + return true; + } } } - for (auto& t : tests) { - auto it = tests_.find(t); - UT_EXPECT_TRUE(it != tests_.end()); - // run test - UT_LOG() << "\n------------------"; - UT_LOG() << "Testing " << t; - UT_LOG() << "------------------"; - int r = it->second(); - UT_LOG() << "\n========"; - if (r == 0) - UT_LOG() << "SUCCESS"; - else - UT_LOG() << "FAIL"; - UT_LOG() << "========"; + } else if (val.is_object()) { + if (!val.contains(field)) return false; + if (val.at(field).get() == value) { + return true; } } + return false; +} +class TestHA : public TuGraphTest { protected: - void RegisterTest(const std::string& name, const std::function& func) { - auto it = tests_.find(name); - UT_EXPECT_TRUE(it == tests_.end()); - tests_.emplace_hint(it, name, func); - } -}; - -#define FMA_REG_TEST(method_name) RegisterTest(#method_name, [this]() { return method_name(); }) - -bool read_only_plugin = false; -bool use_rest = true; -size_t n_ops = 300; -std::string plugin_name = "scan"; // NOLINT - -class HaUnitTest : public UnitTestBase { - public: - std::unique_ptr server_; - std::shared_ptr config_; - std::vector> rests_; - std::vector> rpcs_; - - explicit HaUnitTest(std::shared_ptr config, int n_clients = 16) - : config_(std::move(config)), rests_(n_clients), rpcs_(n_clients) { - omp_set_num_threads(n_clients); - FMA_REG_TEST(AddLabel); - FMA_REG_TEST(AddGraph); - FMA_REG_TEST(Plugin); - FMA_REG_TEST(GetHaInfo); - } - - int AddLabel() { - UT_EXPECT_TRUE(rests_[0]->AddVertexLabel( - "default", "v", - std::vector{{"id", lgraph_api::FieldType::STRING, false}}, - "id")); - // FMA_ASSERT(rests_[0]->AddVertexIndex("default", "v", "id", true)); - int64_t vid; -#pragma omp parallel for - for (size_t i = 0; i < 1; i++) { - vid = rests_[omp_get_thread_num()]->AddVertex( - "default", "v", std::vector{"id"}, std::vector{"001"}); + void SetUp() override { + build_so("./add_vertex_v.so", "../../test/test_procedures/add_vertex_v.cpp"); + FILE *fp; + char buf[100] = {0}; + fp = popen("hostname -I", "r"); + if (fp) { + fread(buf, 1, sizeof(buf) - 1, fp); + pclose(fp); } - auto fields = rests_[0]->GetVertexFields("default", vid); - UT_EXPECT_EQ(fields["id"].AsString(), "001"); - return 0; - } - - int GetHaInfo() { - using namespace web; - using namespace http; - using namespace client; - auto* hclient = static_cast(rests_[0]->GetClient()); - const std::string db_name = "default"; - UT_LOG() << "\n=====get token of admin====="; - web::json::value body; - body[_TU("user")] = web::json::value::string(_TU("admin")); - body[_TU("password")] = web::json::value::string(_TU("73@TuGraph")); - auto response = hclient->request(methods::POST, _TU("/login"), body).get(); - UT_EXPECT_EQ(response.status_code(), status_codes::OK); - auto token = response.extract_json().get().at(_TU("jwt")).as_string(); - std::string author; - author = "Bearer " + ToStdString(token); - - http_request request; - request.headers().clear(); - request.headers().add(_TU("Authorization"), _TU(author)); - request.headers().add(_TU("Content-Type"), _TU("application/json")); - request.set_request_uri(_TU("/info/peers")); - request.set_method(methods::GET); - response = hclient->request(request).get(); - UT_LOG() << _TS(response.to_string()); - UT_LOG() << "+++++++++++++++++++++++++++++++++++++"; - UT_EXPECT_EQ(response.status_code(), status_codes::OK); + host = std::string(buf); + boost::trim(host); + int len = host.find(' '); + if (len != std::string::npos) { + host = host.substr(0, len); + } +#ifndef __SANITIZE_ADDRESS__ + std::string cmd_f = + "mkdir {} && cp -r ../../src/server/lgraph_ha.json " + "./lgraph_server ./resource {} " + "&& cd {} && ./lgraph_server --host {} --port {} --enable_rpc " + "true --enable_ha true --ha_node_offline_ms 5000 " + "--ha_node_remove_ms 10000 " + "--rpc_port {} --directory ./db --log_dir " + "./log --ha_conf {} --verbose 1 -c lgraph_ha.json -d start"; +#else + std::string cmd_f = + "mkdir {} && cp -r ../../src/server/lgraph_ha.json " + "./lgraph_server ./resource {} " + "&& cd {} && ./lgraph_server --host {} --port {} --enable_rpc " + "true --enable_ha true --ha_node_offline_ms 5000 " + "--ha_node_remove_ms 10000 " + "--rpc_port {} --directory ./db --log_dir " + "./log --ha_conf {} --use_pthread 1 --verbose 1 -c lgraph_ha.json -d start"; +#endif - request.headers().clear(); - request.headers().add(_TU("Authorization"), _TU(author)); - request.headers().add(_TU("Content-Type"), _TU("application/json")); - request.set_request_uri(_TU("/info/leader")); - request.set_method(methods::GET); - response = hclient->request(request).get(); - UT_LOG() << _TS(response.to_string()); - UT_LOG() << "--------------------------------------"; - UT_EXPECT_EQ(response.status_code(), status_codes::OK); - return 0; + int rt; + std::string cmd = FMA_FMT(cmd_f.c_str(), "ha1", "ha1", "ha1", host, "27072", "29092", + host + ":29092," + host + ":29093," + host + ":29094"); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + fma_common::SleepS(5); + cmd = FMA_FMT(cmd_f.c_str(), "ha2", "ha2", "ha2", host, "27073", "29093", + host + ":29092," + host + ":29093," + host + ":29094"); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + fma_common::SleepS(5); + cmd = FMA_FMT(cmd_f.c_str(), "ha3", "ha3", "ha3", host, "27074", "29094", + host + ":29092," + host + ":29093," + host + ":29094"); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + fma_common::SleepS(5); } - - int AddGraph() { - rests_[0]->EvalCypher("default", "call dbms.graph.createGraph('g2','desc for g2',1)"); - auto js = rests_[0]->EvalCypher("default", "call dbms.graph.listGraphs()"); - UT_LOG() << js.serialize(); - return 0; + void TearDown() override { + std::string cmd_f = "cd {} && ./lgraph_server -c lgraph_ha.json -d stop"; + for (int i = 1; i < 4; ++i) { + std::string dir = "ha" + std::to_string(i); + std::string cmd = FMA_FMT(cmd_f.c_str(), dir); + int rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + cmd = FMA_FMT("rm -rf {}", dir); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + } + int rt = system("rm -rf add_vertex_v.so"); + UT_EXPECT_EQ(rt, 0); } - int Plugin() { - // AddLabel(); - build_ha_so(); - - auto ReadCode = [](const std::string& path) { - std::string ret; - fma_common::InputFmaStream ifs(path); - ret.resize(ifs.Size()); - ifs.Read(&ret[0], ret.size()); - return ret; - }; + public: + std::string host; +}; - std::string plugin_path, plugin_param; - if (plugin_name == "scan") { - plugin_path = "./scan_graph.so"; - plugin_param = "{\"scan_edges\":true, \"times\":1}"; - } else if (plugin_name == "echo") { - plugin_path = "./echo_binary.so"; - plugin_param = ""; - } else if (plugin_name == "bfs") { - plugin_path = "./breadth_first_search.so"; - // plugin_param = "{\"root_id\":\"Liam Neeson\", \"label\":\"Person\", - // \"field\":\"name\"}"; - plugin_param = "{\"root_id\":\"001\", \"label\":\"v\", \"field\":\"id\"}"; - } +TEST_F(TestHA, HAClient) { + std::string schema = + "{\"schema\" :\n" + "[ \n" + "{ \n" + "\"label\" : \"Person\", \n" + "\"type\" : \"VERTEX\", \n" + "\"primary\" : \"name\", \n" + "\"properties\" : [ \n" + " {\"name\" : \"name\", \"type\":\"STRING\"}, \n" + " {\"name\" : \"birthyear\", \"type\":\"INT16\", " + "\"optional\":true}, \n" + " {\"name\" : \"phone\", \"type\":\"INT16\",\"unique\":true, " + "\"index\":true} \n" + "] \n" + " }, \n" + " { \n" + "\"label\" : \"Film\", \n" + "\"type\" : \"VERTEX\", \n" + "\"primary\" : \"title\", \n" + "\"properties\" : [ \n" + " {\"name\" : \"title\", \"type\":\"STRING\"} \n" + "] \n" + " }, \n" + " {\t \n" + "\"label\": \"PLAY_IN\",\t \n" + "\"type\": \"EDGE\",\t \n" + "\"properties\": [\n" + " {\"name\": \"role\", \"type\": \"STRING\", \"optional\": " + "true}\n" + "],\t \n" + "\"constraints\": [ [\"Person\", \"Film\"] ] \n" + "} \n" + "]\n" + "}"; + std::string data_desc = + "{\"files\": [ \n" + " { \n" + " \"columns\": [\"name\", \"birthyear\", \"phone\"],\n" + " \"format\": \"CSV\", \n" + " \"header\": 0, \n" + " \"label\": \"Person\" \n" + " }]\n" + "}"; + std::string data_person = + "Rachel Kempson,1910,10086\n" + "Michael Redgrave,1908,10087\n" + "Vanessa Redgrave,1937,10088\n" + "Corin Redgrave,1939,10089\n" + "Liam Neeson,1952,10090\n" + "Natasha Richardson,1963,10091\n" + "Richard Harris,1930,10092\n" + "Dennis Quaid,1954,10093\n" + "Lindsay Lohan,1986,10094\n" + "Jemma Redgrave,1965,10095\n" + "Roy Redgrave,1873,10096\n" + "John Williams,1932,10097\n" + "Christopher Nolan,1970,10098"; + build_so("./sortstr.so", "../../test/test_procedures/sortstr.cpp"); + lgraph::RpcClient client(this->host + ":29092", "admin", "73@TuGraph"); + std::string result; + bool ret = client.CallCypher(result, "CALL db.dropDB()"); + UT_EXPECT_TRUE(ret); - rests_[0]->LoadPlugin("default", lgraph_api::PluginCodeType::SO, - lgraph::PluginDesc(plugin_name, - lgraph::plugin::PLUGIN_CODE_TYPE_CPP, - plugin_name, - lgraph::plugin::PLUGIN_VERSION_1, - read_only_plugin, ""), - ReadCode(plugin_path)); + ret = client.CallCypher(result, "CALL dbms.procedures()"); + UT_EXPECT_TRUE(ret); -#pragma omp parallel for - for (int i = 0; i < (int)n_ops; i++) { - if (use_rest) { - UT_LOG() << rests_[omp_get_thread_num()]->ExecutePlugin("default", true, - plugin_name, plugin_param); - } else { - std::string res; - UT_LOG() << rpcs_[omp_get_thread_num()]->CallProcedure( - res, "CPP", plugin_name, plugin_param, 0, false, "default"); - } - } + ret = client.ListProcedures(result, "CPP"); + UT_EXPECT_TRUE(ret); - return 0; - } -}; + ret = client.ImportSchemaFromContent(result, schema) && + client.ImportDataFromContent(result, data_desc, data_person, ","); + UT_EXPECT_TRUE(ret); -class TestHA : public TuGraphTest { - std::shared_ptr config = std::make_shared(); + ret = client.CallCypherToLeader(result, "MATCH (n) RETURN COUNT(n)"); + UT_EXPECT_TRUE(ret); + nlohmann::json res = nlohmann::json::parse(result); + UT_EXPECT_EQ(res[0]["COUNT(n)"], 13); - protected: - void SetUp() override { - using namespace fma_common; - using namespace lgraph; - std::string dir = "./testdb"; - uint16_t http_port = 7091; - uint16_t rpc_port = 8091; - int n_clients = 16; + ret = client.CallGqlToLeader(result, "MATCH (n) RETURN COUNT(n)"); + UT_EXPECT_TRUE(ret); + res = nlohmann::json::parse(result); + UT_EXPECT_EQ(res[0]["COUNT(n)"], 13); - config->db_dir = dir; - config->bind_host = "127.0.0.1"; - config->ha_conf = "127.0.0.1:8091"; - config->http_port = http_port; - config->rpc_port = rpc_port; - config->enable_ha = true; - config->verbose = 1; - t.reset(new HaUnitTest(config, n_clients)); + std::string code_so_path = "./sortstr.so"; + ret = client.LoadProcedure(result, code_so_path, "CPP", "test_plugin1", "SO", + "this is a test plugin", true, "v1"); + UT_EXPECT_TRUE(ret); + ret = client.CallCypher(result, "CALL db.plugin.getPluginInfo('CPP','test_plugin1', false)"); + UT_EXPECT_TRUE(ret); + ret = client.CallProcedureToLeader(result, "CPP", "test_plugin1", "gecfb"); + UT_EXPECT_TRUE(ret); + nlohmann::json json_val = nlohmann::json::parse(result); + UT_EXPECT_TRUE(HasElement(json_val, "bcefg", "result")); - fma_common::file_system::RemoveDir(t->config_->db_dir); - // GraphFactory::create_yago(config_->db_dir); - t->server_.reset(); - t->server_ = std::make_unique(t->config_); - t->server_->Start(); - for (auto& c : t->rests_) { - c.reset(); - c = std::make_unique(FMA_FMT("http://{}:{}", - t->config_->bind_host, t->config_->http_port)); - c->Login(lgraph::_detail::DEFAULT_ADMIN_NAME, lgraph::_detail::DEFAULT_ADMIN_PASS); - } - if (!use_rest) { - for (auto& c : t->rpcs_) { - c = std::make_unique(FMA_FMT("{}:{}", - t->config_->bind_host, t->config_->rpc_port), - lgraph::_detail::DEFAULT_ADMIN_NAME, - lgraph::_detail::DEFAULT_ADMIN_PASS); - } - } - } - void TearDown() override { - for (auto& c : t->rests_) c.reset(); - for (auto& c : t->rpcs_) c.reset(); - t->server_->Stop(); - t->server_.reset(); - fma_common::file_system::RemoveDir(t->config_->db_dir); - } - std::shared_ptr t; -}; + client.CallCypher(result, "CALL db.dropDB()"); + client.Logout(); +} -TEST_F(TestHA, HA) { - std::string includes; - std::string excludes; - t->Run(includes, excludes); +TEST_F(TestHA, HAConsistency) { + std::thread thread = std::thread([this] { + lgraph::RpcClient rpcClient(this->host + ":29092", "admin", "73@TuGraph"); + std::string result; + rpcClient.LoadProcedure(result, "add_vertex_v.so", "CPP", "add_vertex_v", "SO", "", false); + rpcClient.CallProcedure(result, "CPP", "add_vertex_v", "{}"); + rpcClient.Logout(); + }); + fma_common::SleepS(5); + std::string cmd_f = "cd {} && ./lgraph_server -c lgraph_ha.json -d stop"; + std::string cmd = FMA_FMT(cmd_f.c_str(), "ha3"); + int rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + fma_common::SleepS(5); +#ifndef __SANITIZE_ADDRESS__ + cmd_f = + "cd {} && ./lgraph_server --host {} --port {} --enable_rpc " + "true --enable_ha true --ha_node_offline_ms 5000 --ha_node_remove_ms 10000 " + "--rpc_port {} --directory ./db --log_dir " + "./log --ha_conf {} -c lgraph_ha.json -d start"; +#else + cmd_f = + "cd {} && ./lgraph_server --host {} --port {} --enable_rpc " + "true --enable_ha true --ha_node_offline_ms 5000 --ha_node_remove_ms 10000 " + "--rpc_port {} --directory ./db --log_dir " + "./log --ha_conf {} --use_pthread 1 --verbose 1 -c lgraph_ha.json -d start"; +#endif + cmd = FMA_FMT(cmd_f.c_str(), "ha3", host, "27074", "29094", + host + ":29092," + host + ":29093," + host + ":29094"); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + fma_common::SleepS(10); + if (thread.joinable()) thread.join(); + lgraph::RpcClient rpcClient(this->host + ":29092", "admin", "73@TuGraph"); + fma_common::SleepS(30); + std::string result; + rpcClient.CallCypher(result, "MATCH (n) RETURN COUNT(n)", "default", true, 0, host + ":29094"); + nlohmann::json res = nlohmann::json::parse(result); + UT_EXPECT_EQ(res[0]["COUNT(n)"], 3000000); + rpcClient.Logout(); } diff --git a/test/test_ha_base.cpp b/test/test_ha_base.cpp new file mode 100644 index 0000000000..5581b08557 --- /dev/null +++ b/test/test_ha_base.cpp @@ -0,0 +1,299 @@ +/* Copyright (c) 2022 AntGroup. All Rights Reserved. */ + +#include +#include +#include +#include +#include "gtest/gtest.h" + +// The 'U' macro can be used to create a string or character literal of the platform type, i.e. +// utility::char_t. If you are using a library causing conflicts with 'U' macro, it can be turned +// off by defining the macro '_TURN_OFF_PLATFORM_STRING' before including the C++ REST SDK header +// files, and e.g. use '_XPLATSTR' instead. +#define _TURN_OFF_PLATFORM_STRING +#include "cpprest/http_client.h" + +#include "fma-common/logger.h" +#include "./ut_utils.h" +#include "lgraph/lgraph.h" +#include "client/cpp/restful/rest_client.h" +#include "lgraph/lgraph_rpc_client.h" +#include "server/lgraph_server.h" + +void build_ha_so() { + const std::string INCLUDE_DIR = "../../include"; + const std::string DEPS_INCLUDE_DIR = "../../deps/install/include"; + const std::string LIBLGRAPH = "./liblgraph.so"; + int rt; +#ifndef __clang__ + std::string cmd_f = + "g++ -fno-gnu-unique -fPIC -g --std=c++17 -I {} -I {} -rdynamic -O3 -fopenmp -DNDEBUG " + "-o {} {} {} -shared"; +#elif __APPLE__ + std::string cmd_f = + "clang++ -stdlib=libc++ -fPIC -g --std=c++17 -I {} -I {} -rdynamic -O3 -Xpreprocessor " + "-fopenmp -DNDEBUG " + "-o {} {} {} -shared"; +#else + std::string cmd_f = + "clang++ -stdlib=libc++ -fPIC -g --std=c++17 -I {} -I {} -rdynamic -O3 -fopenmp -DNDEBUG " + "-o {} {} {} -shared"; +#endif + std::string cmd; + cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./sortstr.so", + "../../test/test_procedures/sortstr.cpp", LIBLGRAPH); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./scan_graph.so", + "../../test/test_procedures/scan_graph.cpp", LIBLGRAPH); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./echo_binary.so", + "../../test/test_procedures/echo_binary.cpp", LIBLGRAPH); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); + cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./add_label.so", + "../../test/test_procedures/add_label_v.cpp", LIBLGRAPH); + rt = system(cmd.c_str()); + UT_EXPECT_EQ(rt, 0); +} + +class UnitTestBase { + std::map> tests_; + + public: + UnitTestBase() = default; + virtual ~UnitTestBase() = default; + void Run(const std::string& includes, const std::string& excludes) { + auto incs = fma_common::Split(includes, ","); + std::set included(incs.begin(), incs.end()); + auto excs = fma_common::Split(excludes, ","); + std::set excluded(excs.begin(), excs.end()); + + std::set tests; + if (included.empty()) + for (auto& kv : tests_) tests.insert(kv.first); + else + tests = included; + for (auto& t : excluded) { + if (tests.erase(t) == 0) { + FMA_WARN() << "Excluded test [" << t << "] is not in the test list."; + } + } + for (auto& t : tests) { + auto it = tests_.find(t); + UT_EXPECT_TRUE(it != tests_.end()); + // run test + UT_LOG() << "\n------------------"; + UT_LOG() << "Testing " << t; + UT_LOG() << "------------------"; + int r = it->second(); + UT_LOG() << "\n========"; + if (r == 0) + UT_LOG() << "SUCCESS"; + else + UT_LOG() << "FAIL"; + UT_LOG() << "========"; + } + } + + protected: + void RegisterTest(const std::string& name, const std::function& func) { + auto it = tests_.find(name); + UT_EXPECT_TRUE(it == tests_.end()); + tests_.emplace_hint(it, name, func); + } +}; + +#define FMA_REG_TEST(method_name) RegisterTest(#method_name, [this]() { return method_name(); }) + +bool read_only_plugin = false; +bool use_rest = true; +size_t n_ops = 300; +std::string plugin_name = "scan"; // NOLINT + +class HaUnitTest : public UnitTestBase { + public: + std::unique_ptr server_; + std::shared_ptr config_; + std::vector> rests_; + std::vector> rpcs_; + + explicit HaUnitTest(std::shared_ptr config, int n_clients = 16) + : config_(std::move(config)), rests_(n_clients), rpcs_(n_clients) { + omp_set_num_threads(n_clients); + FMA_REG_TEST(AddLabel); + FMA_REG_TEST(AddGraph); + FMA_REG_TEST(Plugin); + FMA_REG_TEST(GetHaInfo); + } + + int AddLabel() { + UT_EXPECT_TRUE(rests_[0]->AddVertexLabel( + "default", "v", + std::vector{{"id", lgraph_api::FieldType::STRING, false}}, + "id")); + // FMA_ASSERT(rests_[0]->AddVertexIndex("default", "v", "id", true)); + int64_t vid; +#pragma omp parallel for + for (size_t i = 0; i < 1; i++) { + vid = rests_[omp_get_thread_num()]->AddVertex( + "default", "v", std::vector{"id"}, std::vector{"001"}); + } + auto fields = rests_[0]->GetVertexFields("default", vid); + UT_EXPECT_EQ(fields["id"].AsString(), "001"); + return 0; + } + + int GetHaInfo() { + using namespace web; + using namespace http; + using namespace client; + auto* hclient = static_cast(rests_[0]->GetClient()); + const std::string db_name = "default"; + UT_LOG() << "\n=====get token of admin====="; + web::json::value body; + body[_TU("user")] = web::json::value::string(_TU("admin")); + body[_TU("password")] = web::json::value::string(_TU("73@TuGraph")); + auto response = hclient->request(methods::POST, _TU("/login"), body).get(); + UT_EXPECT_EQ(response.status_code(), status_codes::OK); + auto token = response.extract_json().get().at(_TU("jwt")).as_string(); + std::string author; + author = "Bearer " + ToStdString(token); + + http_request request; + request.headers().clear(); + request.headers().add(_TU("Authorization"), _TU(author)); + request.headers().add(_TU("Content-Type"), _TU("application/json")); + request.set_request_uri(_TU("/info/peers")); + request.set_method(methods::GET); + response = hclient->request(request).get(); + UT_LOG() << _TS(response.to_string()); + UT_LOG() << "+++++++++++++++++++++++++++++++++++++"; + UT_EXPECT_EQ(response.status_code(), status_codes::OK); + + request.headers().clear(); + request.headers().add(_TU("Authorization"), _TU(author)); + request.headers().add(_TU("Content-Type"), _TU("application/json")); + request.set_request_uri(_TU("/info/leader")); + request.set_method(methods::GET); + response = hclient->request(request).get(); + UT_LOG() << _TS(response.to_string()); + UT_LOG() << "--------------------------------------"; + UT_EXPECT_EQ(response.status_code(), status_codes::OK); + return 0; + } + + int AddGraph() { + rests_[0]->EvalCypher("default", "call dbms.graph.createGraph('g2','desc for g2',1)"); + auto js = rests_[0]->EvalCypher("default", "call dbms.graph.listGraphs()"); + UT_LOG() << js.serialize(); + return 0; + } + + int Plugin() { + // AddLabel(); + build_ha_so(); + + auto ReadCode = [](const std::string& path) { + std::string ret; + fma_common::InputFmaStream ifs(path); + ret.resize(ifs.Size()); + ifs.Read(&ret[0], ret.size()); + return ret; + }; + + std::string plugin_path, plugin_param; + if (plugin_name == "scan") { + plugin_path = "./scan_graph.so"; + plugin_param = "{\"scan_edges\":true, \"times\":1}"; + } else if (plugin_name == "echo") { + plugin_path = "./echo_binary.so"; + plugin_param = ""; + } else if (plugin_name == "bfs") { + plugin_path = "./breadth_first_search.so"; + // plugin_param = "{\"root_id\":\"Liam Neeson\", \"label\":\"Person\", + // \"field\":\"name\"}"; + plugin_param = "{\"root_id\":\"001\", \"label\":\"v\", \"field\":\"id\"}"; + } + + rests_[0]->LoadPlugin("default", lgraph_api::PluginCodeType::SO, + lgraph::PluginDesc(plugin_name, + lgraph::plugin::PLUGIN_CODE_TYPE_CPP, + plugin_name, + lgraph::plugin::PLUGIN_VERSION_1, + read_only_plugin, ""), + ReadCode(plugin_path)); + +#pragma omp parallel for + for (int i = 0; i < (int)n_ops; i++) { + if (use_rest) { + UT_LOG() << rests_[omp_get_thread_num()]->ExecutePlugin("default", true, + plugin_name, plugin_param); + } else { + std::string res; + UT_LOG() << rpcs_[omp_get_thread_num()]->CallProcedure( + res, "CPP", plugin_name, plugin_param, 0, false, "default"); + } + } + + return 0; + } +}; + +class TestHABase : public TuGraphTest { + std::shared_ptr config = std::make_shared(); + + protected: + void SetUp() override { + using namespace fma_common; + using namespace lgraph; + std::string dir = "./testdb"; + uint16_t http_port = 7091; + uint16_t rpc_port = 8091; + int n_clients = 16; + + config->db_dir = dir; + config->bind_host = "127.0.0.1"; + config->ha_conf = "127.0.0.1:8091"; + config->http_port = http_port; + config->rpc_port = rpc_port; + config->enable_ha = true; + config->verbose = 1; + t.reset(new HaUnitTest(config, n_clients)); + + fma_common::file_system::RemoveDir(t->config_->db_dir); + // GraphFactory::create_yago(config_->db_dir); + t->server_.reset(); + t->server_ = std::make_unique(t->config_); + t->server_->Start(); + for (auto& c : t->rests_) { + c.reset(); + c = std::make_unique(FMA_FMT("http://{}:{}", + t->config_->bind_host, t->config_->http_port)); + c->Login(lgraph::_detail::DEFAULT_ADMIN_NAME, lgraph::_detail::DEFAULT_ADMIN_PASS); + } + if (!use_rest) { + for (auto& c : t->rpcs_) { + c = std::make_unique(FMA_FMT("{}:{}", + t->config_->bind_host, t->config_->rpc_port), + lgraph::_detail::DEFAULT_ADMIN_NAME, + lgraph::_detail::DEFAULT_ADMIN_PASS); + } + } + } + void TearDown() override { + for (auto& c : t->rests_) c.reset(); + for (auto& c : t->rpcs_) c.reset(); + t->server_->Stop(); + t->server_.reset(); + fma_common::file_system::RemoveDir(t->config_->db_dir); + } + std::shared_ptr t; +}; + +TEST_F(TestHABase, HA) { + std::string includes; + std::string excludes; + t->Run(includes, excludes); +} diff --git a/test/test_ha_consistency.cpp b/test/test_ha_consistency.cpp deleted file mode 100644 index caf81bc818..0000000000 --- a/test/test_ha_consistency.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* Copyright (c) 2022 AntGroup. All Rights Reserved. */ -#include "gtest/gtest.h" -#include "./ut_utils.h" -#include "lgraph/lgraph.h" -#include "lgraph/lgraph_rpc_client.h" -#include "fma-common/configuration.h" -#include - -void build_add_vertex_so() { - const std::string INCLUDE_DIR = "../../include"; - const std::string DEPS_INCLUDE_DIR = "../../deps/install/include"; - const std::string LIBLGRAPH = "./liblgraph.so"; - int rt; -#ifndef __clang__ - std::string cmd_f = - "g++ -fno-gnu-unique -fPIC -g --std=c++17 -I {} -I {} -rdynamic -O3 -fopenmp -DNDEBUG " - "-o {} {} {} -shared"; -#elif __APPLE__ - std::string cmd_f = - "clang++ -stdlib=libc++ -fPIC -g --std=c++17 -I {} -I {} -rdynamic -O3 -Xpreprocessor " - "-fopenmp -DNDEBUG " - "-o {} {} {} -shared"; -#else - std::string cmd_f = - "clang++ -stdlib=libc++ -fPIC -g --std=c++17 -I {} -I {} -rdynamic -O3 -fopenmp -DNDEBUG " - "-o {} {} {} -shared"; -#endif - std::string cmd; - cmd = FMA_FMT(cmd_f.c_str(), INCLUDE_DIR, DEPS_INCLUDE_DIR, "./add_vertex_v.so", - "../../test/test_procedures/add_vertex_v.cpp", LIBLGRAPH); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); -} - -class TestHAConsistency : public TuGraphTest { - protected: - void SetUp() override { - build_add_vertex_so(); - FILE *fp; - char buf[100]={0}; - fp = popen("hostname -I", "r"); - if (fp) { - fread(buf, 1, sizeof(buf) - 1, fp); - pclose(fp); - } - host = std::string(buf); - boost::trim(host); - int len = host.find(' '); - if (len != std::string::npos) { - host = host.substr(0, len); - } -#ifndef __SANITIZE_ADDRESS__ - std::string cmd_f = - "mkdir {} && cp -r ../../src/server/lgraph_ha.json " - "./lgraph_server ./resource {} " - "&& cd {} && ./lgraph_server --host {} --port {} --enable_rpc " - "true --enable_ha true --ha_node_offline_ms 5000 " - "--ha_node_remove_ms 10000 " - "--rpc_port {} --directory ./db --log_dir " - "./log --ha_conf {} --verbose 1 -c lgraph_ha.json -d start"; -#else - std::string cmd_f = - "mkdir {} && cp -r ../../src/server/lgraph_ha.json " - "./lgraph_server ./resource {} " - "&& cd {} && ./lgraph_server --host {} --port {} --enable_rpc " - "true --enable_ha true --ha_node_offline_ms 5000 " - "--ha_node_remove_ms 10000 " - "--rpc_port {} --directory ./db --log_dir " - "./log --ha_conf {} --use_pthread 1 --verbose 1 -c lgraph_ha.json -d start"; -#endif - - int rt; - std::string cmd = FMA_FMT(cmd_f.c_str(), "ha1", "ha1", "ha1", host, "27072", "29092", - host + ":29092," + host + ":29093," + host + ":29094"); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - fma_common::SleepS(5); - cmd = FMA_FMT(cmd_f.c_str(), "ha2", "ha2", "ha2", host, "27073", "29093", - host + ":29092," + host + ":29093," + host + ":29094"); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - fma_common::SleepS(5); - cmd = FMA_FMT(cmd_f.c_str(), "ha3", "ha3", "ha3", host, "27074", "29094", - host + ":29092," + host + ":29093," + host + ":29094"); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - fma_common::SleepS(5); - } - void TearDown() override { - std::string cmd_f = "cd {} && ./lgraph_server -c lgraph_ha.json -d stop"; - for (int i = 1; i < 4; ++i) { - std::string dir = "ha" + std::to_string(i); - std::string cmd = FMA_FMT(cmd_f.c_str(), dir); - int rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - cmd = FMA_FMT("rm -rf {}", dir); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - } - int rt = system("rm -rf add_vertex_v.so"); - UT_EXPECT_EQ(rt, 0); - } - - public: - std::string host; -}; - -TEST_F(TestHAConsistency, HAConsistency) { - std::thread thread = std::thread([this]{ - lgraph::RpcClient rpcClient(this->host + ":29092", "admin", "73@TuGraph"); - std::string result; - rpcClient.LoadProcedure(result, "add_vertex_v.so", "CPP", - "add_vertex_v", "SO", "", false); - rpcClient.CallProcedure(result, "CPP", "add_vertex_v", "{}"); - rpcClient.Logout(); - }); - fma_common::SleepS(5); - std::string cmd_f = "cd {} && ./lgraph_server -c lgraph_ha.json -d stop"; - std::string cmd = FMA_FMT(cmd_f.c_str(), "ha3"); - int rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - fma_common::SleepS(5); -#ifndef __SANITIZE_ADDRESS__ - cmd_f = "cd {} && ./lgraph_server --host {} --port {} --enable_rpc " - "true --enable_ha true --ha_node_offline_ms 5000 --ha_node_remove_ms 10000 " - "--rpc_port {} --directory ./db --log_dir " - "./log --ha_conf {} -c lgraph_ha.json -d start"; -#else - cmd_f = "cd {} && ./lgraph_server --host {} --port {} --enable_rpc " - "true --enable_ha true --ha_node_offline_ms 5000 --ha_node_remove_ms 10000 " - "--rpc_port {} --directory ./db --log_dir " - "./log --ha_conf {} --use_pthread 1 --verbose 1 -c lgraph_ha.json -d start"; -#endif - cmd = FMA_FMT(cmd_f.c_str(), "ha3", host, "27074", "29094", - host + ":29092," + host + ":29093," + host + ":29094"); - rt = system(cmd.c_str()); - UT_EXPECT_EQ(rt, 0); - fma_common::SleepS(10); - if (thread.joinable()) - thread.join(); - lgraph::RpcClient rpcClient(this->host + ":29092", "admin", "73@TuGraph"); - fma_common::SleepS(30); - std::string result; - rpcClient.CallCypher(result, "MATCH (n) RETURN COUNT(n)", "default", true, 0, - host + ":29094"); - nlohmann::json res = nlohmann::json::parse(result); - UT_EXPECT_EQ(res[0]["COUNT(n)"], 3000000); -} diff --git a/test/test_python_plugin_manager_impl.cpp b/test/test_python_plugin_manager_impl.cpp index ba7bbf757e..5970640b11 100644 --- a/test/test_python_plugin_manager_impl.cpp +++ b/test/test_python_plugin_manager_impl.cpp @@ -237,10 +237,11 @@ TEST_F(TestPythonPluginManagerImpl, PythonPluginManagerImpl) { } for (auto& thr : thrs) thr.join(); UT_EXPECT_EQ(manager.GetNFree(), n_processes); - fma_common::SleepS(max_idle_seconds + 0.4); + fma_common::SleepS(max_idle_seconds * 2); + UT_EXPECT_EQ(manager.GetNFree(), 0); // this call will clean all the processes manager.DoCall(nullptr, user, &db, "sleep", pinfo, "0.1", 2, true, output); - UT_EXPECT_EQ(manager.GetNFree(), 0); + UT_EXPECT_EQ(manager.GetNFree(), 1); thrs.clear(); n_processes = 10; for (size_t i = 0; i < n_processes; i++) { @@ -250,10 +251,9 @@ TEST_F(TestPythonPluginManagerImpl, PythonPluginManagerImpl) { }); } for (auto& thr : thrs) thr.join(); - double sleep_time = 1.4; - manager.DoCall(nullptr, user, &db, "sleep", pinfo, std::to_string(sleep_time), 2, true, - output); - UT_EXPECT_EQ(manager.GetNFree(), max_idle_seconds - std::ceil(sleep_time) + 1); + manager.DoCall(nullptr, user, &db, "sleep", pinfo, std::to_string(2 * max_idle_seconds), + 3 * max_idle_seconds, true, output); + UT_EXPECT_EQ(manager.GetNFree(), 1); std::string path("sleep"); auto res = manager.GetPluginPath(path); UT_LOG() << res; diff --git a/test/test_rpc.cpp b/test/test_rpc.cpp index 64fdbac7a8..9998e492c1 100644 --- a/test/test_rpc.cpp +++ b/test/test_rpc.cpp @@ -1645,6 +1645,10 @@ void test_cpp_procedure(lgraph::RpcClient& client) { UT_EXPECT_TRUE(ret); json_val = web::json::value::parse(str); UT_EXPECT_EQ(HasElement(json_val, "bcefg", "result"), true); + ret = client.CallProcedureToLeader(str, "CPP", "test_procedure1", "bcefg"); + UT_EXPECT_TRUE(ret); + json_val = web::json::value::parse(str); + UT_EXPECT_EQ(HasElement(json_val, "bcefg", "result"), true); ret = client.CallProcedure(str, "CPP", "test_procedure2", "{\"scan_edges\":true, \"times\":2}", 100.10); @@ -1716,6 +1720,23 @@ void test_cypher(lgraph::RpcClient& client) { UT_EXPECT_TRUE(ret); web::json::value json_val = web::json::value::parse(str); UT_EXPECT_EQ(json_val[0]["count(n)"].as_integer(), 6); + ret = client.CallCypherToLeader(str, "match (n) return count(n)"); + UT_EXPECT_TRUE(ret); + json_val = web::json::value::parse(str); + UT_EXPECT_EQ(json_val[0]["count(n)"].as_integer(), 6); +} + +void test_gql(lgraph::RpcClient& client) { + UT_LOG() << "test CallGQL"; + std::string str; + bool ret = client.CallGql(str, "match (n) return count(n)"); + UT_EXPECT_TRUE(ret); + web::json::value json_val = web::json::value::parse(str); + UT_EXPECT_EQ(json_val[0]["count(n)"].as_integer(), 6); + ret = client.CallGqlToLeader(str, "match (n) return count(n)"); + UT_EXPECT_TRUE(ret); + json_val = web::json::value::parse(str); + UT_EXPECT_EQ(json_val[0]["count(n)"].as_integer(), 6); } void test_import_file(lgraph::RpcClient& client) { @@ -1917,6 +1938,7 @@ void* test_rpc_client(void*) { { RpcClient client3("0.0.0.0:19099", "admin", "73@TuGraph"); test_cypher(client3); + test_gql(client3); test_label(client3); test_relationshipTypes(client3); test_index(client3);