From 08fcf477ea53ea65989de256b77e9a7273d6860c Mon Sep 17 00:00:00 2001 From: knakatasf Date: Wed, 2 Jul 2025 05:25:59 +0200 Subject: [PATCH 01/10] chore(doc): Added rest-schema-handling.md --- .../develop/http-api/rest-schema-handling.md | 829 ++++++++++++++++++ 1 file changed, 829 insertions(+) create mode 100644 site/content/3.12/develop/http-api/rest-schema-handling.md diff --git a/site/content/3.12/develop/http-api/rest-schema-handling.md b/site/content/3.12/develop/http-api/rest-schema-handling.md new file mode 100644 index 0000000000..62655c6939 --- /dev/null +++ b/site/content/3.12/develop/http-api/rest-schema-handling.md @@ -0,0 +1,829 @@ +--- +title: GET Schema Endpoint +menuTitle: http-api +description: Returns information about graphs, views, and collections in the database. +--- + +## GET Schema Endpoint `/_api/schema` + +### Introduction +This endpoint shows the structure of the current database. It returns three types of information: + +1. **graphs** – each graph shows its name and how it connects collections using edges (`_from` and `_to`). +2. **views** – each view shows its name and which collections and fields it links to. +3. **collections** – each collection shows: + - a list of attributes (fields) + - the data types of each attribute (`string`, `number`, `bool`, or `object`) + - whether the attribute is optional (meaning some documents/edges may not have it) + - and example documents or edges for reference + +This information is helpful if you want to understand the overall shape and structure of the data in the database. +Additionally, this endpoint also support the following paths: +1. [**/_api/schema/graph/<graph-name>**](#api-schema-graphgraph-name) +2. [**/_api/schema/view/<view-name>**](#api-schema-viewview-name) +3. [**/_api/schema/collection/<collection-name>**](#api-schema-collectioncollection-name) + +--- + +### Parameters + +The endpoint supports two optional query parameters: + +#### `sampleNum` (default value: 100) + +- This tells the endpoint how many documents/edges to look at when examining the schema of each collection. +- For example, if `sampleNum` is 100, it will examine up to 100 documents/edges from each collection. +- That is, the more samples you use, the more accurate the schema result becomes. +- If a collection has fewer documents/edges than the number you pass, it will just use all of them. +- This must be a **natural number**. +- If you pass `0`, a negative number, a decimal, or a non-number, it will result in an error. + +#### `exampleNum` (default value: 1) + +- This controls how many example documents/edges will be shown for each collection. +- If you set it to `0`, no examples will be shown. +- If the number is larger than the actual number of documents/edges, it will just show all of them. +- Like `sampleNum`, this must be a non-negative whole number. Invalid input will cause an error. + +--- + +### Example Request and Response +#### HTTP Request +```http request +GET /_db//_api/schema?sampleNum=100&exampleNum=1 +``` +#### HTTP Response (JSON format) +```http response +{ + "graphs": [ + { + "name": "purchaseHistory", + "relations": [ + { + "collection": "purchased", + "from": [ + "customers" + ], + "to": [ + "products" + ] + } + ] + }, + { + "name": "manufacture", + "relations": [ + { + "collection": "manufactured", + "from": [ + "company" + ], + "to": [ + "products" + ] + } + ] + } + ], + "views": [ + { + "viewName": "descView", + "links": [ + { + "collectionName": "customers", + "fields": [ + { + "attribute": "comment", + "analyzers": [ + "text_en" + ] + } + ] + }, + { + "collectionName": "products", + "fields": [ + { + "attribute": "description", + "analyzers": [ + "text_en" + ] + } + ] + } + ] + } + ], + "collections": [ + { + "collectionName": "company", + "collectionType": "document", + "numOfDocuments": 3, + "schema": [ + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "address", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "established", + "types": [ + "number", + "string" + ], + "optional": false + }, + { + "attribute": "name", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "public", + "types": [ + "bool" + ], + "optional": true + } + ], + "examples": [ + { + "_id": "company/224680", + "_key": "224680", + "address": "San Francisco", + "established": 1989, + "name": "Company A" + } + ] + }, + { + "collectionName": "customers", + "collectionType": "document", + "numOfDocuments": 10, + "schema": [ + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "address", + "types": [ + "string", + "object" + ], + "optional": true + }, + { + "attribute": "age", + "types": [ + "number", + "string" + ], + "optional": false + }, + { + "attribute": "comment", + "types": [ + "string" + ], + "optional": true + }, + { + "attribute": "name", + "types": [ + "string" + ], + "optional": false + } + ], + "examples": [ + { + "_id": "customers/263", + "_key": "263", + "age": 35, + "name": "Ken" + } + ] + }, + { + "collectionName": "manufactured", + "collectionType": "edge", + "numOfEdges": 2, + "schema": [ + { + "attribute": "_from", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_to", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "amount", + "types": [ + "number" + ], + "optional": false + } + ], + "examples": [ + { + "_from": "company/224680", + "_id": "manufactured/224827", + "_key": "224827", + "_to": "products/32291", + "amount": 1200 + } + ] + }, + { + "collectionName": "products", + "collectionType": "document", + "numOfDocuments": 5, + "schema": [ + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "description", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "name", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "price", + "types": [ + "number" + ], + "optional": false + }, + { + "attribute": "version", + "types": [ + "string" + ], + "optional": true + } + ], + "examples": [ + { + "_id": "products/32235", + "_key": "32235", + "description": "This car was made in Japan, and used", + "name": "car", + "price": 120.95 + } + ] + }, + { + "collectionName": "purchased", + "collectionType": "edge", + "numOfEdges": 1, + "schema": [ + { + "attribute": "_from", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_to", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "date", + "types": [ + "string" + ], + "optional": false + } + ], + "examples": [ + { + "_from": "customers/273", + "_id": "purchased/100727", + "_key": "100727", + "_to": "products/32235", + "date": "5/25/2026" + } + ] + } + ] +} +``` + +### /_api/schema/graph/ + +This endpoint returns the structure of a specific graph, including its edge definitions and the `_from` and `_to` collections. + +#### Example Request +```http request +GET /_db//_api/schema/graph/purchaseHistory?sampleNum=100&exampleNum=1 +``` +#### Example Response (JSON format) +```http response +{ + "graphs": [ + { + "name": "purchaseHistory", + "relations": [ + { + "collection": "purchased", + "from": [ + "customers" + ], + "to": [ + "products" + ] + } + ] + } + ], + "collections": [ + { + "collectionName": "customers", + "collectionType": "document", + "numOfDocuments": 10, + "schema": [ + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "address", + "types": [ + "string", + "object" + ], + "optional": true + }, + { + "attribute": "age", + "types": [ + "number", + "string" + ], + "optional": false + }, + { + "attribute": "comment", + "types": [ + "string" + ], + "optional": true + }, + { + "attribute": "name", + "types": [ + "string" + ], + "optional": false + } + ], + "examples": [ + { + "_id": "customers/263", + "_key": "263", + "age": 35, + "name": "Ken" + } + ] + }, + { + "collectionName": "products", + "collectionType": "document", + "numOfDocuments": 5, + "schema": [ + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "description", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "name", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "price", + "types": [ + "number" + ], + "optional": false + }, + { + "attribute": "version", + "types": [ + "string" + ], + "optional": true + } + ], + "examples": [ + { + "_id": "products/32235", + "_key": "32235", + "description": "This car was made in Japan, and used", + "name": "car", + "price": 120.95 + } + ] + }, + { + "collectionName": "purchased", + "collectionType": "edge", + "numOfEdges": 1, + "schema": [ + { + "attribute": "_from", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_to", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "date", + "types": [ + "string" + ], + "optional": false + } + ], + "examples": [ + { + "_from": "customers/273", + "_id": "purchased/100727", + "_key": "100727", + "_to": "products/32235", + "date": "5/25/2026" + } + ] + } + ] +} +``` + +### /_api/schema/view/ + +This endpoint shows the configuration of an specified ArangoSearch view, including its linked collections and fields. + +#### Example Request +```http request +GET /_db//_api/schema/view/descView?sampleNum=100&exampleNum=1 +``` +#### Example Response (JSON format) +```http response +{ + "views": [ + { + "viewName": "descView", + "links": [ + { + "collectionName": "customers", + "fields": [ + { + "attribute": "comment", + "analyzers": [ + "text_en" + ] + } + ] + }, + { + "collectionName": "products", + "fields": [ + { + "attribute": "description", + "analyzers": [ + "text_en" + ] + } + ] + } + ] + } + ], + "collections": [ + { + "collectionName": "customers", + "collectionType": "document", + "numOfDocuments": 10, + "schema": [ + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "address", + "types": [ + "string", + "object" + ], + "optional": true + }, + { + "attribute": "age", + "types": [ + "number", + "string" + ], + "optional": false + }, + { + "attribute": "comment", + "types": [ + "string" + ], + "optional": true + }, + { + "attribute": "name", + "types": [ + "string" + ], + "optional": false + } + ], + "examples": [ + { + "_id": "customers/263", + "_key": "263", + "age": 35, + "name": "Ken" + } + ] + }, + { + "collectionName": "products", + "collectionType": "document", + "numOfDocuments": 5, + "schema": [ + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "description", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "name", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "price", + "types": [ + "number" + ], + "optional": false + }, + { + "attribute": "version", + "types": [ + "string" + ], + "optional": true + } + ], + "examples": [ + { + "_id": "products/32235", + "_key": "32235", + "description": "This car was made in Japan, and used", + "name": "car", + "price": 120.95 + } + ] + } + ] +} +``` +### /_api/schema/collection/ + +This endpoint returns schema information for a specific collection, such as attribute names, types, and example documents. + +#### Example Request +```http request +GET /_db//_api/schema/collection/products?sampleNum=100&exampleNum=1 +``` +#### Example Response (JSON format) +```http response +{ + "collectionName": "products", + "collectionType": "document", + "numOfDocuments": 5, + "schema": [ + { + "attribute": "_id", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "_key", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "description", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "name", + "types": [ + "string" + ], + "optional": false + }, + { + "attribute": "price", + "types": [ + "number" + ], + "optional": false + }, + { + "attribute": "version", + "types": [ + "string" + ], + "optional": true + } + ], + "examples": [ + { + "_id": "products/32235", + "_key": "32235", + "description": "This car was made in Japan, and used", + "name": "car", + "price": 120.95 + } + ] + } +``` \ No newline at end of file From ad686997309fd24fe9ca01ea2c038741e9247178 Mon Sep 17 00:00:00 2001 From: knakatasf Date: Wed, 2 Jul 2025 22:55:37 +0200 Subject: [PATCH 02/10] chore(docs): Edited schemas.md --- .../{rest-schema-handling.md => schemas.md} | 391 +++++++++++++++--- 1 file changed, 338 insertions(+), 53 deletions(-) rename site/content/3.12/develop/http-api/{rest-schema-handling.md => schemas.md} (55%) diff --git a/site/content/3.12/develop/http-api/rest-schema-handling.md b/site/content/3.12/develop/http-api/schemas.md similarity index 55% rename from site/content/3.12/develop/http-api/rest-schema-handling.md rename to site/content/3.12/develop/http-api/schemas.md index 62655c6939..41b30e77cc 100644 --- a/site/content/3.12/develop/http-api/rest-schema-handling.md +++ b/site/content/3.12/develop/http-api/schemas.md @@ -1,14 +1,25 @@ --- -title: GET Schema Endpoint -menuTitle: http-api -description: Returns information about graphs, views, and collections in the database. + +title: HTTP interface for database structures (schema, graph, view) +menuTitle: Schemas +description: >- + HTTP interface for database structures gives you information about + graphs, views, collections and schemas in the database. +# GET /_api/schema +# GET /_api/schema/graph/{graph-name} +# GET /_api/schema/view/{view-name} +# GET /_api/schema/collection/{collection-name} + --- -## GET Schema Endpoint `/_api/schema` +The interface provides the means to collect information on database +structures including graphs, views, collections and their schemas +at one stop. This information is helpful if you want to understand +the overall shape and structure of the database. -### Introduction -This endpoint shows the structure of the current database. It returns three types of information: +--- +## Information the interface gives you 1. **graphs** – each graph shows its name and how it connects collections using edges (`_from` and `_to`). 2. **views** – each view shows its name and which collections and fields it links to. 3. **collections** – each collection shows: @@ -17,43 +28,90 @@ This endpoint shows the structure of the current database. It returns three type - whether the attribute is optional (meaning some documents/edges may not have it) - and example documents or edges for reference -This information is helpful if you want to understand the overall shape and structure of the data in the database. -Additionally, this endpoint also support the following paths: -1. [**/_api/schema/graph/<graph-name>**](#api-schema-graphgraph-name) -2. [**/_api/schema/view/<view-name>**](#api-schema-viewview-name) -3. [**/_api/schema/collection/<collection-name>**](#api-schema-collectioncollection-name) - --- -### Parameters +## Paths the interface supports +1. [**GET /_api/schema**](#api-schema-graphgraph-name) - Gives you all the information above on the database. +1. [**GET /_api/schema/graph/<graph-name>**](#api-schema-graphgraph-name) - Provides the specified graph and the connected collections. +2. [**GET /_api/schema/view/<view-name>**](#api-schema-viewview-name) - Shows the specified view and the linked collections. +3. [**GET /_api/schema/collection/<collection-name>**](#api-schema-collectioncollection-name) - Displays the specified collection. -The endpoint supports two optional query parameters: - -#### `sampleNum` (default value: 100) - -- This tells the endpoint how many documents/edges to look at when examining the schema of each collection. -- For example, if `sampleNum` is 100, it will examine up to 100 documents/edges from each collection. -- That is, the more samples you use, the more accurate the schema result becomes. -- If a collection has fewer documents/edges than the number you pass, it will just use all of them. -- This must be a **natural number**. -- If you pass `0`, a negative number, a decimal, or a non-number, it will result in an error. +--- -#### `exampleNum` (default value: 1) +## GET /_api/schema -- This controls how many example documents/edges will be shown for each collection. -- If you set it to `0`, no examples will be shown. -- If the number is larger than the actual number of documents/edges, it will just show all of them. -- Like `sampleNum`, this must be a non-negative whole number. Invalid input will cause an error. +```openapi +paths: + /_db/{database-name}/_api/schema: + get: + operationId: getAllSchemas + description: | + Show all the information on the database including graphs, views and collections. + parameters: + - name: database-name + in: path + required: true + example: _system + description: | + The name of a database that you want to examine its structures and schemas. + schema: + type: string + + - name: sampleNum + in: query + required: false + description: | + The number of documents/edges to examine per collection when determining + attribute types and whether an attribute is optional. + Must be a positive integer. Defaults to `100`. + If larger than the number of documents, it will only use the available ones. + schema: + type: integer + minimum: 1 + default: 100 + + - name: exampleNum + in: query + required: false + description: | + The number of example documents/edges to return per collection. + Must be a non-negative integer and not be larger than `sampleNum`. + If `0`, no examples will be returned. Defaults to `1`. + schema: + type: integer + minimum: 0 + default: 1 + + responses: + '200 OK': + description: | + The schema overview was successfully returned. + '400 Bad Request': + description: | + Invalid query parameters (e.g., negative or non-numeric values). + '404 Not Found': + description: | + Collection, view or graph not found on the database. + '405 Method Not Allowed': + description: | + When request method is not GET. This endpoint only supports GET. + '500 Internal Server Error': + description: | + Internal server error. + tags: + - Schema +``` ---- +**Examples** +HTTP Request -### Example Request and Response -#### HTTP Request ```http request -GET /_db//_api/schema?sampleNum=100&exampleNum=1 +GET /_db/_system/_api/schema?sampleNum=100&exampleNum=1 ``` -#### HTTP Response (JSON format) -```http response +
+HTTP Response (click to show) + +```json { "graphs": [ { @@ -396,17 +454,92 @@ GET /_db//_api/schema?sampleNum=100&exampleNum=1 ] } ``` +
-### /_api/schema/graph/ +--- + +## GET /_api/schema/graph/{graph-name} + +```openapi +paths: + /_db/{database-name}/_api/schema/graph/{graph-name}: + get: + operationId: getGraph + description: | + Show the specified graph information and its connected colletions. + parameters: + - name: database-name + in: path + required: true + example: _system + description: | + The name of a database that you want to examine its structures and schemas. + schema: + type: string + + - name: graph-name + in: path + required: true + description: | + The name of a graph that you want to examine its structures and schemas. + schema: + type: string + + - name: sampleNum + in: query + required: false + description: | + The number of documents/edges to examine per collection + when determining attribute types and whether an attribute is optional. + Must be a positive integer. Defaults to `100`. + If larger than the number of documents, it will only use the available ones. + schema: + type: integer + minimum: 1 + default: 100 + + - name: exampleNum + in: query + required: false + description: | + The number of example documents/edges to return per collection. + Must be a non-negative integer and not be larger than `sampleNum`. + If `0`, no examples will be returned. Defaults to `1`. + schema: + type: integer + minimum: 0 + default: 1 + + responses: + '200 OK': + description: | + The schema overview was successfully returned. + '400 Bad Request': + description: | + Invalid query parameters (e.g., negative or non-numeric values). + '404 Not Found': + description: | + Unknown path suffix (e.g., /schema/foo/bar). Collection or graph not found on the database. + '405 Method Not Allowed': + description: | + When request method is not GET. This endpoint only supports GET. + '500 Internal Server Error': + description: | + Internal server error. + tags: + - Schema +``` -This endpoint returns the structure of a specific graph, including its edge definitions and the `_from` and `_to` collections. +**Examples** +HTTP Request -#### Example Request ```http request -GET /_db//_api/schema/graph/purchaseHistory?sampleNum=100&exampleNum=1 +GET /_db/_system/_api/schema/graph/purchaseHistory?sampleNum=100&exampleNum=1 ``` -#### Example Response (JSON format) -```http response +
+HTTP Response (click to show) + +```json { "graphs": [ { @@ -596,17 +729,92 @@ GET /_db//_api/schema/graph/purchaseHistory?sampleNum=100&example ] } ``` +
+ +--- -### /_api/schema/view/ +## GET /_api/schema/view/{view-name} -This endpoint shows the configuration of an specified ArangoSearch view, including its linked collections and fields. +```openapi +paths: + /_db/{database-name}/_api/schema/view/{view-name}: + get: + operationId: getView + description: | + Show the specified view information and its linked colletions. + parameters: + - name: database-name + in: path + required: true + example: _system + description: | + The name of a database that you want to examine its structures and schemas. + schema: + type: string + + - name: view-name + in: path + required: true + description: | + The name of a view that you want to examine its structures and schemas. + schema: + type: string + + - name: sampleNum + in: query + required: false + description: | + The number of documents/edges to examine per collection + when determining attribute types and whether an attribute is optional. + Must be a positive integer. Defaults to `100`. + If larger than the number of documents, it will only use the available ones. + schema: + type: integer + minimum: 1 + default: 100 + + - name: exampleNum + in: query + required: false + description: | + The number of example documents/edges to return per collection. + Must be a non-negative integer and not be larger than `sampleNum`. + If `0`, no examples will be returned. Defaults to `1`. + schema: + type: integer + minimum: 0 + default: 1 + + responses: + '200 OK': + description: | + The schema overview was successfully returned. + '400 Bad Request': + description: | + Invalid query parameters (e.g., negative or non-numeric values). + '404 Not Found': + description: | + Unknown path suffix (e.g., /schema/foo/bar). Collection or view not found on the database. + '405 Method Not Allowed': + description: | + When request method is not GET. This endpoint only supports GET. + '500 Internal Server Error': + description: | + Internal server error. + tags: + - Schema +``` + +**Examples** +HTTP Request -#### Example Request ```http request -GET /_db//_api/schema/view/descView?sampleNum=100&exampleNum=1 +GET /_db/_system/_api/schema/view/descViewy?sampleNum=100&exampleNum=1 ``` -#### Example Response (JSON format) -```http response +
+HTTP Response (click to show) + +```json { "views": [ { @@ -758,16 +966,92 @@ GET /_db//_api/schema/view/descView?sampleNum=100&exampleNum=1 ] } ``` -### /_api/schema/collection/ +
-This endpoint returns schema information for a specific collection, such as attribute names, types, and example documents. +--- + +## GET /_api/schema/collection/{collection-name} + +```openapi +paths: + /_db/{database-name}/_api/schema/collection/{collection-name}: + get: + operationId: getCollection + description: | + Show the specified collection information and its schemas. + parameters: + - name: database-name + in: path + required: true + example: _system + description: | + The name of a database that you want to examine its structures and schemas. + schema: + type: string + + - name: collection-name + in: path + required: true + description: | + The name of a collection that you want to examine its schemas. + schema: + type: string + + - name: sampleNum + in: query + required: false + description: | + The number of documents/edges to examine per collection + when determining attribute types and whether an attribute is optional. + Must be a positive integer. Defaults to `100`. + If larger than the number of documents, it will only use the available ones. + schema: + type: integer + minimum: 1 + default: 100 + + - name: exampleNum + in: query + required: false + description: | + The number of example documents/edges to return per collection. + Must be a non-negative integer and not be larger than `sampleNum`. + If `0`, no examples will be returned. Defaults to `1`. + schema: + type: integer + minimum: 0 + default: 1 + + responses: + '200 OK': + description: | + The schema overview was successfully returned. + '400 Bad Request': + description: | + Invalid query parameters (e.g., negative or non-numeric values). + '404 Not Found': + description: | + Unknown path suffix (e.g., /schema/foo/bar). Collection not found on the database. + '405 Method Not Allowed': + description: | + When request method is not GET. This endpoint only supports GET. + '500 Internal Server Error': + description: | + Internal server error. + tags: + - Schema +``` + +**Examples** +HTTP Request -#### Example Request ```http request -GET /_db//_api/schema/collection/products?sampleNum=100&exampleNum=1 +GET /_db/_system/_api/schema/view/descViewy?sampleNum=100&exampleNum=1 ``` -#### Example Response (JSON format) -```http response +
+HTTP Response (click to show) + +```json { "collectionName": "products", "collectionType": "document", @@ -826,4 +1110,5 @@ GET /_db//_api/schema/collection/products?sampleNum=100&exampleNu } ] } -``` \ No newline at end of file +``` +
\ No newline at end of file From 02bdc37bd45d3429d52b1e9deb1efacf12d35ccb Mon Sep 17 00:00:00 2001 From: knakatasf Date: Thu, 3 Jul 2025 04:59:30 +0200 Subject: [PATCH 03/10] chore(docs): Edited schemas.md --- site/content/3.12/develop/http-api/schemas.md | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/site/content/3.12/develop/http-api/schemas.md b/site/content/3.12/develop/http-api/schemas.md index 41b30e77cc..7a326eb386 100644 --- a/site/content/3.12/develop/http-api/schemas.md +++ b/site/content/3.12/develop/http-api/schemas.md @@ -19,10 +19,10 @@ the overall shape and structure of the database. --- -## Information the interface gives you +## What This Interface Returns 1. **graphs** – each graph shows its name and how it connects collections using edges (`_from` and `_to`). 2. **views** – each view shows its name and which collections and fields it links to. -3. **collections** – each collection shows: +3. **collections** – only connected or linked collections are shown. Each collection shows: - a list of attributes (fields) - the data types of each attribute (`string`, `number`, `bool`, or `object`) - whether the attribute is optional (meaning some documents/edges may not have it) @@ -30,15 +30,15 @@ the overall shape and structure of the database. --- -## Paths the interface supports -1. [**GET /_api/schema**](#api-schema-graphgraph-name) - Gives you all the information above on the database. -1. [**GET /_api/schema/graph/<graph-name>**](#api-schema-graphgraph-name) - Provides the specified graph and the connected collections. -2. [**GET /_api/schema/view/<view-name>**](#api-schema-viewview-name) - Shows the specified view and the linked collections. -3. [**GET /_api/schema/collection/<collection-name>**](#api-schema-collectioncollection-name) - Displays the specified collection. +## Supported API Paths +1. [**GET /_api/schema**](#get-apischema) – Returns all graphs, views, and collections in the database. +2. [**GET /_api/schema/graph/<graph-name>**](#get-apischemagraphgraph-name) – Returns the specified graph and its connected collections. +3. [**GET /_api/schema/view/<view-name>**](#get-apischemaviewview-name) – Returns the specified view and its linked collections. +4. [**GET /_api/schema/collection/<collection-name>**](#get-apischemacollectioncollection-name) – Returns the specified collection and its schema. --- -## GET /_api/schema +

GET /_api/schema

```openapi paths: @@ -458,7 +458,8 @@ GET /_db/_system/_api/schema?sampleNum=100&exampleNum=1 --- -## GET /_api/schema/graph/{graph-name} + +

GET /_api/schema/graph/<graph-name>

```openapi paths: @@ -733,7 +734,8 @@ GET /_db/_system/_api/schema/graph/purchaseHistory?sampleNum=100&exampleNum=1 --- -## GET /_api/schema/view/{view-name} + +

GET /_api/schema/view/<view-name>

```openapi paths: @@ -809,7 +811,7 @@ paths: HTTP Request ```http request -GET /_db/_system/_api/schema/view/descViewy?sampleNum=100&exampleNum=1 +GET /_db/_system/_api/schema/view/descView?sampleNum=100&exampleNum=1 ```
HTTP Response (click to show) @@ -970,7 +972,7 @@ GET /_db/_system/_api/schema/view/descViewy?sampleNum=100&exampleNum=1 --- -## GET /_api/schema/collection/{collection-name} +

GET /_api/schema/collection/<collection-name>

```openapi paths: @@ -1046,7 +1048,7 @@ paths: HTTP Request ```http request -GET /_db/_system/_api/schema/view/descViewy?sampleNum=100&exampleNum=1 +GET /_db/_system/_api/schema/collection/products?sampleNum=100&exampleNum=1 ```
HTTP Response (click to show) From bf8b88e156a47086abb1a1211927fcb0999d2294 Mon Sep 17 00:00:00 2001 From: knakatasf Date: Sat, 5 Jul 2025 06:26:12 +0200 Subject: [PATCH 04/10] chore(docs): Edited schemas.md --- site/content/3.12/develop/http-api/schemas.md | 1060 ++++------------- 1 file changed, 205 insertions(+), 855 deletions(-) diff --git a/site/content/3.12/develop/http-api/schemas.md b/site/content/3.12/develop/http-api/schemas.md index 7a326eb386..0a487d8435 100644 --- a/site/content/3.12/develop/http-api/schemas.md +++ b/site/content/3.12/develop/http-api/schemas.md @@ -1,52 +1,31 @@ --- - -title: HTTP interface for database structures (schema, graph, view) +title: HTTP interface for sampled schemas of collection in graph and view. menuTitle: Schemas +weight: 15 description: >- - HTTP interface for database structures gives you information about - graphs, views, collections and schemas in the database. -# GET /_api/schema -# GET /_api/schema/graph/{graph-name} -# GET /_api/schema/view/{view-name} -# GET /_api/schema/collection/{collection-name} - ---- - -The interface provides the means to collect information on database -structures including graphs, views, collections and their schemas -at one stop. This information is helpful if you want to understand -the overall shape and structure of the database. - + This HTTP interface gives you sampled document and edge structures + for collections connected by graphs or linked by views. The interface + also supports API paths to inspect individual graphs, views and collections. --- - -## What This Interface Returns -1. **graphs** – each graph shows its name and how it connects collections using edges (`_from` and `_to`). -2. **views** – each view shows its name and which collections and fields it links to. -3. **collections** – only connected or linked collections are shown. Each collection shows: - - a list of attributes (fields) - - the data types of each attribute (`string`, `number`, `bool`, or `object`) - - whether the attribute is optional (meaning some documents/edges may not have it) - - and example documents or edges for reference - ---- - -## Supported API Paths -1. [**GET /_api/schema**](#get-apischema) – Returns all graphs, views, and collections in the database. -2. [**GET /_api/schema/graph/<graph-name>**](#get-apischemagraphgraph-name) – Returns the specified graph and its connected collections. -3. [**GET /_api/schema/view/<view-name>**](#get-apischemaviewview-name) – Returns the specified view and its linked collections. -4. [**GET /_api/schema/collection/<collection-name>**](#get-apischemacollectioncollection-name) – Returns the specified collection and its schema. - +This interface gives you a unified overview of the structure of graphs and views, +along with sampled schemas for the involved collections. +The interface also supports API paths to inspect a specific graph, view, or collection individually. --- -

GET /_api/schema

+### Get all graphs, views, collections and their schemas ```openapi paths: /_db/{database-name}/_api/schema: get: - operationId: getAllSchemas + operationId: listSchemas description: | - Show all the information on the database including graphs, views and collections. + Show all graphs, views and collections in the database along with their sampled schemas. + - Each graph shows its name and how it connects collections using edges (`_from` and `_to`). + - Each view shows its name and which collections and fields it links to. + - Each collections shows a list of attribute, the data types of each attribute, + whether the attribute is optional (meaning some documents/edges mat not have it), + and example documents or edges for reference. parameters: - name: database-name in: path @@ -76,26 +55,20 @@ paths: description: | The number of example documents/edges to return per collection. Must be a non-negative integer and not be larger than `sampleNum`. - If `0`, no examples will be returned. Defaults to `1`. + If set to `0`, no examples are returned. Defaults to `1`. schema: type: integer minimum: 0 default: 1 responses: - '200 OK': + '200': description: | The schema overview was successfully returned. - '400 Bad Request': + '400': description: | Invalid query parameters (e.g., negative or non-numeric values). - '404 Not Found': - description: | - Collection, view or graph not found on the database. - '405 Method Not Allowed': - description: | - When request method is not GET. This endpoint only supports GET. - '500 Internal Server Error': + '500': description: | Internal server error. tags: @@ -103,371 +76,80 @@ paths: ``` **Examples** -HTTP Request -```http request -GET /_db/_system/_api/schema?sampleNum=100&exampleNum=1 -``` -
-HTTP Response (click to show) - -```json -{ - "graphs": [ - { - "name": "purchaseHistory", - "relations": [ - { - "collection": "purchased", - "from": [ - "customers" - ], - "to": [ - "products" - ] - } - ] - }, - { - "name": "manufacture", - "relations": [ - { - "collection": "manufactured", - "from": [ - "company" - ], - "to": [ - "products" - ] - } - ] - } - ], - "views": [ - { - "viewName": "descView", - "links": [ - { - "collectionName": "customers", - "fields": [ - { - "attribute": "comment", - "analyzers": [ - "text_en" - ] - } - ] - }, - { - "collectionName": "products", - "fields": [ - { - "attribute": "description", - "analyzers": [ - "text_en" - ] - } - ] - } - ] - } - ], - "collections": [ - { - "collectionName": "company", - "collectionType": "document", - "numOfDocuments": 3, - "schema": [ - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "address", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "established", - "types": [ - "number", - "string" - ], - "optional": false - }, - { - "attribute": "name", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "public", - "types": [ - "bool" - ], - "optional": true - } - ], - "examples": [ - { - "_id": "company/224680", - "_key": "224680", - "address": "San Francisco", - "established": 1989, - "name": "Company A" - } - ] - }, - { - "collectionName": "customers", - "collectionType": "document", - "numOfDocuments": 10, - "schema": [ - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "address", - "types": [ - "string", - "object" - ], - "optional": true - }, - { - "attribute": "age", - "types": [ - "number", - "string" - ], - "optional": false - }, - { - "attribute": "comment", - "types": [ - "string" - ], - "optional": true - }, - { - "attribute": "name", - "types": [ - "string" - ], - "optional": false - } - ], - "examples": [ - { - "_id": "customers/263", - "_key": "263", - "age": 35, - "name": "Ken" - } - ] - }, - { - "collectionName": "manufactured", - "collectionType": "edge", - "numOfEdges": 2, - "schema": [ - { - "attribute": "_from", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_to", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "amount", - "types": [ - "number" - ], - "optional": false - } - ], - "examples": [ - { - "_from": "company/224680", - "_id": "manufactured/224827", - "_key": "224827", - "_to": "products/32291", - "amount": 1200 - } - ] - }, - { - "collectionName": "products", - "collectionType": "document", - "numOfDocuments": 5, - "schema": [ - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "description", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "name", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "price", - "types": [ - "number" - ], - "optional": false - }, - { - "attribute": "version", - "types": [ - "string" - ], - "optional": true - } - ], - "examples": [ - { - "_id": "products/32235", - "_key": "32235", - "description": "This car was made in Japan, and used", - "name": "car", - "price": 120.95 - } - ] - }, - { - "collectionName": "purchased", - "collectionType": "edge", - "numOfEdges": 1, - "schema": [ - { - "attribute": "_from", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_to", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "date", - "types": [ - "string" - ], - "optional": false - } - ], - "examples": [ - { - "_from": "customers/273", - "_id": "purchased/100727", - "_key": "100727", - "_to": "products/32235", - "date": "5/25/2026" - } - ] - } - ] -} +```curl +--- +description: Create a graph, view and collections, insert sample documents, and fetch their schema +name: create_graph_view_and_collection_and_get_schema +--- +var cn1 = "customers"; +var cn2 = "restaurants"; +var cn3 = "reviewed"; +var gn = "reviewGraph"; +var vn = "restaurantView"; + +var gm = require("@arangodb/general-graph"); + +try { db._drop(cn1); } catch (e) {} +try { db._drop(cn2); } catch (e) {} +try { db._drop(cn3); } catch (e) {} +try { gm._drop(gn, true); } catch (e) {} +try { db._dropView(vn); } catch (e) {} + +var coll1 = db._create(cn1, { waitForSync: true }); +coll1.save({_key: "Alice", name: "Alice", age: 20, address: "Cologne"}); +coll1.save({_key: "Bob", name: "Bob", age: 30, address: "San Francisco"}); +coll1.save({_key: "Charlie", name: "Charlie", age: 40, address: "Tokyo"}); + +var coll2 = db._create(cn2, { waitForSync: true }); +coll2.save({_key: "Italian", name: "Italian Restaurant", address: "Milano", description: "Traditional Italian"}); +coll2.save({_key: "American", name: "American Diner", address: "New York", description: "Typical American"}); +coll2.save({_key: "Sushi", name: "Sushi Bar", address: "Kyoto", description: "Casual Japanese"}); + +gm._create(gn, [gm._relation(cn3, cn1, cn2)]); +var graph = gm._graph(gn); +graph.reviewed.save({_from: "customers/Alice", _to: "restaurants/Italian", rating: 5}); +graph.reviewed.save({_from: "customers/Bob", _to: "restaurants/American", rating: 4}); +graph.reviewed.save({_from: "customers/Charlie", _to: "restaurants/Sushi", rating: 3}); + +db._createView(vn, "arangosearch", { links: { + restaurants : { + fields: { + description: { + analyzers: ["text_en"] + } + } + } +}}); + +var url = "/_api/schema"; +var response = logCurlRequest('GET', url); +assert(response.code === 200); +logJsonResponse(response); + +db._drop(cn1); +db._drop(cn2); +db._drop(cn3); +gm._drop(gn, true); +db._dropView(vn); ``` -
--- -

GET /_api/schema/graph/<graph-name>

+### Get a graph, its connected collections, and their schemas ```openapi paths: /_db/{database-name}/_api/schema/graph/{graph-name}: get: - operationId: getGraph + operationId: listGraphsAndSchemas description: | - Show the specified graph information and its connected colletions. + Show the specified graph and its connected colletions along with their sampled schemas. + - The graph shows its name and how it connects collections using edges (`_from` and `_to`). + - Each collection shows a list of attributes, the data types of each attribute, + whether the attribute is optional (meaning some documents/edges may not have it), + and example documents or edges for reference. parameters: - name: database-name in: path @@ -505,26 +187,23 @@ paths: description: | The number of example documents/edges to return per collection. Must be a non-negative integer and not be larger than `sampleNum`. - If `0`, no examples will be returned. Defaults to `1`. + If set to `0`, no examples are returned. Defaults to `1`. schema: type: integer minimum: 0 default: 1 responses: - '200 OK': + '200': description: | The schema overview was successfully returned. - '400 Bad Request': + '400': description: | Invalid query parameters (e.g., negative or non-numeric values). - '404 Not Found': + '404': description: | Unknown path suffix (e.g., /schema/foo/bar). Collection or graph not found on the database. - '405 Method Not Allowed': - description: | - When request method is not GET. This endpoint only supports GET. - '500 Internal Server Error': + '500': description: | Internal server error. tags: @@ -532,218 +211,65 @@ paths: ``` **Examples** -HTTP Request - -```http request -GET /_db/_system/_api/schema/graph/purchaseHistory?sampleNum=100&exampleNum=1 -``` -
-HTTP Response (click to show) -```json -{ - "graphs": [ - { - "name": "purchaseHistory", - "relations": [ - { - "collection": "purchased", - "from": [ - "customers" - ], - "to": [ - "products" - ] - } - ] - } - ], - "collections": [ - { - "collectionName": "customers", - "collectionType": "document", - "numOfDocuments": 10, - "schema": [ - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "address", - "types": [ - "string", - "object" - ], - "optional": true - }, - { - "attribute": "age", - "types": [ - "number", - "string" - ], - "optional": false - }, - { - "attribute": "comment", - "types": [ - "string" - ], - "optional": true - }, - { - "attribute": "name", - "types": [ - "string" - ], - "optional": false - } - ], - "examples": [ - { - "_id": "customers/263", - "_key": "263", - "age": 35, - "name": "Ken" - } - ] - }, - { - "collectionName": "products", - "collectionType": "document", - "numOfDocuments": 5, - "schema": [ - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "description", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "name", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "price", - "types": [ - "number" - ], - "optional": false - }, - { - "attribute": "version", - "types": [ - "string" - ], - "optional": true - } - ], - "examples": [ - { - "_id": "products/32235", - "_key": "32235", - "description": "This car was made in Japan, and used", - "name": "car", - "price": 120.95 - } - ] - }, - { - "collectionName": "purchased", - "collectionType": "edge", - "numOfEdges": 1, - "schema": [ - { - "attribute": "_from", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_to", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "date", - "types": [ - "string" - ], - "optional": false - } - ], - "examples": [ - { - "_from": "customers/273", - "_id": "purchased/100727", - "_key": "100727", - "_to": "products/32235", - "date": "5/25/2026" - } - ] - } - ] -} +```curl +--- +description: Create a graph and collections, insert sample documents, and fetch their schema +name: create_graph_and_collection_and_get_schema +--- +var cn1 = "customers"; +var cn2 = "restaurants"; +var cn3 = "reviewed"; +var gn = "reviewGraph"; + +var gm = require("@arangodb/general-graph"); + +try { db._drop(cn1); } catch (e) {} +try { db._drop(cn2); } catch (e) {} +try { db._drop(cn3); } catch (e) {} +try { gm._drop(gn, true); } catch (e) {} + +var coll1 = db._create(cn1, { waitForSync: true }); +coll1.save({_key: "Alice", name: "Alice", age: 20, address: "Cologne"}); +coll1.save({_key: "Bob", name: "Bob", age: 30, address: "San Francisco"}); +coll1.save({_key: "Charlie", name: "Charlie", age: 40, address: "Tokyo"}); + +var coll2 = db._create(cn2, { waitForSync: true }); +coll2.save({_key: "Italian", name: "Italian Restaurant", address: "Milano", description: "Traditional Italian"}); +coll2.save({_key: "American", name: "American Diner", address: "New York", description: "Typical American"}); +coll2.save({_key: "Sushi", name: "Sushi Bar", address: "Kyoto", description: "Casual Japanese"}); + +gm._create(gn, [gm._relation(cn3, cn1, cn2)]); +var graph = gm._graph(gn); +graph.reviewed.save({_from: "customers/Alice", _to: "restaurants/Italian", rating: 5}); +graph.reviewed.save({_from: "customers/Bob", _to: "restaurants/American", rating: 4}); +graph.reviewed.save({_from: "customers/Charlie", _to: "restaurants/Sushi", rating: 3}); + +var url = "/_api/schema/graph/" + gn; +var response = logCurlRequest('GET', url); +assert(response.code === 200); +logJsonResponse(response); + +db._drop(cn1); +db._drop(cn2); +db._drop(cn3); +gm._drop(gn, true); ``` -
- --- - -

GET /_api/schema/view/<view-name>

+### Get a view, its linked collections, and their schemas ```openapi paths: /_db/{database-name}/_api/schema/view/{view-name}: get: - operationId: getView + operationId: listViewsAndSchemas description: | - Show the specified view information and its linked colletions. + Show the specified view and its linked collections along with their sample schemas + - The view shows its name and which collections and fields it links to. + - Each collection shows a list of attributes, the data types of each attribute + whether the attribute is optional (meaning some documents/edges may not have it), + and example documents or edges for reference parameters: - name: database-name in: path @@ -781,26 +307,23 @@ paths: description: | The number of example documents/edges to return per collection. Must be a non-negative integer and not be larger than `sampleNum`. - If `0`, no examples will be returned. Defaults to `1`. + If set to `0`, no examples are returned. Defaults to `1`. schema: type: integer minimum: 0 default: 1 responses: - '200 OK': + '200': description: | The schema overview was successfully returned. - '400 Bad Request': + '400': description: | Invalid query parameters (e.g., negative or non-numeric values). - '404 Not Found': + '404': description: | Unknown path suffix (e.g., /schema/foo/bar). Collection or view not found on the database. - '405 Method Not Allowed': - description: | - When request method is not GET. This endpoint only supports GET. - '500 Internal Server Error': + '500': description: | Internal server error. tags: @@ -808,179 +331,55 @@ paths: ``` **Examples** -HTTP Request -```http request -GET /_db/_system/_api/schema/view/descView?sampleNum=100&exampleNum=1 -``` -
-HTTP Response (click to show) - -```json -{ - "views": [ - { - "viewName": "descView", - "links": [ - { - "collectionName": "customers", - "fields": [ - { - "attribute": "comment", - "analyzers": [ - "text_en" - ] - } - ] - }, - { - "collectionName": "products", - "fields": [ - { - "attribute": "description", - "analyzers": [ - "text_en" - ] - } - ] - } - ] - } - ], - "collections": [ - { - "collectionName": "customers", - "collectionType": "document", - "numOfDocuments": 10, - "schema": [ - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "address", - "types": [ - "string", - "object" - ], - "optional": true - }, - { - "attribute": "age", - "types": [ - "number", - "string" - ], - "optional": false - }, - { - "attribute": "comment", - "types": [ - "string" - ], - "optional": true - }, - { - "attribute": "name", - "types": [ - "string" - ], - "optional": false - } - ], - "examples": [ - { - "_id": "customers/263", - "_key": "263", - "age": 35, - "name": "Ken" - } - ] - }, - { - "collectionName": "products", - "collectionType": "document", - "numOfDocuments": 5, - "schema": [ - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "description", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "name", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "price", - "types": [ - "number" - ], - "optional": false - }, - { - "attribute": "version", - "types": [ - "string" - ], - "optional": true - } - ], - "examples": [ - { - "_id": "products/32235", - "_key": "32235", - "description": "This car was made in Japan, and used", - "name": "car", - "price": 120.95 - } - ] - } - ] -} +```curl +--- +description: Create a view and collections, insert sample documents, and fetch their schema +name: create_view_and_collection_and_get_schema +--- +var cn = "restaurants"; +var vn = "restaurantView"; +try { db._drop(cn); } catch (e) {} +try { db._dropView(vn); } catch (e) {} + +var coll = db._create(cn, { waitForSync: true }); +coll.save({name: "Italian Restaurant", address: "Milano", description: "Traditional Italian"}); +coll.save({name: "American Diner", address: "New York", description: "Typical American"}); +coll.save({name: "Sushi Bar", address: "Kyoto", description: "Casual Japanese"}); + +db._createView(vn, "arangosearch", { links: { + restaurants : { + fields: { + description: { + analyzers: ["text_en"] + } + } + } +}}); + +var url = "/_api/schema/view/" + vn; +var response = logCurlRequest('GET', url); +assert(response.code === 200); +logJsonResponse(response); + +db._dropView(vn); +db._drop(cn) ``` -
--- -

GET /_api/schema/collection/<collection-name>

+### Get a collection, and its schemas ```openapi paths: /_db/{database-name}/_api/schema/collection/{collection-name}: get: - operationId: getCollection + operationId: listCollectionAndSchemas description: | - Show the specified collection information and its schemas. + Show the specified collection and its sampled schemas. + The schema shows a list of attributes (fields), the data types of each attribute + whether the attribute is optional (meaning some documents/edges may not have it), + and example documents or edges for reference parameters: - name: database-name in: path @@ -1018,26 +417,23 @@ paths: description: | The number of example documents/edges to return per collection. Must be a non-negative integer and not be larger than `sampleNum`. - If `0`, no examples will be returned. Defaults to `1`. + If set to `0`, no examples are returned. Defaults to `1`. schema: type: integer minimum: 0 default: 1 responses: - '200 OK': + '200': description: | The schema overview was successfully returned. - '400 Bad Request': + '400 ': description: | Invalid query parameters (e.g., negative or non-numeric values). - '404 Not Found': + '404': description: | Unknown path suffix (e.g., /schema/foo/bar). Collection not found on the database. - '405 Method Not Allowed': - description: | - When request method is not GET. This endpoint only supports GET. - '500 Internal Server Error': + '500': description: | Internal server error. tags: @@ -1045,72 +441,26 @@ paths: ``` **Examples** -HTTP Request -```http request -GET /_db/_system/_api/schema/collection/products?sampleNum=100&exampleNum=1 -``` -
-HTTP Response (click to show) +```curl +--- +description: Create a collection, insert sample documents, and fetch its schema +name: create_collection_and_get_schema +--- +var cn = "customers"; +try { db._drop(cn); } catch (e) {} -```json -{ - "collectionName": "products", - "collectionType": "document", - "numOfDocuments": 5, - "schema": [ - { - "attribute": "_id", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "_key", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "description", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "name", - "types": [ - "string" - ], - "optional": false - }, - { - "attribute": "price", - "types": [ - "number" - ], - "optional": false - }, - { - "attribute": "version", - "types": [ - "string" - ], - "optional": true - } - ], - "examples": [ - { - "_id": "products/32235", - "_key": "32235", - "description": "This car was made in Japan, and used", - "name": "car", - "price": 120.95 - } - ] - } -``` -
\ No newline at end of file +var coll = db._create(cn, { waitForSync: true }); +coll.save({name: "Alice", age: 20, address: "Cologne"}); +coll.save({name: "Bob", age: 30, address: "San Francisco"}); +coll.save({name: "Charlie", age: 40, address: "Tokyo"}); + +var url = "/_api/schema/collection/" + cn; + +var response = logCurlRequest('GET', url); + +assert(response.code === 200); + +logJsonResponse(response); +db._drop(cn); +``` \ No newline at end of file From f8b6212facdfbc136e30ae21dd592ae4d7857e7f Mon Sep 17 00:00:00 2001 From: knakatasf Date: Sun, 6 Jul 2025 20:23:40 +0200 Subject: [PATCH 05/10] chore(docs): Changed the weight of schemas.md --- site/content/3.12/develop/http-api/schemas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/3.12/develop/http-api/schemas.md b/site/content/3.12/develop/http-api/schemas.md index 0a487d8435..4f7f29be48 100644 --- a/site/content/3.12/develop/http-api/schemas.md +++ b/site/content/3.12/develop/http-api/schemas.md @@ -1,7 +1,7 @@ --- title: HTTP interface for sampled schemas of collection in graph and view. menuTitle: Schemas -weight: 15 +weight: 33 description: >- This HTTP interface gives you sampled document and edge structures for collections connected by graphs or linked by views. The interface From 2c6bd92d84a9b1507ff6f4c301cbfd2edcc13dc1 Mon Sep 17 00:00:00 2001 From: Simran Spiller Date: Tue, 8 Jul 2025 13:59:11 +0200 Subject: [PATCH 06/10] First round of review --- site/content/3.12/develop/http-api/schemas.md | 531 +++++++++--------- 1 file changed, 255 insertions(+), 276 deletions(-) diff --git a/site/content/3.12/develop/http-api/schemas.md b/site/content/3.12/develop/http-api/schemas.md index 4f7f29be48..c0593e31f5 100644 --- a/site/content/3.12/develop/http-api/schemas.md +++ b/site/content/3.12/develop/http-api/schemas.md @@ -1,78 +1,75 @@ --- -title: HTTP interface for sampled schemas of collection in graph and view. +title: HTTP interface for sampled schemas of collections, Views, and graphs menuTitle: Schemas weight: 33 description: >- - This HTTP interface gives you sampled document and edge structures - for collections connected by graphs or linked by views. The interface - also supports API paths to inspect individual graphs, views and collections. ---- -This interface gives you a unified overview of the structure of graphs and views, -along with sampled schemas for the involved collections. -The interface also supports API paths to inspect a specific graph, view, or collection individually. + The Schema API lets you analyze the document structure of your data to infer + schemas --- +This interface provides a unified overview of the structure of graphs, Views, +and the underlying collections. The involved collections are sampled using a +configurable number of documents. The result is approximate schema based on the +analyzed subset of data. It can help you understand how the data is organized, +what data types are used, and which attributes you may want to query. -### Get all graphs, views, collections and their schemas +## Get schemas for all graphs, Views, and collections ```openapi paths: - /_db/{database-name}/_api/schema: - get: - operationId: listSchemas - description: | - Show all graphs, views and collections in the database along with their sampled schemas. - - Each graph shows its name and how it connects collections using edges (`_from` and `_to`). - - Each view shows its name and which collections and fields it links to. - - Each collections shows a list of attribute, the data types of each attribute, - whether the attribute is optional (meaning some documents/edges mat not have it), - and example documents or edges for reference. - parameters: - - name: database-name - in: path - required: true - example: _system - description: | - The name of a database that you want to examine its structures and schemas. - schema: - type: string - - - name: sampleNum - in: query - required: false - description: | - The number of documents/edges to examine per collection when determining - attribute types and whether an attribute is optional. - Must be a positive integer. Defaults to `100`. - If larger than the number of documents, it will only use the available ones. - schema: - type: integer - minimum: 1 - default: 100 - - - name: exampleNum - in: query - required: false - description: | - The number of example documents/edges to return per collection. - Must be a non-negative integer and not be larger than `sampleNum`. - If set to `0`, no examples are returned. Defaults to `1`. - schema: - type: integer - minimum: 0 - default: 1 - - responses: - '200': - description: | - The schema overview was successfully returned. - '400': - description: | - Invalid query parameters (e.g., negative or non-numeric values). - '500': - description: | - Internal server error. - tags: - - Schema + /_db/{database-name}/_api/schema: + get: + operationId: listSchemas + description: | + Show all graphs, views and collections in the database along with their sampled schemas. + - Each graph shows its name and how it connects collections using edges (`_from` and `_to`). + - Each view shows its name and which collections and fields it links to. + - Each collections shows a list of attribute, the data types of each attribute, + whether the attribute is optional (meaning some documents/edges mat not have it), + and example documents or edges for reference. + parameters: + - name: database-name + in: path + required: true + example: _system + description: | + The name of a database that you want to examine its structures and schemas. + schema: + type: string + - name: sampleNum + in: query + required: false + description: | + The number of documents/edges to examine per collection when determining + attribute types and whether an attribute is optional. + Must be a positive integer. + If larger than the number of documents, it will only use the available ones. + schema: + type: integer + minimum: 1 + default: 100 + - name: exampleNum + in: query + required: false + description: | + The number of example documents/edges to return per collection. + Must be a non-negative integer and not be larger than `sampleNum`. + If set to `0`, no examples are returned. + schema: + type: integer + minimum: 0 + default: 1 + responses: + '200': + description: | + The schema overview was successfully returned. + '400': + description: | + Invalid query parameters (e.g., negative or non-numeric values). + '500': + description: | + Internal server error. + tags: + - Schema ``` **Examples** @@ -134,80 +131,73 @@ gm._drop(gn, true); db._dropView(vn); ``` ---- - - -### Get a graph, its connected collections, and their schemas +## Get a schema for a graph ```openapi paths: - /_db/{database-name}/_api/schema/graph/{graph-name}: - get: - operationId: listGraphsAndSchemas - description: | - Show the specified graph and its connected colletions along with their sampled schemas. - - The graph shows its name and how it connects collections using edges (`_from` and `_to`). - - Each collection shows a list of attributes, the data types of each attribute, - whether the attribute is optional (meaning some documents/edges may not have it), - and example documents or edges for reference. - parameters: - - name: database-name - in: path - required: true - example: _system - description: | - The name of a database that you want to examine its structures and schemas. - schema: - type: string - - - name: graph-name - in: path - required: true - description: | - The name of a graph that you want to examine its structures and schemas. - schema: - type: string - - - name: sampleNum - in: query - required: false - description: | - The number of documents/edges to examine per collection - when determining attribute types and whether an attribute is optional. - Must be a positive integer. Defaults to `100`. - If larger than the number of documents, it will only use the available ones. - schema: - type: integer - minimum: 1 - default: 100 - - - name: exampleNum - in: query - required: false - description: | - The number of example documents/edges to return per collection. - Must be a non-negative integer and not be larger than `sampleNum`. - If set to `0`, no examples are returned. Defaults to `1`. - schema: - type: integer - minimum: 0 - default: 1 - - responses: - '200': - description: | - The schema overview was successfully returned. - '400': - description: | - Invalid query parameters (e.g., negative or non-numeric values). - '404': - description: | - Unknown path suffix (e.g., /schema/foo/bar). Collection or graph not found on the database. - '500': - description: | - Internal server error. - tags: - - Schema + /_db/{database-name}/_api/schema/graph/{graph-name}: + get: + operationId: getGraphSchema + description: | + Show the specified graph and its connected collections along with their sampled schemas. + - The graph shows its name and how it connects collections using edges (`_from` and `_to`). + - Each collection shows a list of attributes, the data types of each attribute, + whether the attribute is optional (meaning some documents/edges may not have it), + and example documents or edges for reference. + parameters: + - name: database-name + in: path + required: true + example: _system + description: | + The name of a database that you want to examine its structures and schemas. + schema: + type: string + - name: graph-name + in: path + required: true + description: | + The name of a graph that you want to examine its structures and schemas. + schema: + type: string + - name: sampleNum + in: query + required: false + description: | + The number of documents/edges to examine per collection + when determining attribute types and whether an attribute is optional. + Must be a positive integer. + If larger than the number of documents, it will only use the available ones. + schema: + type: integer + minimum: 1 + default: 100 + - name: exampleNum + in: query + required: false + description: | + The number of example documents/edges to return per collection. + Must be a non-negative integer and not be larger than `sampleNum`. + If set to `0`, no examples are returned. + schema: + type: integer + minimum: 0 + default: 1 + responses: + '200': + description: | + The schema overview was successfully returned. + '400': + description: | + Invalid query parameters (e.g., negative or non-numeric values). + '404': + description: | + Unknown path suffix (e.g., /schema/foo/bar). Collection or graph not found on the database. + '500': + description: | + Internal server error. + tags: + - Schema ``` **Examples** @@ -255,79 +245,74 @@ db._drop(cn2); db._drop(cn3); gm._drop(gn, true); ``` ---- -### Get a view, its linked collections, and their schemas +## Get a schema for a View ```openapi paths: - /_db/{database-name}/_api/schema/view/{view-name}: - get: - operationId: listViewsAndSchemas - description: | - Show the specified view and its linked collections along with their sample schemas - - The view shows its name and which collections and fields it links to. - - Each collection shows a list of attributes, the data types of each attribute - whether the attribute is optional (meaning some documents/edges may not have it), - and example documents or edges for reference - parameters: - - name: database-name - in: path - required: true - example: _system - description: | - The name of a database that you want to examine its structures and schemas. - schema: - type: string - - - name: view-name - in: path - required: true - description: | - The name of a view that you want to examine its structures and schemas. - schema: - type: string - - - name: sampleNum - in: query - required: false - description: | - The number of documents/edges to examine per collection - when determining attribute types and whether an attribute is optional. - Must be a positive integer. Defaults to `100`. - If larger than the number of documents, it will only use the available ones. - schema: - type: integer - minimum: 1 - default: 100 - - - name: exampleNum - in: query - required: false - description: | - The number of example documents/edges to return per collection. - Must be a non-negative integer and not be larger than `sampleNum`. - If set to `0`, no examples are returned. Defaults to `1`. - schema: - type: integer - minimum: 0 - default: 1 - - responses: - '200': - description: | - The schema overview was successfully returned. - '400': - description: | - Invalid query parameters (e.g., negative or non-numeric values). - '404': - description: | - Unknown path suffix (e.g., /schema/foo/bar). Collection or view not found on the database. - '500': - description: | - Internal server error. - tags: - - Schema + /_db/{database-name}/_api/schema/view/{view-name}: + get: + operationId: getViewSchema + description: | + Show the specified view and its linked collections along with their sample schemas + - The view shows its name and which collections and fields it links to. + - Each collection shows a list of attributes, the data types of each attribute + whether the attribute is optional (meaning some documents/edges may not have it), + and example documents or edges for reference + parameters: + - name: database-name + in: path + required: true + example: _system + description: | + The name of a database that you want to examine its structures and schemas. + schema: + type: string + - name: view-name + in: path + required: true + description: | + The name of a view that you want to examine its structures and schemas. + schema: + type: string + - name: sampleNum + in: query + required: false + description: | + The number of documents/edges to examine per collection + when determining attribute types and whether an attribute is optional. + Must be a positive integer. + If larger than the number of documents, it will only use the available ones. + schema: + type: integer + minimum: 1 + default: 100 + - name: exampleNum + in: query + required: false + description: | + The number of example documents/edges to return per collection. + Must be a non-negative integer and not be larger than `sampleNum`. + If set to `0`, no examples are returned. + schema: + type: integer + minimum: 0 + default: 1 + responses: + '200': + description: | + The schema overview was successfully returned. + '400': + description: | + Invalid query parameters (e.g., negative or non-numeric values). + '404': + description: | + Unknown path suffix (e.g., /schema/foo/bar). Collection or view not found on the database. + '500': + description: | + Internal server error. + tags: + - Schema ``` **Examples** @@ -366,78 +351,72 @@ db._dropView(vn); db._drop(cn) ``` ---- - -### Get a collection, and its schemas +## Get a schema for a collection ```openapi paths: - /_db/{database-name}/_api/schema/collection/{collection-name}: - get: - operationId: listCollectionAndSchemas - description: | - Show the specified collection and its sampled schemas. - The schema shows a list of attributes (fields), the data types of each attribute - whether the attribute is optional (meaning some documents/edges may not have it), - and example documents or edges for reference - parameters: - - name: database-name - in: path - required: true - example: _system - description: | - The name of a database that you want to examine its structures and schemas. - schema: - type: string - - - name: collection-name - in: path - required: true - description: | - The name of a collection that you want to examine its schemas. - schema: - type: string - - - name: sampleNum - in: query - required: false - description: | - The number of documents/edges to examine per collection - when determining attribute types and whether an attribute is optional. - Must be a positive integer. Defaults to `100`. - If larger than the number of documents, it will only use the available ones. - schema: - type: integer - minimum: 1 - default: 100 - - - name: exampleNum - in: query - required: false - description: | - The number of example documents/edges to return per collection. - Must be a non-negative integer and not be larger than `sampleNum`. - If set to `0`, no examples are returned. Defaults to `1`. - schema: - type: integer - minimum: 0 - default: 1 - - responses: - '200': - description: | - The schema overview was successfully returned. - '400 ': - description: | - Invalid query parameters (e.g., negative or non-numeric values). - '404': - description: | - Unknown path suffix (e.g., /schema/foo/bar). Collection not found on the database. - '500': - description: | - Internal server error. - tags: - - Schema + /_db/{database-name}/_api/schema/collection/{collection-name}: + get: + operationId: getCollectionSchema + description: | + Show the specified collection and its sampled schemas. + The schema shows a list of attributes (fields), the data types of each attribute + whether the attribute is optional (meaning some documents/edges may not have it), + and example documents or edges for reference + parameters: + - name: database-name + in: path + required: true + example: _system + description: | + The name of a database that you want to examine its structures and schemas. + schema: + type: string + - name: collection-name + in: path + required: true + description: | + The name of a collection that you want to examine its schemas. + schema: + type: string + - name: sampleNum + in: query + required: false + description: | + The number of documents/edges to examine per collection + when determining attribute types and whether an attribute is optional. + Must be a positive integer. + If larger than the number of documents, it will only use the available ones. + schema: + type: integer + minimum: 1 + default: 100 + - name: exampleNum + in: query + required: false + description: | + The number of example documents/edges to return per collection. + Must be a non-negative integer and not be larger than `sampleNum`. + If set to `0`, no examples are returned. + schema: + type: integer + minimum: 0 + default: 1 + responses: + '200': + description: | + The schema overview was successfully returned. + '400': + description: | + Invalid query parameters (e.g., negative or non-numeric values). + '404': + description: | + Unknown path suffix (e.g., /schema/foo/bar). Collection not found on the database. + '500': + description: | + Internal server error. + tags: + - Schema ``` **Examples** @@ -463,4 +442,4 @@ assert(response.code === 200); logJsonResponse(response); db._drop(cn); -``` \ No newline at end of file +``` From 745c5270b68807f43da0fad97973bee9a54adacc Mon Sep 17 00:00:00 2001 From: Paula Mihu <97217318+nerpaula@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:53:49 +0200 Subject: [PATCH 07/10] DOC-759 | Reworked GraphML documentation (#722) * reworked graphml documentation * fix merge related errors * Apply suggestions from code review Co-authored-by: Simran * review * minor changes * review --------- Co-authored-by: Simran --- site/content/3.13/aql/functions/vector.md | 2 +- site/content/3.13/data-science/_index.md | 6 +- .../3.13/data-science/arangographml/deploy.md | 76 ----- .../3.13/data-science/arangographml/ui.md | 264 ------------------ .../{arangographml => graphml}/_index.md | 68 +++-- .../notebooks-api.md} | 228 ++++----------- .../3.13/data-science/graphml/quickstart.md | 57 ++++ site/content/3.13/data-science/graphml/ui.md | 244 ++++++++++++++++ .../working-with-indexes/vector-indexes.md | 2 +- .../images/graphml-ui-confusion-matrix.png | Bin 0 -> 88842 bytes 10 files changed, 399 insertions(+), 548 deletions(-) delete mode 100644 site/content/3.13/data-science/arangographml/deploy.md delete mode 100644 site/content/3.13/data-science/arangographml/ui.md rename site/content/3.13/data-science/{arangographml => graphml}/_index.md (70%) rename site/content/3.13/data-science/{arangographml/getting-started.md => graphml/notebooks-api.md} (79%) create mode 100644 site/content/3.13/data-science/graphml/quickstart.md create mode 100644 site/content/3.13/data-science/graphml/ui.md create mode 100644 site/content/images/graphml-ui-confusion-matrix.png diff --git a/site/content/3.13/aql/functions/vector.md b/site/content/3.13/aql/functions/vector.md index e6a6de0134..45341b8ea8 100644 --- a/site/content/3.13/aql/functions/vector.md +++ b/site/content/3.13/aql/functions/vector.md @@ -12,7 +12,7 @@ To use vector search, you need to have vector embeddings stored in documents and the attribute that stores them needs to be indexed by a [vector index](../../index-and-search/indexing/working-with-indexes/vector-indexes.md). -You can calculate vector embeddings using [ArangoDB's GraphML](../../data-science/arangographml/_index.md) +You can calculate vector embeddings using [ArangoDB's GraphML](../../data-science/graphml/_index.md) capabilities (available in ArangoGraph) or using external tools. {{< warning >}} diff --git a/site/content/3.13/data-science/_index.md b/site/content/3.13/data-science/_index.md index 8655091987..55d9602ec5 100644 --- a/site/content/3.13/data-science/_index.md +++ b/site/content/3.13/data-science/_index.md @@ -1,6 +1,6 @@ --- -title: Data Science -menuTitle: Data Science +title: Data Science and GenAI +menuTitle: Data Science & GenAI weight: 115 description: >- ArangoDB lets you apply analytics and machine learning to graph data at scale @@ -69,7 +69,7 @@ GraphML can answer questions like: ![Graph ML](../../images/graph-ml.png) For ArangoDB's enterprise-ready, graph-powered machine learning offering, -see [ArangoGraphML](arangographml/_index.md). +see [ArangoGraphML](graphml/_index.md). ## Use Cases diff --git a/site/content/3.13/data-science/arangographml/deploy.md b/site/content/3.13/data-science/arangographml/deploy.md deleted file mode 100644 index 2fa947b758..0000000000 --- a/site/content/3.13/data-science/arangographml/deploy.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Deploy ArangoGraphML -menuTitle: Deploy -weight: 5 -description: >- - You can deploy ArangoGraphML in your own Kubernetes cluster or use the managed - cloud service that comes with a ready-to-go, pre-configured environment ---- - -## Managed cloud service versus self-managed - -ArangoDB offers two deployment options, tailored to suit diverse requirements -and infrastructure preferences: -- Managed cloud service via the [ArangoGraph Insights Platform](https://dashboard.arangodb.cloud/home?utm_source=docs&utm_medium=cluster_pages&utm_campaign=docs_traffic) -- Self-managed solution via the [ArangoDB Kubernetes Operator](https://github.com/arangodb/kube-arangodb) - -### ArangoGraphML - -ArangoGraphML provides enterprise-ready Graph Machine Learning as a -Cloud Service via Jupyter Notebooks that run on the -[ArangoGraph Insights Platform](https://dashboard.arangodb.cloud/home?utm_source=docs&utm_medium=cluster_pages&utm_campaign=docs_traffic). - -{{< tip >}} -To get access to ArangoGraphML services and packages, -[get in touch](https://www.arangodb.com/contact/) -with the ArangoDB team. -{{< /tip >}} - -- **Accessible at all levels** - - Low code UI - - Notebooks - - APIs -- **Full usability** - - MLOps lifecycle - - Metrics - - Metadata capture - - Model management - -![ArangoGraphML Pipeline](../../../images/ArangoGraphML_Pipeline.png) - -#### Setup - -The ArangoGraphML managed-service runs on the -[ArangoGraph Insights Platform](https://dashboard.arangodb.cloud/home?utm_source=docs&utm_medium=cluster_pages&utm_campaign=docs_traffic). -It offers a pre-configured environment where everything, -including necessary components and configurations, comes preloaded. You don't -need to set up or configure the infrastructure, and can immediately start using the -GraphML functionalities. - -To summarize, all you need to do is: -1. Sign up for an [ArangoGraph account](https://dashboard.arangodb.cloud/home?utm_source=docs&utm_medium=cluster_pages&utm_campaign=docs_traffic). -2. Create a new [deployment in ArangoGraph](../../arangograph/deployments/_index.md#how-to-create-a-new-deployment). -3. Start using the ArangoGraphML functionalities. - -### Self-managed ArangoGraphML - -The self-managed solution enables you to deploy and manage ArangoML within your -Kubernetes cluster using the [ArangoDB Kubernetes Operator](https://github.com/arangodb/kube-arangodb). - -The self-managed package includes the same features as in ArangoGraphML. -The primary distinction lies in the environment setup: with the self-managed -solution, you have direct control over configuring your environment. - -#### Setup - -You can run ArangoGraphML in your Kubernetes -cluster provided you already have a running `ArangoDeployment`. If you don't -have one yet, consider checking the installation guide of the -[ArangoDB Kubernetes Operator](https://arangodb.github.io/kube-arangodb/docs/using-the-operator.html) -and the [ArangoDeployment Custom Resource](https://arangodb.github.io/kube-arangodb/docs/deployment-resource-reference.html) -description. - -To start ArangoGraphML in your Kubernetes cluster, follow the instructions provided -in the [ArangoMLExtension Custom Resource](https://arangodb.github.io/kube-arangodb/docs/mlextension-resource.html) -description. Once the `CustomResource` has been created and the ArangoGraphML extension -is ready, you can start using it. \ No newline at end of file diff --git a/site/content/3.13/data-science/arangographml/ui.md b/site/content/3.13/data-science/arangographml/ui.md deleted file mode 100644 index bbe6f9df88..0000000000 --- a/site/content/3.13/data-science/arangographml/ui.md +++ /dev/null @@ -1,264 +0,0 @@ ---- -title: GraphML -menuTitle: GraphML -weight: 15 -description: >- - Learn how to create, configure, and run a full machine learning workflow for ArangoGraphML using the steps and features in the ArangoDB web interface ---- -Solve high-computational graph problems with Graph Machine Learning. Apply ML on a selected graph to predict connections, get better product recommendations, classify nodes, and perform node embeddings. Configure and run the whole machine learning flow entirely in the web interface. - -## What You Can Do with GraphML - -GraphML directly supports two primary machine learning tasks: - -* **Node Classification:** Automatically assign a label to nodes in your graph. For example, you can classify customers as "likely to churn" or "high value," or identify fraudulent transactions. -* **Node Embeddings:** Generate numerical representations (vectors) for each node. The main purpose of these embeddings is to measure similarity: nodes that are alike are placed close together in a high-dimensional space. By comparing the embeddings of two nodes, you can calculate their similarity. This is the foundation for advanced tasks like link prediction (predicting a future connection), building recommendation engines, and finding similar items. - -## How ArangoGraphML Works - -The underlying framework for ArangoGraphML is **[GraphSAGE](https://snap.stanford.edu/graphsage/)**. GraphSAGE (Graph Sample and AggreGatE) is a powerful Graph Neural Network (GNN) **framework** designed for inductive representation learning on large graphs. It is used to generate low-dimensional vector representations for nodes and is especially useful for graphs that have rich node attribute information. The overall process involves two main stages: - -1. **Featurization**: Your raw graph data is transformed into numerical representations that the model can understand. - * The system iterates over your selected vertices and converts their attributes: booleans become `0` or `1`, numbers are normalized, and text attributes are converted into numerical vectors using sentence transformers. - * All of these numerical features are then combined (concatenated). - * Finally, **Incremental PCA** (Incremental Principal Component Analysis a dimensionality reduction technique) is used to reduce the size of the combined features, which helps remove noise and keep only the most important information. - -2. **Training**: The model learns from the graph's structure by sampling and aggregating information from each node's local neighborhood. - * For each node, GraphSAGE looks at connections up to **2 hops away**. - * Specifically, it uniformly samples up to **25 direct neighbors** (depth 1) and for each of those, it samples up to **10 of their neighbors** (depth 2). - * By aggregating feature information from this sampled neighborhood, the model creates a rich "embedding" for each node that captures both its own features and its role in the graph. - -## Limitations - -* **Edge Attributes**: The current version of ArangoGraphML does not support the use of edge attributes as features. -* **Dangling Edges**: Edges that point to non-existent vertices ("dangling edges") are not caught during the featurization analysis. They may cause errors later, during the Training phase. -* **Prediction Scope**: Predictions can only be run in batches on the graph. It is not possible to request a prediction for a single document on-the-fly (Example, via an AQL query). -* **Memory Usage**: Both featurization and training can be memory-intensive. Out-of-memory errors can occur on large graphs with insufficient system resources. - -## The GraphML Workflow - -The entire process is organized into sequential steps within a **Project**, giving you a clear path from data to prediction: - -1. **Featurization:** Select your data and convert it into numerical features. -2. **Training:** Train a GraphSAGE model on the features and graph structure. -3. **Model Selection:** Evaluate the trained models and choose the best one. -4. **Prediction:** Use the selected model to generate predictions on your data. -5. **Scheduling (Optional):** Automate the prediction process to run at regular intervals. - -## Creating a GraphML Project - -To create a new GraphML project using the ArangoDB Web Interface, follow these steps: - -1. From the left-hand sidebar, select the database where you want to create the project. -2. In the left-hand navigation menu, click **Data Science Suite** to open the GraphML project management interface, then click Run GraphML. - ![Navigate to Data Science](../../../images/datascience-intro.jpg) -3. In the **GraphML projects** view, click **Add new project**. -4. The **Create ML project** modal opens. Enter a **Name** for your machine learning project. -5. Click the **Create project** button to finalize the creation. -6. After creation, the new project appears in the list under **GraphML projects**. Click the project name to begin with a Featurization job. - -## Featurization Phase - -After clicking on a project name, you are taken to a screen where you can configure and start a new Featurization job. Follow these steps: -- **Select a Graph** – In the **Features** section, choose your target graph from the **Select a graph** dropdown. -- **Choose Vertex Collections** – Pick the vertex collections that you want to include for feature extraction. -- **Select Attributes** – Choose the attributes from your vertex collection to convert into machine-understandable features. - -{{< info >}} -Attributes cannot be used if their values are lists or arrays. -{{< /info >}} - -{{< info >}} -A metagraph is basically just the configured subset of a graph (the vertex and edge collections and the specified attributes). This is what you see represented in the metagraph object in the JSON specification on the right. -{{< /info >}} - -The featurization process has several configurable options, grouped into Configuration and Advanced settings. These are also shown in a JSON format on the right side of the screen for transparency. - -**Configuration** -These settings control the overall featurization job and how features are stored. - -- **Batch size** – The number of documents to process in a single batch. -- **Run analysis checks** – Whether to run analysis checks to perform a high-level analysis of the data quality before proceeding. Default is `true`. -- **Skip labels** – Skip the featurization process for attributes marked as labels. Default is `false`. - -**Feature Storage Options** -These settings control where the generated features are stored. - -- **Overwrite FS graph** – Whether to overwrite the Feature Store graph if features were previously generated. Default is `false`, therefore features are written to an existing graph Featere store graph. -- **Write to source graph** – Whether to store the generated features on the Source Graph. Default is `true`. -- **Use feature store** – Enable the use of the Feature Store database, which allows you to store features separately from your Source Database. Default is `false`, therefore feature are written to the source graph. - -**Advanced Settings: Handling Imperfect Data** - -Real-world data is often messy. It can have missing values or mismatched data types. This section allows you to define default rules for how the featurization process should handle these data quality issues for each feature type, preventing the job from failing unexpectedly. - -For each feature type (Text, Numeric, Category, and Label), you can set two types of strategies: - -**Missing Strategy:** Defines what to do when an attribute is completely missing from a document. - -**Mismatch Strategy:** Defines what to do when an attribute exists but has the wrong data type. - -**Missing Value Strategies** - -**Raise:** The job immediately stops and reports an error if a data type mismatch is found. Use this when any type mismatch indicates a critical data error. - -**Replace:** Replaces a missing value with a default you provide (e.g., 0 for numbers, "unknown" for text). The job then continues. Use this when missing values are expected. - -**Mismatch Value Strategies** - -**Raise:** The strictest option. The job will immediately stop and report an error if a data type mismatch is found. Use this when any type mismatch indicates a critical data error. - -**Replace:** If a mismatch is found, the system immediately replaces the value with the default you specify, without attempting to convert it first. Use this when you don't trust the mismatched data and prefer to substitute it directly. - -**Coerce and raise:** A balanced approach. The system first tries to convert (coerce) the value to the correct type (e.g., string "123" to number 123). If the conversion is successful, it uses the new value. If it fails, the job stops. This is often the best default strategy. - -**Coerce and replace:** The most forgiving option. The system first tries to convert the value. If the conversion fails, it replaces the value with the default you specify and continues the job. Use this for very "dirty" datasets where completing the job is the highest priority. - -Once all selections are done, click the **Begin featurization** button. This triggers a **node embedding-compatible featurization job**. Once the job status changes to **Ready for training**, you can start the **ML Training** step. - -![Navigate to Featurization](../../../images/graph-ml-ui-featurization.png) - -## Training Phase - -The training is the second step in the ML workflow after featurization. In the training phase, you configure and launch a machine learning training job on your graph data. - - -- **Select a training job type** – From the **Select a type of training job** dropdown, choose the type of model you want to train (Example, Node Classification, Node Embedding). - - -#### Node Classification - -Node Classification is used to categorize the nodes in your graph based on their features and structural connections within the graph. - -**Use cases include:** -- Entity categorization (Example, movies into genres, users into segments) -- Fraud detection in transaction networks - -**Configuration Parameters:** -- **Type of Training Job:** Node classification -- **Target Vertex Collection:** Choose the collection to classify (Example, `movie`) -- **Batch Size:** The nummer of documents processed in a single training iteration. (Example, 256) -- **Data Load Batch Size:** The number of documents loaded from ArangoDB into memory in a single batch during the data loading phase. (Example, 50000) -- **Data Load Parallelism:** The number of parallel processes used when loading data from ArangoDB into memory for trainnig. (Example, 10) - -After setting these values, click the **Begin training** button to start the job. - -![Node Classification](../../../images/ml-nodeclassification.png) - - #### Node Embedding - -Node Embedding is used to generate vector embeddings (dense numerical representations) of graph nodes that capture structural and feature-based information. - -**Use cases include:** -- Similarity search (Example, finding similar products, users, or documents) -- Link prediction (Example, suggesting new connections) - -**Configuration Parameters:** -- **Type of Training Job:** Node embeddings -- **Target Vertex Collection:** Select the collection to generate embeddings for (Example, `movie` or `person`) -- No label is required for training in this mode - -Once the configuration is complete, click **Begin training** to launch the embedding job. - -![Node Embeddings](../../../images/ml-node-embedding.png) - - -After training is complete, the next step in the ArangoGraphML workflow is **Model Selection**. - -## Model Selection Phase - -Once the training is finished, the job status updates to **READY FOR MODEL SELECTION**. This means the model has been trained using the provided vertex and edge data and is now ready for evaluation. - -**Understanding Vertex Collections:** - -**X Vertex Collection:** These are the source nodes used during training. They represent the full set of nodes on which features were computed (Example, person, movie). - -**Y Vertex Collection:** These are the target nodes that contain labeled data. The labels in this collection are used to supervise the training process and are the basis for evaluating prediction quality. - -The target collection is where the model's predictions are stored when running a prediction job. - -**Model Selection Interface:** - -A list of trained models is displayed, along with performance metrics (Accuracy, Precision, Recall, F1 score, Loss). -Review the results of different model runs and configurations. - -Select the best performing model suitable for your prediction task. - -![Model Selection](../../../images/graph-ml-model.png) - -## Prediction Phase - -After selecting a model, you can create a Prediction Job. The Prediction Job generates predictions and persists them to the source graph, either in a new collection or within the source documents. - -### Overview - -The Prediction interface allows inference to be run using the selected model. It enables configuration of how predictions are executed, which collections are involved, and whether new or outdated documents should be automatically featurized before prediction. - -You have two important options for this: - -**Featurize new documents:** Enable this option to generate features for documents that have been added since the model was trained. This is useful for getting predictions on new data without having to retrain the model. - -**Featurize outdated documents:** Enable this option to re-generate features for documents that have been modified since the last featurization. This ensures your predictions reflect the latest changes to your data. -In addition to these settings, you will also define the target data, where to store results, and whether to run the job on a recurring schedule. - -In addition to these settings, you also define the target data, where to store results, and whether to run the job on a recurring schedule. - -![prediction phase](../../../images/graph-prediction.png) - -### Configuration Options -The Prediction screen displays the following configuration options: - -- Selected Model: Displays the model selected during the Model Selection phase. This model will be used to perform inference. - -- Target Vertex Collection: This is the vertex collection on which predictions are applied. - -- Prediction Type: Depending on the training job (for example, classification or embedding), the prediction outputs class labels or updated embeddings. - -### Featurization Settings -Two toggles are available to control automatic featurization during prediction - -**Featurize New Documents:** -This option controls whether newly added documents are automatically featurized. It is useful when new data arrives after training, allowing predictions to continue without requiring a full retraining process. - -**Featurize Outdated Documents:** -Enable or disable the featurization of outdated documents. Outdated documents are those whose attributes (used during featurization) have changed since the last feature computation. This ensures prediction results are based on up-to-date information. - -These options provide flexibility in handling dynamic graph data and keeping predictions relevant without repeating the entire ML workflow. - -**Data load batch size** – Specifies the number of documents to load in a single batch (Example, 500000). - -**Data load parallelism** – Number of parallel threads used to process the prediction workload (Example, 10). - -**Prediction field** – The field in the documents where the predicted values are stored. - -### Enable Scheduling - -You can configure automatic predictions using the **Enable scheduling** checkbox. - -When scheduling is turned on, predictions run automatically based on a set CRON expression. This helps keep prediction results up to date as new data is added to the system. - -#### Schedule (CRON expression) - -You can define a CRON expression that sets when the prediction job should run. For example: -0 0 1 1 * -This CRON pattern executes the prediction every year on January 1st at 00:00. - -Below the CRON field, a user-friendly scheduling interface helps translate it: - -- **Period**: Options include *Hourly*, *Daily*, *Weekly*, *Monthly*, or *Yearly*. -- **Month**: *(Example, January)* -- **Day of Month**: *(Example, 1)* -- **Day of Week**: *(optional)* -- **Hours and Minutes**: Set the exact time for execution *(Example, 0:00)* - - -### Execute Prediction -After reviewing the configuration, click the Run Prediction button. ArangoGraphML then: - -- Perform featurization - -- Run inference using the selected model - -- Write prediction results into the target vertex collection or a specified output location - -Once prediction is complete, you can analyze the results directly in the Web Interface or export them for downstream use. diff --git a/site/content/3.13/data-science/arangographml/_index.md b/site/content/3.13/data-science/graphml/_index.md similarity index 70% rename from site/content/3.13/data-science/arangographml/_index.md rename to site/content/3.13/data-science/graphml/_index.md index e8d6ea4137..ba8e2d6c46 100644 --- a/site/content/3.13/data-science/arangographml/_index.md +++ b/site/content/3.13/data-science/graphml/_index.md @@ -1,18 +1,23 @@ --- -title: ArangoGraphML -menuTitle: ArangoGraphML +title: ArangoDB GraphML +menuTitle: GraphML weight: 125 description: >- - Enterprise-ready, graph-powered machine learning as a cloud service or self-managed + Boost your machine learning models with graph data using ArangoDB's advanced GraphML capabilities aliases: - - graphml + - arangographml --- Traditional Machine Learning (ML) overlooks the connections and relationships between data points, which is where graph machine learning excels. However, accessibility to GraphML has been limited to sizable enterprises equipped with -specialized teams of data scientists. ArangoGraphML simplifies the utilization of GraphML, +specialized teams of data scientists. ArangoDB simplifies the utilization of Graph Machine Learning, enabling a broader range of personas to extract profound insights from their data. +With ArangoDB, you can solve high-computational graph problems using Graph Machine +Learning. Apply it on a selected graph to predict connections, get better product +recommendations, classify nodes, and perform node embeddings. You can configure and run +the whole machine learning flow entirely through the web interface or programmatically. + ## How GraphML works Graph machine learning leverages the inherent structure of graph data, where @@ -21,18 +26,29 @@ traditional ML, which primarily operates on tabular data, GraphML applies specialized algorithms like Graph Neural Networks (GNNs), node embeddings, and link prediction to uncover complex patterns and insights. +The underlying framework for ArangoDB's GraphML is **[GraphSAGE](https://snap.stanford.edu/graphsage/)**. +GraphSAGE (Graph Sample and AggreGatE) is a powerful Graph Neural Network (GNN) +**framework** designed for inductive representation learning on large graphs. +It is used to generate low-dimensional vector representations for nodes and is +especially useful for graphs that have rich node attribute information. +The overall process involves the following steps: + 1. **Graph Construction**: - Raw data is transformed into a graph structure, defining nodes and edges based + - Raw data is transformed into a graph structure, defining nodes and edges based on real-world relationships. -2. **Featurization**: - Nodes and edges are enriched with features that help in training predictive models. -3. **Model Training**: - Machine learning techniques are applied on GNNs to identify patterns and make predictions. +2. **Featurization**: Your raw graph data is transformed into numerical representations that the model can understand. + - The system iterates over your selected vertices and converts their attributes: booleans become `0` or `1`, numbers are normalized, and text attributes are converted into numerical vectors using sentence transformers. + - All of these numerical features are then combined (concatenated). + - Finally, **Incremental PCA** (Incremental Principal Component Analysis a dimensionality reduction technique) is used to reduce the size of the combined features, which helps remove noise and keep only the most important information. +3. **Training**: The model learns from the graph's structure by sampling and aggregating information from each node's local neighborhood. + - For each node, GraphSAGE looks at connections up to **2 hops away**. + - Specifically, it uniformly samples up to **25 direct neighbors** (depth 1) and for each of those, it samples up to **10 of their neighbors** (depth 2). + - By aggregating feature information from this sampled neighborhood, the model creates a rich "embedding" for each node that captures both its own features and its role in the graph. 4. **Inference & Insights**: - The trained model is used to classify nodes, detect anomalies, recommend items, + - The trained model is used to classify nodes, detect anomalies, recommend items, or predict future connections. -ArangoGraphML streamlines these steps, providing an intuitive and scalable +ArangoDB streamlines these steps, providing an intuitive and scalable framework to integrate GraphML into various applications, from fraud detection to recommendation systems. @@ -40,16 +56,18 @@ to recommendation systems. ![GraphML Workflow](../../../images/GraphML-How-it-works.webp) -It is no longer necessary to understand the complexities involved with graph -machine learning, thanks to the accessibility of the ArangoML package. -Solutions with ArangoGraphML only require input from a user about -their data, and the ArangoGraphML managed service handles the rest. +You no longer need to understand the complexities of graph machine learning to +benefit from it. Solutions with ArangoDB's GraphML only require input from a user about +their data, and the GraphML managed service handles the rest. The platform comes preloaded with all the tools needed to prepare your graph for machine learning, high-accuracy training, and persisting predictions back to the database for application use. -## Supported Tasks +## What you can do with GraphML + +GraphML directly supports two primary machine learning tasks: +**Node Classification** and **Node Embeddings**. ### Node Classification @@ -58,7 +76,7 @@ predict the label of a node based on both its own features and its relationships within the graph. It requires a set of labeled nodes to train a model, which then classifies unlabeled nodes based on learned patterns. -**How it works in ArangoGraphML** +**How it works in ArangoDB** - A portion of the nodes in a graph is labeled for training. - The model learns patterns from both **node features** and @@ -97,7 +115,7 @@ into numerical vector representations, preserving their **structural relationshi within the graph. Unlike simple feature aggregation, node embeddings **capture the influence of neighboring nodes and graph topology**, making them powerful for downstream tasks like clustering, anomaly detection, -and link prediction. These combinations can provide valuable insights. +and link prediction. This combination provides valuable insights. Consider using [ArangoDB's Vector Search](https://arangodb.com/2024/11/vector-search-in-arangodb-practical-insights-and-hands-on-examples/) capabilities to find similar nodes based on their embeddings. @@ -116,7 +134,7 @@ Essentially, they aggregate both the node's attributes and the connectivity patt within the graph. This fusion helps capture not only the individual properties of a node but also its position and role within the network. -**How it works in ArangoGraphML** +**How it works in ArangoDB** - The model learns an embedding (a vector representation) for each node based on its **position within the graph and its connections**. @@ -161,21 +179,21 @@ a node but also its position and role within the network. | **Key Advantage** | Learns labels based on node connections and attributes | Learns structural patterns and node relationships | | **Use Cases** | Fraud detection, customer segmentation, disease classification | Recommendations, anomaly detection, link prediction | -ArangoGraphML provides the infrastructure to efficiently train and apply these +GraphML provides the infrastructure to efficiently train and apply these models, helping users extract meaningful insights from complex graph data. ## Metrics and Compliance -ArangoGraphML supports tracking your ML pipeline by storing all relevant metadata +GraphML supports tracking your ML pipeline by storing all relevant metadata and metrics in a Graph called ArangoPipe. This is only available to you and is never viewable by ArangoDB. This metadata graph links all experiments to the source data, feature generation activities, training runs, and prediction jobs, allowing you to track the entire ML pipeline without having to leave ArangoDB. -### Security +## Security -Each deployment that uses ArangoGraphML has an `arangopipe` database created, +Each deployment that uses GraphML has an `arangopipe` database created, which houses all ML Metadata information. Since this data lives within the deployment, it benefits from the ArangoGraph security features and SOC 2 compliance. -All ArangoGraphML services live alongside the ArangoGraph deployment and are only +All GraphML services live alongside the ArangoGraph deployment and are only accessible within that organization. diff --git a/site/content/3.13/data-science/arangographml/getting-started.md b/site/content/3.13/data-science/graphml/notebooks-api.md similarity index 79% rename from site/content/3.13/data-science/arangographml/getting-started.md rename to site/content/3.13/data-science/graphml/notebooks-api.md index 6bd614167e..c9ade4cacf 100644 --- a/site/content/3.13/data-science/arangographml/getting-started.md +++ b/site/content/3.13/data-science/graphml/notebooks-api.md @@ -1,29 +1,39 @@ --- -title: Getting Started with ArangoGraphML -menuTitle: Getting Started -weight: 10 +title: How to use GraphML in a scriptable manner +menuTitle: Notebooks & API +weight: 15 description: >- - How to control all resources inside ArangoGraphML in a scriptable manner + Control all resources inside GraphML via Jupyter Notebooks or Python API aliases: - getting-started-with-arangographml + - ../arangographml/getting-started + - ../arangographml-getting-started-with-arangographml --- -ArangoGraphML provides an easy-to-use & scalable interface to run Graph Machine Learning on ArangoDB Data. Since all of the orchestration and ML logic is managed by ArangoGraph, all that is typically required are JSON specifications outlining individual processes to solve an ML Task. If you are using the self-managed solution, additional configurations may be required. -The `arangoml` is a Python Package allowing you to manage all of the necessary ArangoGraphML components, including: -- **Project Management**: Projects are a metadata-tracking entity that sit at the top level of ArangoGraphML. All activities must link to a project. -- **Featurization**: The step of converting human-understandable data to machine-understandable data (i.e features), such that it can be used to train Graph Neural Networks (GNNs). -- **Training**: Train a set of models based on the name of the generated/existing features, and a definition of the ML Task we want to solve (e.g Node Classification, Embedding Generation). +{{< tag "ArangoDB Platform" >}} + +The ArangoDB Platform provides an easy-to-use & scalable interface to run +Graph Machine Learning on ArangoDB data. Since all the orchestration and Machine Learning logic is +managed by ArangoDB, all that is typically required are JSON specifications outlining +individual processes to solve a Machine Learning task. + +The `arangoml` Python package allows you to manage all the necessary +GraphML components, including: +- **Project Management**: Projects are a metadata-tracking entity that sit at + the top level of ArangoDB GraphML. All activities must link to a project. +- **Featurization**: The step of converting human-understandable data to + machine-understandable data (e.g. features), such that it can be used to + train Graph Neural Networks (GNNs). +- **Training**: Train a set of models based on the name of the generated/existing + features, and a definition of the ML task you want to solve (e.g. Node Classification, Embedding Generation). - **Model Selection**: Select the best model based on the metrics generated during training. -- **Predictions**: Generate predictions based on the selected model, and persit the results to the source graph (either in the source document, or in a new collection). +- **Predictions**: Generate predictions based on the selected model, and persist + the results to the source graph (either in the source document, or in a new collection). -{{< tip >}} -To enable the ArangoGraphML services in the ArangoGraph platform, -[get in touch](https://www.arangodb.com/contact/) -with the ArangoDB team. Regular notebooks in ArangoGraph don't include the -`arangoml` package. -{{< /tip >}} - -ArangoGraphML's suite of services and packages is driven by **"specifications"**. These specifications are standard Python dictionaries that describe the task being performed, & the data being used. The ArangoGraphML services work closely together, with the previous task being used as the input for the next. +GraphML's suite of services and packages is driven by **"specifications"**. +These specifications are standard Python dictionaries that describe the task +being performed, and the data being used. The GraphML services work closely +together, with the previous task being used as the input for the next. Let's take a look at using the `arangoml` package to: @@ -35,13 +45,9 @@ Let's take a look at using the `arangoml` package to: ## Initialize ArangoML -{{< tabs "arangoml" >}} - -{{< tab "ArangoGraphML" >}} - **API Documentation: [arangoml.ArangoMLMagics.enable_arangoml](https://arangoml.github.io/arangoml/magics.html#arangoml.magic.ArangoMLMagics.enable_arangoml)** -The `arangoml` package comes pre-loaded with every ArangoGraphML notebook environment. +The `arangoml` package comes pre-loaded with every GraphML notebook environment. To start using it, simply import it, and enable it via a Jupyter Magic Command. ```py @@ -49,116 +55,12 @@ arangoml = %enable_arangoml ``` {{< tip >}} -ArangoGraphML comes with other ArangoDB Magic Commands! See the full list [here](https://arangoml.github.io/arangoml/magics.html). +GraphML comes with other ArangoDB Magic Commands! See the full list [here](https://arangoml.github.io/arangoml/magics.html). {{< /tip >}} -{{< /tab >}} - -{{< tab "Self-managed" >}} - -**API Documentation: [arangoml.ArangoML](https://arangoml.github.io/arangoml/client.html#arangoml.main.ArangoML)** - -The `ArangoML` class is the main entry point for the `arangoml` package. -It has the following parameters: -- `client`: An instance of arango.client.ArangoClient. Defaults to `None`. If not provided, the **hosts** argument must be provided. -- `hosts`: The ArangoDB host(s) to connect to. This can be a single host, or a - list of hosts. -- `username`: The ArangoDB username to use for authentication. -- `password`: The ArangoDB password to use for authentication. -- `user_token`: The ArangoDB user token to use for authentication. - This is an alternative to username/password authentication. -- `ca_cert_file`: The path to the CA certificate file to use for TLS - verification. Defaults to `None`. -- `api_endpoint`: The URL to the ArangoGraphML API Service. -- `settings_files`: A list of secrets files to be loaded as settings. Parameters provided as arguments will override those in the settings files (e.g `settings.toml`). -- `version`: The ArangoML API date version. Defaults to the latest version. - -It is possible to instantiate an ArangoML object in multiple ways: - -1. Via parameters -```py -from arangoml import ArangoML - -arangoml = ArangoML( - hosts="http://localhost:8529" - username="root", - password="password", - # ca_cert_file="/path/to/ca.pem", - # user_token="..." - api_endpoint="http://localhost:8501", -) -``` - -2. Via parameters and a custom `ArangoClient` instance -```py -from arangoml import ArangoML -from arango import ArangoClient - -client = ArangoClient( - hosts="http://localhost:8529", - verify_override="/path/to/ca.pem", - hosts_resolver=..., - ... -) - -arangoml = ArangoML( - client=client, - username="root", - password="password", - # user_token="..." - api_endpoint="http://localhost:8501", -) -``` - -3. Via environment variables -```py -import os -from arangoml import ArangoML - -os.environ["ARANGODB_HOSTS"] = "http://localhost:8529" -os.environ["ARANGODB_CA_CERT_FILE"]="/path/to/ca.pem" -os.environ["ARANGODB_USER"] = "root" -os.environ["ARANGODB_PW"] = "password" -# os.environ["ARANGODB_USER_TOKEN"] = "..." -os.environ["ML_API_SERVICES_ENDPOINT"] = "http://localhost:8501" - -arangoml = ArangoML() -``` - -4. Via configuration files -```py -import os -from arangoml import ArangoML - -arangoml = ArangoML(settings_files=["settings_1.toml", "settings_2.toml"]) -``` - -5. Via a Jupyter Magic Command - -**API Documentation: [arangoml.ArangoMLMagics.enable_arangoml](https://arangoml.github.io/arangoml/magics.html#arangoml.magic.ArangoMLMagics.enable_arangoml)** - -``` -%load_ext arangoml -%enable_arangoml -``` -{{< info >}} -This assumes you are working out of a Jupyter Notebook environment, and -have set the environment variables in the notebook environment with user -authentication that has **_system** access. -{{< /info >}} - -{{< tip >}} -Running `%load_ext arangoml` also provides access to other [ArangoGraphML -Jupyter Magic Commands](https://arangoml.github.io/arangoml/magics.html). -{{< /tip >}} - -{{< /tab >}} - -{{< /tabs >}} - ## Load the database -This example is using ArangoML to predict the **class** of `Events` in a +This example is using GraphML to predict the **class** of `Events` in a Knowledge Graph constructed from the [GDELT Project](https://www.gdeltproject.org/). > GDELT monitors the world's news media from nearly every corner of every @@ -180,15 +82,9 @@ news sources, and locations are interconnected into a large graph. ![Example Event](../../../images/ArangoML_open_intelligence_visualization.png) -Let's get started! - -{{< tabs "arangoml" >}} - -{{< tab "ArangoGraphML" >}} - The [`arango-datasets`](../../components/tools/arango-datasets.md) Python package -allows you to load pre-defined datasets into ArangoDB. It comes pre-installed in the -ArangoGraphML notebook environment. +allows you to load pre-defined datasets into ArangoDB Platform. It comes pre-installed in the +GraphML notebook environment. ```py DATASET_NAME = "OPEN_INTELLIGENCE_ANGOLA" @@ -199,42 +95,11 @@ DATASET_NAME = "OPEN_INTELLIGENCE_ANGOLA" %load_dataset {DATASET_NAME} ``` -{{< /tab >}} - -{{< tab "Self-managed" >}} - -The [`arango-datasets`](../../components/tools/arango-datasets.md) Python package -allows you to load pre-defined datasets into ArangoDB. It can be installed with the -following command: - -``` -pip install arango-datasets -``` - -```py -from arango_datasets.datasets import Datasets - -DATASET_NAME = "OPEN_INTELLIGENCE_ANGOLA" - -db = arangoml.client.db( - name=DATASET_NAME, - username=arangoml.settings.get("ARANGODB_USER"), - password=arangoml.settings.get("ARANGODB_PW"), - user_token=arangoml.settings.get("ARANGODB_USER_TOKEN"), - verify=True -) - -Datasets(dataset_db).load(DATASET_NAME) -``` -{{< /tab >}} - -{{< /tabs >}} - ## Projects **API Documentation: [ArangoML.projects](https://arangoml.github.io/arangoml/api.html#projects)** -Projects are an important reference used throughout the entire ArangoGraphML +Projects are an important reference used throughout the entire GraphML lifecycle. All activities link back to a project. The creation of the project is very simple. @@ -410,7 +275,6 @@ Once a Featurization Job has been submitted, you can wait for it to complete usi featurization_job_result = arangoml.wait_for_featurization(featurization_job.job_id) ``` - **Example Output:** ```py { @@ -511,12 +375,11 @@ You can also cancel a Featurization Job using the `arangoml.jobs.cancel_job` met arangoml.jobs.cancel_job(prediction_job.job_id) ``` - ## Training **API Documentation: [ArangoML.jobs.train](https://arangoml.github.io/arangoml/api.html#agml_api.jobs.v1.api.jobs_api.JobsApi.train)** -Training Graph Machine Learning Models with ArangoGraphML requires two steps: +Training Graph Machine Learning Models with GraphML requires two steps: 1. Describe which data points should be included in the Training Job. 2. Pass the Training Specification to the Training Service. @@ -553,12 +416,13 @@ A Training Specification allows for concisely defining your training task in a single object and then passing that object to the training service using the Python API client, as shown below. -The ArangoGraphML Training Service is responsible for training a series of +The GraphML Training Service is responsible for training a series of Graph Machine Learning Models using the data provided in the Training Specification. It assumes that the data has been featurized and is ready to be used for training. -Given that we have run a Featurization Job, we can create the Training Specification using the `featurization_job_result` object returned from the Featurization Job: +Given that we have run a Featurization Job, we can create the Training Specification +using the `featurization_job_result` object returned from the Featurization Job: ```py # 1. Define the Training Specification @@ -736,10 +600,11 @@ arangoml.jobs.cancel_job(training_job.job_id) ## Model Selection Model Statistics can be observed upon completion of a Training Job. -To select a Model, the ArangoGraphML Projects Service can be used to gather +To select a Model, the GraphML Projects Service can be used to gather all relevant models and choose the preferred model for a Prediction Job. -First, let's list all the trained models using [ArangoML.list_models](https://arangoml.github.io/arangoml/client.html#arangoml.main.ArangoML.list_models): +First, let's list all the trained models using +[ArangoML.list_models](https://arangoml.github.io/arangoml/client.html#arangoml.main.ArangoML.list_models): ```py # 1. List all trained Models @@ -752,7 +617,11 @@ models = arangoml.list_models( print(len(models)) ``` -The cell below selects the model with the highest **test accuracy** using [ArangoML.get_best_model](https://arangoml.github.io/arangoml/client.html#arangoml.main.ArangoML.get_best_model), but there may be other factors that motivate you to choose another model. See the `model_statistics` in the output field below for more information on the full list of available metrics. +The cell below selects the model with the highest **test accuracy** using +[ArangoML.get_best_model](https://arangoml.github.io/arangoml/client.html#arangoml.main.ArangoML.get_best_model), +but there may be other factors that motivate you to choose another model. See +the `model_statistics` in the output field below for more information on the +full list of available metrics. ```py @@ -848,7 +717,10 @@ collection, or within the source documents. - `modelID`: The model ID to use for generating predictions. - `featurizeNewDocuments`: Boolean for enabling or disabling the featurization of new documents. Useful if you don't want to re-train the model upon new data. Default is `false`. - `featurizeOutdatedDocuments`: Boolean for enabling or disabling the featurization of outdated documents. Outdated documents are those whose features have changed since the last featurization. Default is `false`. -- `schedule`: A cron expression to schedule the prediction job (e.g `0 0 * * *` for daily predictions). Default is `None`. +- `schedule`: A cron expression to schedule the prediction job. The cron syntax is a set of + five fields in a line, indicating when the job should be executed. The format must follow + the following order: `minute` `hour` `day-of-month` `month` `day-of-week` + (e.g. `0 0 * * *` for daily predictions at 00:00). Default is `None`. - `embeddingsField`: The name of the field to store the generated embeddings. This is only used for Graph Embedding tasks. Default is `None`. ```py diff --git a/site/content/3.13/data-science/graphml/quickstart.md b/site/content/3.13/data-science/graphml/quickstart.md new file mode 100644 index 0000000000..97b9fabeb3 --- /dev/null +++ b/site/content/3.13/data-science/graphml/quickstart.md @@ -0,0 +1,57 @@ +--- +title: How to get started with GraphML +menuTitle: Quickstart +weight: 5 +description: >- + You can use GraphML straight within the ArangoDB Platform, via the web interface + or via Notebooks +aliases: + - ../arangographml/deploy +--- + +## Web interface versus Jupyter Notebooks + +The ArangoDB Platform provides enterprise-ready Graph Machine Learning in two options, +tailored to suit diverse requirements and preferences: +- Using the web interface +- In a scriptable manner, using the integrated Jupyter Notebooks and the HTTP API for GraphML + +## Setup + +{{< tabs "graphml-setup" >}} + +{{< tab "Web Interface" >}} +The web interface of the ArangoDB Platform allows you to create, configure, and +run a full machine learning workflow for GraphML. To get started, see the +[Web interface for GraphML](ui.md) page. +{{< /tab >}} + +{{< tab "Notebooks" >}} +The ArangoDB Notebooks service runs on the +[ArangoGraph Insights Platform](https://dashboard.arangodb.cloud/home?utm_source=docs&utm_medium=cluster_pages&utm_campaign=docs_traffic). +It offers a pre-configured environment where everything, +including necessary components and configurations, comes preloaded. You don't +need to set up or configure the infrastructure, and can immediately start using the +GraphML functionalities in a scriptable manner. To get started, see the +[GraphML Notebooks & API](notebooks-api.md) reference documentation. + +{{< tip >}} +To get access to GraphML services and packages in ArangoGraph Insights Platform, +[get in touch](https://www.arangodb.com/contact/) +with the ArangoDB team. +{{< /tip >}} + +- **Accessible at all levels** + - Low code UI + - Notebooks + - APIs +- **Full usability** + - MLOps lifecycle + - Metrics + - Metadata capture + - Model management + +![ArangoGraphML Pipeline](../../../images/ArangoGraphML_Pipeline.png) +{{< /tab >}} + +{{< /tabs >}} \ No newline at end of file diff --git a/site/content/3.13/data-science/graphml/ui.md b/site/content/3.13/data-science/graphml/ui.md new file mode 100644 index 0000000000..45ef75dd6a --- /dev/null +++ b/site/content/3.13/data-science/graphml/ui.md @@ -0,0 +1,244 @@ +--- +title: How to use GraphML in the ArangoDB Platform web interface +menuTitle: Web Interface +weight: 10 +description: >- + Learn how to create, configure, and run a full machine learning workflow for + GraphML in four steps using the features in the ArangoDB web interface +--- +{{< tag "ArangoDB Platform" >}} + +## The GraphML workflow in the web interface + +The entire process is organized into sequential steps within a **Project**, +giving you a clear path from data to prediction: + +1. **Featurization**: Select your data and convert it into numerical representations. +2. **Training**: Train a GraphSAGE model on the features and graph structure. +3. **Model Selection**: Evaluate the trained models and choose the best one. +4. **Prediction**: Use the selected model to generate predictions on your data. + You can also automate the prediction process to run at regular intervals. + +## Create a GraphML project + +To create a new GraphML project using the ArangoDB Platform web interface, follow these steps: + +1. From the left-hand sidebar, select the database where you want to create the project. +2. In the left-hand sidebar, click **GenAI** to open the GraphML project management interface, then click **Run GraphML**. + ![Navigate to Data Science](../../../images/datascience-intro.jpg) +3. In the **GraphML projects** view, click **Add new project**. +4. The **Create ML project** modal opens. Enter a **Name** for your machine learning project. +5. Click the **Create project** button to finalize the creation. +6. After creation, the new project appears in the list under **GraphML projects**. + Click the project name to begin with a Featurization job. + +## Featurization phase + +After clicking on a project name, you are taken to a screen where you can +configure and start a new Featurization job. Follow these steps: +1. **Select a Graph**: In the **Features** section, choose your target graph from the **Select a graph** dropdown menu. +2. **Select Vertex Collection(s)**: Pick the vertex collection(s) that you want to include for feature extraction. +3. **Select Attributes**: Choose the attributes from your vertex collection to + convert into machine-understandable features. Attributes cannot be used if their values are lists or arrays. + +{{< info >}} +A metagraph is basically just the configured subset of a graph (the vertex and +edge collections and the specified attributes). This is what you see represented +in the metagraph object in the JSON specification on the right. +{{< /info >}} + +### Configuration options + +The featurization process has several configurable options, grouped into +**Configuration** and **Advanced** settings. These are also shown in a JSON +format on the right side of the screen for transparency. + +In the **Configuration** tab, you can control the overall featurization job and +how features are stored. +- **Batch size**: The number of documents to process in a single batch. +- **Run analysis checks**: Whether to run analysis checks to perform a high-level + analysis of the data quality before proceeding. The default value is `true`. +- **Skip labels**: Skip the featurization process for attributes marked as labels. + The default value is `false`. +- **Overwrite FS graph**: Whether to overwrite the Feature Store graph if features + were previously generated. The default value is `false`, therefore features are + written to an existing Feature Store graph. +- **Write to source graph**: Whether to store the generated features on the Source + Graph. The default value is `true`. +- **Use feature store**: Enable the use of the Feature Store database, which + allows you to store features separately from your Source Database. The default + value is `false`, therefore features are written to the source graph. + +### Handling imperfect data + +Real-world datasets often contain missing values or mismatched data types. Use +the strategies below to control how each feature type (**Text**, **Numeric**, +**Category**, **Label**) handles these issues during featurization. + +| **Strategy type** | **Option** | **Description** | **When to use** | +|-------------------|-----------------------|-----------------------------------------------------------------------------------------------------|---------------------------------------------------------------| +| Missing | **Raise** | Stops the job and reports an error when a value is missing. | When missing data indicates a critical issue. | +| | **Replace** | Substitutes missing values with a default you provide (e.g., `0` for numbers, `"unknown"` for text). | When missing values are expected. | +| Mismatch | **Raise** | The strictest option. Stops the job on any data type mismatch. | When any data type mismatch indicates a critical error. | +| | **Replace** | Replaces mismatched values with a default you provide, without trying to convert it first. | When mismatched values are unreliable, and you prefer to substitute it directly. | +| | **Coerce and Raise** | Attempts to convert (coerce) the value to the correct type (e.g. string "123" to number `123`). If the conversion is successful, it uses the new value. If it fails, the job stops. | A balanced approach, often the best default strategy. | +| | **Coerce and Replace**| The most forgiving option. The system first tries to convert the value. If it fails, it replaces the value with the specified default and continues the job. | For very dirty datasets where completing the job is the highest priority. | + +Once you’ve set your strategies, click **Begin featurization** to start the node +embedding-compatible featurization job. When the job status updates to +**Ready for training**, proceed to the **Training** step. + +![Navigate to Featurization](../../../images/graph-ml-ui-featurization.png) + +## Training phase + +The training is the second step in the ML workflow after featurization. +In the training phase, you configure and launch a machine learning training +job on your graph data. + +From the **Select a type of training job** dropdown menu, choose the type of +model you want to train (**Node Classification** or **Node Embeddings**). + +#### Node classification + +Node Classification is used to categorize the nodes in your graph based on their +features and structural connections within the graph. + +**Use cases include:** +- Entity categorization (e.g. movies into genres, users into segments) +- Fraud detection in transaction networks + +**Configuration parameters:** +- **Type of Training Job**: Node classification +- **Target Vertex Collection**: Choose the collection to classify (e.g. `movie`) +- **Batch Size**: The number of documents processed in a single training iteration. (e.g. `256`) +- **Data Load Batch Size**: The number of documents loaded from ArangoDB into memory in a single batch during the data loading phase (e.g. `50000`). +- **Data Load Parallelism**: The number of parallel processes used when loading data from ArangoDB into memory for training (e.g. `10`). + +After setting these values, click the **Begin training** button to start the job. + +![Node Classification](../../../images/ml-nodeclassification.png) + +#### Node embeddings + +Node Embeddings are used to generate vector embeddings (dense numerical representations) +of graph nodes that capture structural and feature-based information. + +**Use cases include:** +- Similarity search (e.g. finding similar products, users, or documents) +- Link prediction (e.g. suggesting new connections) + +**Configuration parameters:** +- **Type of Training Job:** Node embeddings +- **Target Vertex Collection:** Select the collection to generate embeddings for (e.g. `movie` or `person`) +- No label is required for training in this mode + +**Understanding Vertex Collections:** + +- **Vertex Collection**: These are the source nodes used during training. + They represent the full set of nodes on which features were computed (e.g. `person`, `movie`). +- **Vertex Collection**: These are the target nodes that contain labeled data. + The labels in this collection are used to supervise the training process and + are the basis for evaluating prediction quality. + +The target collection is where the model's predictions are stored when running a prediction job. + +Once the configuration is complete, click **Begin training** to start the embedding job. + +![Node Embeddings](../../../images/ml-node-embedding.png) + +## Model selection phase + +Once the training is finished, the job status updates to **READY FOR MODEL SELECTION**. +This means the model has been trained using the provided vertex and edge data +and is now ready for evaluation. + +A list of trained models is displayed, along with performance metrics +(**Accuracy**, **Precision**, **Recall**, **F1 score**, **Loss**). Review the results of different +model runs and configurations. + +![GraphML Model Selection](../../../images/graph-ml-model.png) + +Select the best performing model suitable for your prediction task. You can also +open the **Confusion Matrix** to compare predicted values versus actual values. + +![GraphML Confusion Matrix](../../../images/graphml-ui-confusion-matrix.png) + +## Prediction phase + +After selecting a model, you can create a Prediction Job. The Prediction Job +generates predictions and persists them to the source graph, either in a new +collection or within the source documents. + +The Prediction interface allows inference to be run using the selected model. +It enables configuration of how predictions are executed, which collections are +involved, and whether new or outdated documents should be automatically featurized +before prediction. + +You have two important options: + +- **Featurize new documents:** Enable this option to generate features for + documents that have been added since the model was trained. This is useful + for getting predictions on new data without having to retrain the model. +- **Featurize outdated documents:** Enable this option to re-generate features + for documents that have been modified. Outdated documents are those whose + attributes (used during featurization) have changed since the last feature + computation. This ensures your predictions reflect the latest changes to your data. + +In addition to these settings, you can also define the target data, where to store +results, and whether to run the job on a recurring schedule. + +These options provide flexibility in handling dynamic graph data and keeping +predictions relevant without repeating the entire ML workflow. + +- **Data load batch size**: Specifies the number of documents to load in a + single batch (e.g. `500000`). +- **Data load parallelism**: The number of parallel threads used to process + the prediction workload (e.g. `10`). +- **Prediction field**: The field in the documents where the predicted values are stored. + +![GraphML prediction phase](../../../images/graph-prediction.png) + +### Configuration options + +The Prediction screen displays the following configuration options: +- **Select Model**: Displays the model selected during the Model Selection phase. This model will be used to perform inference. +- **Target Vertex Collection**: This is the vertex collection on which predictions are applied. +- **Prediction Type**: Depending on the training job (for example, classification or embedding), the prediction outputs class labels or updated embeddings. + +### Enable scheduling + +You can configure automatic predictions using the **Enable scheduling** checkbox. +When scheduling is enabled, predictions run automatically based on a set CRON +expression. This helps keep prediction results up-to-date as new data is added to the system. + +You can define a cron expression that sets when the prediction job should run. +The cron syntax is a set of five fields in a line, indicating when the job should +be executed. The format must follow the following order: `minute` `hour` `day-of-month` `month` `day-of-week` +(e.g. `0 0 * * *` for daily predictions at 00:00, or `0 0 1 1 *` to execute the prediction +on January 1st at 00:00). + +When a field is set to an asterisk `*`, it means that any value is allowed for that field, +whenever the other field conditions are met. + +Below the CRON field, a user-friendly scheduling interface helps translate it: +- **Period**: Options include **Hourly**, **Daily**, **Weekly**, **Monthly**, or **Yearly**. +- **Month**: Indicates the month. For example, `1` for January. +- **Day of Month**: Indicates the day of the month. For example, `1` for + the first day of the month. +- **Day of Week** (optional): Indicates the day of the week. For example, + Monday is `1` and Tuesday is `2`. +- **Hours and Minutes**: Set the exact time for execution. For example, + if the hour is set to `8` and the minute to `0`, then the job runs at 8:00 AM. + +### Execute prediction + +After reviewing the configuration, click the **Run Prediction** button. +Once prediction is complete, you can analyze the results directly in +the Web Interface or export them for downstream use. + +## Limitations + +- **Edge Attributes**: The current version of GraphML does not support the use of edge attributes as features. +- **Dangling Edges**: Edges that point to non-existent vertices ("dangling edges") are not caught during the featurization analysis. They may cause errors later, during the Training phase. +- **Memory Usage**: Both featurization and training can be memory-intensive. Out-of-memory errors can occur on large graphs with insufficient system resources. \ No newline at end of file diff --git a/site/content/3.13/index-and-search/indexing/working-with-indexes/vector-indexes.md b/site/content/3.13/index-and-search/indexing/working-with-indexes/vector-indexes.md index 90b82edbf9..236093878b 100644 --- a/site/content/3.13/index-and-search/indexing/working-with-indexes/vector-indexes.md +++ b/site/content/3.13/index-and-search/indexing/working-with-indexes/vector-indexes.md @@ -33,7 +33,7 @@ startup option needs to be enabled on the deployment you want to restore to. {{< /warning >}} 1. Enable the experimental vector index feature. -2. Calculate vector embeddings using [ArangoDB's GraphML](../../../data-science/arangographml/_index.md) +2. Calculate vector embeddings using [ArangoDB's GraphML](../../../data-science/graphml/_index.md) capabilities (available in ArangoGraph) or using external tools. Store each vector as an attribute in the respective document. 3. Create a vector index over this attribute. You need to choose which diff --git a/site/content/images/graphml-ui-confusion-matrix.png b/site/content/images/graphml-ui-confusion-matrix.png new file mode 100644 index 0000000000000000000000000000000000000000..1ddfa5f9b82451dced12e77cd57d9a5bcbb5e2f2 GIT binary patch literal 88842 zcmeFZXIN9))&>fK^p4WIQWT^rp@h&urHcYe7XcLrC6v$t2vVdfA|O?fA|OqA??~@G z^b+YMK!8y0V()Xly^s3bpZC}G36DwUDw$)>Ip!Ge`;HZ&t*J^*!bF0DgF}AruF?Y> z96S^b&Lwvu0^pmjC3$Ha99*>*ii+Cz6cstNo$Re%*jnP?FvM6!6{6X6Fb}%*&>xN(4Kvd=3pA zQ&y|amycd;*g;?%I0Xj` zmjd3}Svs3@!0l|IFe$hU*I!3S0q-xq7UJUg>kwxf87>12Z4O0yCrb`-L195*E(i$+ z2M5^cnU&N7C6)i24t$c~dhYD(ASEQ^=H@2oCMIa_WGy5jDJdx=EGi@_dJ8z>7R(*$ zY!1H#g4k&y3wtQX#dXb}+PgT*aB*GS=>Pux{W~q;FaEg`6!xEP z0UZ>&Xb}<-6c+lwYXhf(FTR!1egU_%eXR7t4loaJ4~V#!82GQ_e{1>Yj{iE-;GZ*r z{`~vQf3^I2<|CM;lcK#Ha7$;%KO^&>lmFfLpA*4C7d`)%CH^+^U*7_DhLC`T{%_16 zB)ZZEa2y;toO??058=4$NrdHBlt%(X1s=;$zvpAn5#h)RHtS)8H5RYEd1?Tourbir z*KbKwZcY^Dy`pZWBFw?4tfM0*+97K{7hm_n!^&e>vl4>{LWzn^4PTvJU0KQR`MS!a zM!Wosi{sMoFYasF-=A^km{C*V{{GUEn>XTpMHKA&>+5}-Z?k)dv|LN*Z%x6z%@sX= zxE3{^2yO_P#?AK){U2IGU`cqt|2Qt~rPk@*OaX7b`kpS(0UwLsi zq5XP~81(?+cQaBGQUB~u3#!^_WgaAP4v@S!_NDdx)YCuonB!6>t;Eq{^eI6z$SUPD zsM&|7i;#?=pb?@*>lGtGInd|%hZ_XntH*aYNVVqgx}iA_=5gkx{oTfX!hQvE*0N7! z^4gHQ(GnmUkE?$;h=nNNvPWrSB3B8A5yh-a@E`2480`y29ew#){j25W@UvO{W?5iN z?8*LYgu%`3EcfqU`@_Id{e1QS3F}=%J*}?Ts>LF!-O((Tn6=S-2IXwBUnklRe!qA5BM`4c z2e)Z=R0TWY(EN4aotHyDjD1e`^R!YR{OkOCfrFaT-Q=uNw;y*UxlKRb_DFQEzJ7&ULH`%%i)gx%<+lO z%Av@5KfF_OvYz#lQIo>&V^2eGA%*Hr=Jg(1Ud2p%ZnZ!(iIsnG0jWkr?{DX_YVC-$ zv3ySywjV1ne@D)?Uw67;zv)NFb{ERun($&UgZs66_s2U^D$I6^_g5N(R1y~6@ARv( ze~VgQh?F%kHxy*16Eyk>tyrQvx5m3tv6yD@>r_Y;rQwU0NRjoa#g5jU#XKvWa-FOI z&3FxZAFpI?Hc`qtx3T+F&y+c@JaAiz(XVWe=H(rvJJ#q6_1SJ9tl~|EH`*1o^Sl-u zF?MXZdN#KQPOa_Z+#A1b6@F)AyS*)fb?qnUdkW&XT_mb-;68uFa(BYy^5?qR!}-u; z=6um#hbJox|GqftBtm*~8{O&Gdg4@_0a$XEZ0A9W%gAHs-p~HcQ_PH;&&f`Z=jzu? ztx5?cA}4lI;O%TV*qd&34|D zJv?0aG6?CQ=r$k1TUly1d2F#+caD&iFk|%EX`$a-&B;GNXMJVgZ}r$;T{@U@GgVs- zz64)Mk3F$omRYa^F-tmqsangcIyu>Ay7RIeI#nsTY>(NTnk;$V-}vsxLqx(a3DzT- zXW99o-TU=R`y$`l_IiePcV-C5p7ilfRk>llc1KT_*yw{0Cx<^+vOryNYUjX+RRUdX zqjcU|A0PZywOM(_Kw$am)AmAZIJ(OX>8M&ZZk2EZGjD pOae=CjwYDq~Mz_hT!N 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 N`cRZOy_iY;-h+`w-WcZpZt*Gim?t`P*z12 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 Date: Fri, 4 Jul 2025 15:38:46 +0200 Subject: [PATCH 08/10] DOC-745 | Fix API call recording OpenAPI description and release notes (#737) * Fix API call recording OpenAPI description and release notes * Apply to 3.13 * Upper-case requestType in responses --- .../3.12/develop/http-api/monitoring/logs.md | 79 +++++++++++++++++-- .../version-3.12/whats-new-in-3-12.md | 15 ++-- .../3.13/develop/http-api/monitoring/logs.md | 79 +++++++++++++++++-- .../version-3.12/whats-new-in-3-12.md | 15 ++-- 4 files changed, 160 insertions(+), 28 deletions(-) diff --git a/site/content/3.12/develop/http-api/monitoring/logs.md b/site/content/3.12/develop/http-api/monitoring/logs.md index 64bef4afa0..f07f8a1109 100644 --- a/site/content/3.12/develop/http-api/monitoring/logs.md +++ b/site/content/3.12/develop/http-api/monitoring/logs.md @@ -848,14 +848,17 @@ paths: operationId: getRecentApiCalls description: | Get a list of the most recent requests with a timestamp and the endpoint. - This feature is for debugging purposes. + This feature is for debugging purposes. You can control how much memory is used to record API calls with the - `--server.memory-per-api-call-list` and `--server.number-of-api-call-lists` - startup options. + `--server.api-recording-memory-limit` startup option. - You can disable API call recording via the `--server.api-call-recording` - startup option. The endpoint returns an empty list of calls in this case. + You can disable this endpoint + with the `--log.recording-api-enabled` startup option. + + Whether API calls are recorded is independently controlled by the + `--server.api-call-recording` startup option. + The endpoint returns an empty list of calls if turned off. parameters: - name: database-name in: path @@ -870,7 +873,7 @@ paths: responses: '200': description: | - The + Returns the recorded API calls. content: application/json: schema: @@ -913,7 +916,7 @@ paths: description: | The HTTP request method. type: string - enum: [get, patch, put, delete, head] + enum: [GET, PATCH, PUT, DELETE, HEAD] path: description: | The HTTP request path excluding the database prefix (`/_db/`). @@ -953,6 +956,68 @@ paths: description: | A descriptive error message. type: string + '403': + description: | + The recording API has been disabled. + content: + application/json: + schema: + type: object + required: + - error + - code + - errorNum + - errorMessage + properties: + error: + description: | + A flag indicating that an error occurred. + type: boolean + example: true + code: + description: | + The HTTP response status code. + type: integer + example: 403 + errorNum: + description: | + ArangoDB error number for the error that occurred. + type: integer + errorMessage: + description: | + A descriptive error message. + type: string + '501': + description: | + The method has not been called on a Coordinator or single server. + content: + application/json: + schema: + type: object + required: + - error + - code + - errorNum + - errorMessage + properties: + error: + description: | + A flag indicating that an error occurred. + type: boolean + example: true + code: + description: | + The HTTP response status code. + type: integer + example: 501 + errorNum: + description: | + ArangoDB error number for the error that occurred. + type: integer + errorMessage: + description: | + A descriptive error message. + type: string tags: - Monitoring ``` diff --git a/site/content/3.12/release-notes/version-3.12/whats-new-in-3-12.md b/site/content/3.12/release-notes/version-3.12/whats-new-in-3-12.md index 77b8c26bb2..e89f9790c8 100644 --- a/site/content/3.12/release-notes/version-3.12/whats-new-in-3-12.md +++ b/site/content/3.12/release-notes/version-3.12/whats-new-in-3-12.md @@ -2167,19 +2167,20 @@ A new `/_admin/server/api-calls` endpoint has been added to let you retrieve a list of the most recent requests with a timestamp and the endpoint. This feature is for debugging purposes. -You can configure the memory limit for this feature with the following startup options: +You can configure the memory limit for this feature with the following startup option: -- `--server.number-of-api-call-lists`: - The size of the ring buffer for API call record lists (default: `256`). -- `--server.memory-per-api-call-list`: - The amount of memory used for a single API call record list (default: `100000` bytes) - -This means that approximately 25 MB of memory are reserved by default. +- `--server.api-recording-memory-limit`: + Size limit for the list of API call records (default: `25600000`). +This means that 25 MB of memory is reserved by default. API call recording is enabled by default but you can disable it via the new `--server.api-call-recording` startup option. +The `/_admin/server/api-calls` endpoint exposes the recorded API calls. +It is enabled by default. You can disable it altogether by setting the new +`--log.recording-api-enabled` startup option to `false`. + A metric has been added for the time spent on API call recording to track the impact of this feature: diff --git a/site/content/3.13/develop/http-api/monitoring/logs.md b/site/content/3.13/develop/http-api/monitoring/logs.md index 64bef4afa0..f07f8a1109 100644 --- a/site/content/3.13/develop/http-api/monitoring/logs.md +++ b/site/content/3.13/develop/http-api/monitoring/logs.md @@ -848,14 +848,17 @@ paths: operationId: getRecentApiCalls description: | Get a list of the most recent requests with a timestamp and the endpoint. - This feature is for debugging purposes. + This feature is for debugging purposes. You can control how much memory is used to record API calls with the - `--server.memory-per-api-call-list` and `--server.number-of-api-call-lists` - startup options. + `--server.api-recording-memory-limit` startup option. - You can disable API call recording via the `--server.api-call-recording` - startup option. The endpoint returns an empty list of calls in this case. + You can disable this endpoint + with the `--log.recording-api-enabled` startup option. + + Whether API calls are recorded is independently controlled by the + `--server.api-call-recording` startup option. + The endpoint returns an empty list of calls if turned off. parameters: - name: database-name in: path @@ -870,7 +873,7 @@ paths: responses: '200': description: | - The + Returns the recorded API calls. content: application/json: schema: @@ -913,7 +916,7 @@ paths: description: | The HTTP request method. type: string - enum: [get, patch, put, delete, head] + enum: [GET, PATCH, PUT, DELETE, HEAD] path: description: | The HTTP request path excluding the database prefix (`/_db/`). @@ -953,6 +956,68 @@ paths: description: | A descriptive error message. type: string + '403': + description: | + The recording API has been disabled. + content: + application/json: + schema: + type: object + required: + - error + - code + - errorNum + - errorMessage + properties: + error: + description: | + A flag indicating that an error occurred. + type: boolean + example: true + code: + description: | + The HTTP response status code. + type: integer + example: 403 + errorNum: + description: | + ArangoDB error number for the error that occurred. + type: integer + errorMessage: + description: | + A descriptive error message. + type: string + '501': + description: | + The method has not been called on a Coordinator or single server. + content: + application/json: + schema: + type: object + required: + - error + - code + - errorNum + - errorMessage + properties: + error: + description: | + A flag indicating that an error occurred. + type: boolean + example: true + code: + description: | + The HTTP response status code. + type: integer + example: 501 + errorNum: + description: | + ArangoDB error number for the error that occurred. + type: integer + errorMessage: + description: | + A descriptive error message. + type: string tags: - Monitoring ``` diff --git a/site/content/3.13/release-notes/version-3.12/whats-new-in-3-12.md b/site/content/3.13/release-notes/version-3.12/whats-new-in-3-12.md index 77b8c26bb2..e89f9790c8 100644 --- a/site/content/3.13/release-notes/version-3.12/whats-new-in-3-12.md +++ b/site/content/3.13/release-notes/version-3.12/whats-new-in-3-12.md @@ -2167,19 +2167,20 @@ A new `/_admin/server/api-calls` endpoint has been added to let you retrieve a list of the most recent requests with a timestamp and the endpoint. This feature is for debugging purposes. -You can configure the memory limit for this feature with the following startup options: +You can configure the memory limit for this feature with the following startup option: -- `--server.number-of-api-call-lists`: - The size of the ring buffer for API call record lists (default: `256`). -- `--server.memory-per-api-call-list`: - The amount of memory used for a single API call record list (default: `100000` bytes) - -This means that approximately 25 MB of memory are reserved by default. +- `--server.api-recording-memory-limit`: + Size limit for the list of API call records (default: `25600000`). +This means that 25 MB of memory is reserved by default. API call recording is enabled by default but you can disable it via the new `--server.api-call-recording` startup option. +The `/_admin/server/api-calls` endpoint exposes the recorded API calls. +It is enabled by default. You can disable it altogether by setting the new +`--log.recording-api-enabled` startup option to `false`. + A metric has been added for the time spent on API call recording to track the impact of this feature: From c8bb29f49b4fb222cf79d340ffd92bf4b41b2f40 Mon Sep 17 00:00:00 2001 From: Simran Date: Tue, 8 Jul 2025 11:04:26 +0200 Subject: [PATCH 09/10] DOC-769 | Graph Visualizer Canvas Actions and UI updates (#734) * Update for UI changes * Add Canvas Actions * Fix links * fix typo --------- Co-authored-by: Paula Mihu <97217318+nerpaula@users.noreply.github.com> --- site/content/3.13/graphs/graph-visualizer.md | 86 ++++++++++++++------ 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/site/content/3.13/graphs/graph-visualizer.md b/site/content/3.13/graphs/graph-visualizer.md index 17e63c4e49..1c2d6623e6 100644 --- a/site/content/3.13/graphs/graph-visualizer.md +++ b/site/content/3.13/graphs/graph-visualizer.md @@ -55,68 +55,104 @@ The main area of the viewport may initially be empty in the following cases: - Reopening the Graph Visualizer after a previous session --> -You can [Add nodes to the canvas](#add-nodes-to-the-canvas) as well as -[Display a subgraph using a query](#display-a-subgraph-using-a-query). +You can [Add nodes to the canvas manually](#add-nodes-to-the-canvas-manually) +as well as [Add nodes and edges using a query](#add-nodes-and-edges-using-a-query). +Afterwards, you can also [Add nodes and edges using a query based on a selection](#add-nodes-and-edges-using-a-query-based-on-a-selection). ### The viewport The Graph Visualizer interface is comprised of the following components: - **Canvas**: The main area of the viewport. -- **Explore**: - A widget in the top left corner that opens a dialog for selecting nodes and - edges to display. +- **Search & add nodes to canvas** and **Queries**: + A widget in the top left corner that opens dialogs for selecting nodes and + edges to display (manually or using queries). - [**Customization**](#visual-customization): A sidebar on the right-hand side to adjust the styling. - [**Layout and Navigation**](#layouts-and-navigation-tools): A minimap and multiple tools for the canvas in the bottom right corner. -### Add nodes to the canvas +### Add nodes to the canvas manually You can add individual nodes to the canvas in addition to what is already displayed. -1. Click **Explore**. -2. On the **Search** tab, select a **Vertex type**. This is the name of the - collection that stores the node you want to select. -3. Enter a value into the **Search** field. +1. Click **Search & add nodes to canvas**. +2. Select a **Node type**. This is the name of the collection that stores the + node you want to select. +3. Enter a value into the **Search** field. This searches common attributes + as indicated by the placeholder text and finds up to 10 nodes that contain + this text in one of these attributes (case-insensitive). 4. Select one or more nodes from the list on the left-hand side. 5. Optional: You can check the attributes of the selected nodes on the right-hand side. Use the buttons at the bottom to switch between nodes. 6. Click **Add _n_ vertices**. 7. To see the neighbor nodes and the edges that connect them, right-click a node, - click **Expand (_n_)** and then **All (_n_)**. + click **Expand (_n_)** and then **All (_n_)**. -### Display a subgraph using a query +### Add nodes and edges using a query -You can run an AQL query to view a subset of the graph. -It replaces the current content of the canvas. +You can run an AQL query to add a nodes, edges, or paths of the graph to the canvas. -1. Click **Explore**. -2. On the **New query** tab, enter an AQL query that returns edges or paths +1. Click **Queries** of the top-left widget. +2. Click **New query**. +3. Enter an AQL query that returns nodes, edges, or paths (e.g. a graph traversal query), for example: ```aql - FOR edge IN edgeColl FILTER edge.value > 10 RETURN edge + FOR node IN coll LIMIT 10 RETURN node // [ { "_id": "..." }, ... ] ``` ```aql - FOR v, e, p IN 1..3 ANY "coll/753" GRAPH "myGraph" RETURN p + FOR edge IN edgeColl FILTER edge.value > 10 RETURN edge // [ { "_from": "...", "_to": "...", "_id": "..." }, ... ] ``` -3. The edges and their nodes appear on the canvas. + ```aql + FOR v, e, p IN 1..3 ANY "coll/753" GRAPH "myGraph" RETURN p // [ { "vertices": [...], "edges": [...] }, ... ] + ``` +4. Click **Run query**. Depending on what the query returns, either only nodes + or edges with their nodes appear on the canvas, in addition to what is + already displayed. {{< tip >}} You can save queries for future use: -1. Click **Explore**. -2. On the **New query** tab, click **Save as**, enter a name and optionally a - description, then click **Save**. -3. To run a saved query, click **Explore**. -4. On the **Saved Queries** tab, you can see a list of saved queries, and the - following actions are available for each: +1. Click **Queries** of the top-left widget. +2. Click **New query**. +3. Enter or edit the AQL query you want to save. You can optionally use + bind variables to parameterize saved queries. +4. Enter a name and optionally a description, then click **Save**. +5. To run a saved query, click **Queries** of the top-left widget. +6. Select a query from the list. The following actions are available for each query: + - **Bind Variables** to set for the query. - **Run** the query. - **Copy** the query string to the clipboard. - **Delete** a no longer needed query. {{< /tip >}} +### Add nodes and edges using a query based on a selection + +You can select nodes and edges on the canvas and then use a **Canvas Action**. +This runs an AQL query to add nodes, edges, or paths of the graph to the canvas. +The query has access to the current selection via special bind variables. + +1. Create a selection. You have different options: + - Click a node or edge to select only this element. + - Hold the **Shift** or **Ctrl** key and click multiple nodes and edges. + - Hold the **Shift** or **Ctrl** key and drag the mouse to perform a box selection. +2. Right-click the canvas, click **Canvas Action**, and select a saved action. +3. Depending on the query, additional nodes or edges with their nodes are added + to the canvas. +4. To create a custom Canvas Action query, click **Queries** of the top-left widget. +5. Click **Canvas Actions**, then **New action**. +6. Enter an AQL query that makes use of the special bind variable `@nodes`, + `@edges`, or both and returns nodes, edges, or paths. Examples: + ```aql + FOR selectedNode IN @nodes + FOR v, e, p IN 1..3 ANY selectedNode GRAPH "myGraph" + FILTER p.edges[*].value ALL < 7 + LIMIT 20 + RETURN p + ``` +7. Enter a name and optionally a description for the action and click **Save**. + ### View node and edge properties You can inspect the document attributes of node or edge as follows: From 82ea518b9787465b5d6fdc88e86927fd1c05aa23 Mon Sep 17 00:00:00 2001 From: knakatasf Date: Wed, 9 Jul 2025 01:46:05 +0200 Subject: [PATCH 10/10] chore(docs): Added info on indexes of a collection --- site/content/3.12/develop/http-api/schemas.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/site/content/3.12/develop/http-api/schemas.md b/site/content/3.12/develop/http-api/schemas.md index c0593e31f5..5266c2478d 100644 --- a/site/content/3.12/develop/http-api/schemas.md +++ b/site/content/3.12/develop/http-api/schemas.md @@ -26,6 +26,7 @@ paths: - Each collections shows a list of attribute, the data types of each attribute, whether the attribute is optional (meaning some documents/edges mat not have it), and example documents or edges for reference. + Any indexes defined on the collection’s attributes are shown, if available. parameters: - name: database-name in: path @@ -144,6 +145,7 @@ paths: - Each collection shows a list of attributes, the data types of each attribute, whether the attribute is optional (meaning some documents/edges may not have it), and example documents or edges for reference. + Any indexes defined on the collection’s attributes are shown, if available. parameters: - name: database-name in: path @@ -258,7 +260,8 @@ paths: - The view shows its name and which collections and fields it links to. - Each collection shows a list of attributes, the data types of each attribute whether the attribute is optional (meaning some documents/edges may not have it), - and example documents or edges for reference + and example documents or edges for reference. + Any indexes defined on the collection’s attributes are shown, if available. parameters: - name: database-name in: path @@ -362,7 +365,8 @@ paths: Show the specified collection and its sampled schemas. The schema shows a list of attributes (fields), the data types of each attribute whether the attribute is optional (meaning some documents/edges may not have it), - and example documents or edges for reference + and example documents or edges for reference. + Any indexes defined on the collection’s attributes are shown, if available. parameters: - name: database-name in: path