diff --git a/doc/source/cypher_manual/data_types.md b/doc/source/cypher_manual/data_types.md index df916bed7..f7c3a52ee 100644 --- a/doc/source/cypher_manual/data_types.md +++ b/doc/source/cypher_manual/data_types.md @@ -8,22 +8,22 @@ The following table showcases all data types supported by NeuG and their differe | Category | Type | System Default value | NeuG Example | Neo4j Example | |----------|------|---------------------|--------------|---------------| -| Primitive | INT32 | `0` | `Return CAST(42, 'INT32')` | `Return 42` | -| Primitive | UINT32 | `0` | `Return CAST(42, 'UINT32')` | unsupported | -| Primitive | INT64 | `0` | `Return 9223372036854775807` | `Return 9223372036854775807` | -| Primitive | UINT64 | `0` | `Return CAST(9223372036854775807, 'UINT64')` | unsupported | -| Primitive | FLOAT | `0.0` | `Return CAST(3.14, 'FLOAT')` | `Return 3.14f` | -| Primitive | DOUBLE | `0.0` | `Return 3.14159265359` | `Return 3.14159265359d` | -| Primitive | BOOL | `false` | `Return true` | `Return true` | -| Primitive | NULL | `null` | `Return null` | `Return null` | -| String | VARCHAR | `''` (empty string) | `Return 'Hello World'` | `Return 'Hello World'` | -| Temporal | DATE | `1970-01-01` | `Return date('2022-06-06')` | `Return date('2022-06-06')` | -| Temporal | DATETIME | `1970-01-01 00:00:00` | `Return timestamp('2022-06-06 12:00:00')` | `Return datetime('2022-06-06T12:00:00')` | -| Temporal | INTERVAL | `0 year 0 month 0 day` (zero interval) | `RETURN interval('1 year 2 month 3 day')` | `Return duration('P1Y2M3D')` | -| Composite | LIST | `[]` (empty list) | `Return [1, 2, 3]` | `Return [1, 2, 3]` | -| Pattern | NODE | `{}` (empty node) | `{_ID: 0, _LABEL: person, id: 1, name: marko, age: 29}` | `(:person {name: 'Alice', age: 30})` | -| Pattern | REL | `{}` (empty edge) | `{_ID: 2, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0}` | `[:knows {weight: 1.0}]` | -| Pattern | REPEATED PATH | `[]` (empty path) | `{_ID: 0, _LABEL: person}, {_ID: 4294967298, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 2}, {_ID: 2, _LABEL: person}, {_ID: 4297064449, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 2, _DST_ID: 72057594037927937}, {_ID: 72057594037927937, _LABEL: software}` | `(:Person {name: "Kiefer", id: 4, age: 1992})-[:FOLLOWS]->(:Person {name: "Jack", id: 3, age: 1979})-[:FOLLOWS]->(:Person {name: "Kevin", id: 5, age: 1997})` | +| Primitive | INT32 | `0` | `RETURN CAST(42, 'INT32')` | `RETURN 42` | +| Primitive | UINT32 | `0` | `RETURN CAST(42, 'UINT32')` | unsupported | +| Primitive | INT64 | `0` | `RETURN 9223372036854775807` | `RETURN 9223372036854775807` | +| Primitive | UINT64 | `0` | `RETURN CAST(9223372036854775807, 'UINT64')` | unsupported | +| Primitive | FLOAT | `0.0` | `RETURN CAST(3.14, 'FLOAT')` | `RETURN 3.14f` | +| Primitive | DOUBLE | `0.0` | `RETURN 3.14159265359` | `RETURN 3.14159265359d` | +| Primitive | BOOL | `false` | `RETURN true` | `RETURN true` | +| Primitive | NULL | `null` | `RETURN null` | `RETURN null` | +| String | VARCHAR | `''` (empty string) | `RETURN 'Hello World'` | `RETURN 'Hello World'` | +| Temporal | DATE | `1970-01-01` | `RETURN date('2022-06-06')` | `RETURN date('2022-06-06')` | +| Temporal | DATETIME | `1970-01-01 00:00:00` | `RETURN timestamp('2022-06-06 12:00:00')` | `RETURN datetime('2022-06-06T12:00:00')` | +| Temporal | INTERVAL | `0 year 0 month 0 day` (zero interval) | `RETURN interval('1 year 2 month 3 day')` | `RETURN duration('P1Y2M3D')` | +| Composite | LIST | `[]` (empty list) | `RETURN [1, 2, 3]` | `RETURN [1, 2, 3]` | +| Pattern | NODE | `{}` (empty node) | `{_ID: 0, _LABEL: Person, id: 1, name: marko, age: 29}` | `(:Person {name: 'Alice', age: 30})` | +| Pattern | REL | `{}` (empty edge) | `{_ID: 2, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0}` | `[:KNOWS {weight: 1.0}]` | +| Pattern | REPEATED PATH | `[]` (empty path) | `{_ID: 0, _LABEL: Person}, {_ID: 4294967298, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 2}, {_ID: 2, _LABEL: Person}, {_ID: 4297064449, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 2, _DST_ID: 72057594037927937}, {_ID: 72057594037927937, _LABEL: Software}` | `(:Person {name: "Kiefer", id: 4, age: 1992})-[:FOLLOWS]->(:Person {name: "Jack", id: 3, age: 1979})-[:FOLLOWS]->(:Person {name: "Kevin", id: 5, age: 1997})` | ## Detailed Introduction @@ -32,7 +32,7 @@ The following table showcases all data types supported by NeuG and their differe #### INT32 - **Description**: 32-bit signed integer type - **Range**: [2,147,483,648, 2,147,483,647] -- **Query Example**: `Return CAST(42, 'INT32') as int32_value;` +- **Query Example**: `RETURN CAST(42, 'INT32') AS int32_value;` #### UINT32 - **Description**: 32-bit unsigned integer type @@ -123,12 +123,12 @@ RETURN ['marko', 2]; Combining different property types from nodes in a list: ```cypher -MATCH (n:person) RETURN [n.name, n.age]; +MATCH (n:Person) RETURN [n.name, n.age]; ``` Supporting nested list structures: ```cypher -MATCH (n:person) RETURN [["name", n.name], ["age", n.age]]; +MATCH (n:Person) RETURN [["name", n.name], ["age", n.age]]; ``` **Key Technical Details:** @@ -143,17 +143,17 @@ MATCH (n:person) RETURN [["name", n.name], ["age", n.age]]; #### NODE - **Description**: Represents a node in the graph - **Internal Structure** (order is insignificant): `_ID` (internal identifier), `_LABEL` (indication of node type) and property fields -- **Query Example**: `MATCH (n:person) RETURN n AS node_value;` -- **NeuG Format**: `{_ID: 0, _LABEL: person, id: 1, name: marko, age: 29}` +- **Query Example**: `MATCH (n:Person) RETURN n AS node_value;` +- **NeuG Format**: `{_ID: 0, _LABEL: Person, id: 1, name: marko, age: 29}` #### REL (Edge) - **Description**: Represents an edge in the graph - **Internal Structure** (order is insignificant): `_ID` (edge internal identifier), `_LABEL` (indication of edge type), `_SRC_ID` (internal identifier of source node), `_SRC_LABEL` (label of source node), `_DST_ID` (internal identifier of destination node), `_DST_LABEL` (label of destination node), and property fields -- **Query Example**: `MATCH ()-[r:knows]->() RETURN r AS rel_value;` -- **NeuG Format**: `{_ID: 2, _LABEL: knows, _SRC_ID: 0, _SRC_LABEL: person, _DST_ID: 2, _DST_LABEL: person, weight: 1.0}` +- **Query Example**: `MATCH ()-[r:KNOWS]->() RETURN r AS rel_value;` +- **NeuG Format**: `{_ID: 2, _LABEL: KNOWS, _SRC_ID: 0, _SRC_LABEL: Person, _DST_ID: 2, _DST_LABEL: Person, weight: 1.0}` #### PATH - **Description**: Represents a graph path formed by alternating nodes and edges. - **Internal Structure**: An **ordered sequence** of nodes and edges along the path, including the starting and ending nodes. -- **Query Example**: `MATCH (a:person)-[p*1..2]->(c) RETURN p AS path_value;` -- **NeuG Format**: `{_ID: 0, _LABEL: person}, {_ID: 4294967298, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 2}, {_ID: 2, _LABEL: person}, {_ID: 4297064449, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 2, _DST_ID: 72057594037927937}, {_ID: 72057594037927937, _LABEL: software}` +- **Query Example**: `MATCH (a:Person)-[p*1..2]->(c) RETURN p AS path_value;` +- **NeuG Format**: `{_ID: 0, _LABEL: Person}, {_ID: 4294967298, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 2}, {_ID: 2, _LABEL: Person}, {_ID: 4297064449, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 2, _DST_ID: 72057594037927937}, {_ID: 72057594037927937, _LABEL: Software}` diff --git a/doc/source/cypher_manual/ddl_clause.md b/doc/source/cypher_manual/ddl_clause.md index de1aa9da8..4f9c41891 100644 --- a/doc/source/cypher_manual/ddl_clause.md +++ b/doc/source/cypher_manual/ddl_clause.md @@ -21,20 +21,20 @@ Please refer to the following examples for more usages. ## Create Node Type -Create a node with Label type "person", specifying the property names, types, and primary key for the person. +Create a node with Label type "Person", specifying the property names, types, and primary key for the Person. ``` -CREATE NODE TABLE person ( +CREATE NODE TABLE Person ( name STRING, age INT32, PRIMARY KEY (name) ); ``` -By default, if a person type already exists in the database, an error will be reported. Use IF NOT EXISTS to avoid errors - it will only create if the type doesn't exist in the database, otherwise it will do nothing. +By default, if a Person type already exists in the database, an error will be reported. Use IF NOT EXISTS to avoid errors - it will only create if the type doesn't exist in the database, otherwise it will do nothing. ``` -CREATE NODE TABLE IF NOT EXISTS person ( +CREATE NODE TABLE IF NOT EXISTS Person ( name STRING, age INT32, PRIMARY KEY (name) @@ -43,11 +43,11 @@ CREATE NODE TABLE IF NOT EXISTS person ( ## Create Edge Type -Create an edge of type "knows" from person to person, specifying the property names and types for knows. Currently, edges do not support specifying primary keys. +Create an edge of type "KNOWS" from Person to Person, specifying the property names and types for KNOWS. Currently, edges do not support specifying primary keys. ``` -CREATE REL TABLE IF NOT EXISTS knows ( - FROM person TO person, +CREATE REL TABLE IF NOT EXISTS KNOWS ( + FROM Person TO Person, weight DOUBLE ); ``` @@ -56,11 +56,11 @@ CREATE REL TABLE IF NOT EXISTS knows ( Optionally, you can add exactly one *multiplicity* token after the last column definition (and a comma), before the closing `)` of the `CREATE REL TABLE` header. It describes cardinality along the forward direction (from source to target). Allowed values are `ONE_TO_ONE`, `ONE_TO_MANY`, `MANY_TO_ONE`, and `MANY_TO_MANY`. If you omit it, the edge type uses `MANY_TO_MANY` by default. -For example, on the same `person` / `knows` / `weight` shape as above: +For example, on the same `Person` / `KNOWS` / `weight` shape as above: ``` -CREATE REL TABLE IF NOT EXISTS knows ( - FROM person TO person, +CREATE REL TABLE IF NOT EXISTS KNOWS ( + FROM Person TO Person, weight DOUBLE, MANY_TO_MANY ); @@ -70,11 +70,11 @@ CREATE REL TABLE IF NOT EXISTS knows ( You can append a `WITH ( … )` clause *after* the closing `)` of the table header. Inside the parentheses, pass one or more options as `name = value`, where values are literals. A common key is `sort_key_for_nbr`, whose value is typically a string literal naming an edge property used for ordering. The clause is optional. -Example, still with `person`, `knows`, and `weight` only—here `weight` is used as the sort column name: +Example, still with `Person`, `KNOWS`, and `weight` only—here `weight` is used as the sort column name: ``` -CREATE REL TABLE IF NOT EXISTS knows ( - FROM person TO person, +CREATE REL TABLE IF NOT EXISTS KNOWS ( + FROM Person TO Person, weight DOUBLE ) WITH (sort_key_for_nbr = 'weight'); ``` @@ -88,7 +88,7 @@ Multiplicity and `WITH` options are defined at **edge type** scope (the edge nam Delete a specified Node type. Use IF EXISTS to avoid errors when the type doesn't exist. ``` -DROP TABLE IF EXISTS person; +DROP TABLE IF EXISTS Person; ``` ## Drop Edge Type @@ -96,7 +96,7 @@ DROP TABLE IF EXISTS person; Delete a specified Edge type. Use IF EXISTS to avoid errors when the type doesn't exist. ``` -DROP TABLE IF EXISTS knows; +DROP TABLE IF EXISTS KNOWS; ``` ## Rename Node or Edge Type @@ -104,8 +104,8 @@ DROP TABLE IF EXISTS knows; Rename a node or edge type by `RENAME TO`. ``` -ALTER TABLE person RENAME TO person2; -ALTER TABLE knows RENAME TO knows2; +ALTER TABLE Person RENAME TO Person2; +ALTER TABLE KNOWS RENAME TO KNOWS2; ``` ## Add Property @@ -113,8 +113,8 @@ ALTER TABLE knows RENAME TO knows2; Add properties to a node or edge type. ``` -ALTER TABLE person ADD IF NOT EXISTS gender INT32; -ALTER TABLE knows ADD IF NOT EXISTS info STRING; +ALTER TABLE Person ADD IF NOT EXISTS gender INT32; +ALTER TABLE KNOWS ADD IF NOT EXISTS info STRING; ``` ## Drop Property @@ -122,8 +122,8 @@ ALTER TABLE knows ADD IF NOT EXISTS info STRING; Remove properties from a node or edge type. ``` -ALTER TABLE person DROP IF EXISTS gender; -ALTER TABLE knows DROP IF EXISTS info; +ALTER TABLE Person DROP IF EXISTS gender; +ALTER TABLE KNOWS DROP IF EXISTS info; ``` ## Rename Property @@ -131,6 +131,6 @@ ALTER TABLE knows DROP IF EXISTS info; Rename properties of a node or edge type. ``` -ALTER TABLE person RENAME age TO age2; -ALTER TABLE knows RENAME weight TO weight2; +ALTER TABLE Person RENAME age TO age2; +ALTER TABLE KNOWS RENAME weight TO weight2; ``` \ No newline at end of file diff --git a/doc/source/cypher_manual/dml_clause.md b/doc/source/cypher_manual/dml_clause.md index 82d450eb3..b8975e67b 100644 --- a/doc/source/cypher_manual/dml_clause.md +++ b/doc/source/cypher_manual/dml_clause.md @@ -80,7 +80,7 @@ The SET clause is used to update properties of existing nodes and edges. Update properties of a specific node. ```cypher -MATCH (a:person) +MATCH (a:Person) WHERE a.name = 'marko' SET a.age = 37, a.city = 'New York' RETURN a.* @@ -91,7 +91,7 @@ RETURN a.* Update properties of a specific edge. ```cypher -MATCH (a:person)-[k:knows]->(b:person) +MATCH (a:Person)-[k:KNOWS]->(b:Person) WHERE a.name = 'marko' AND b.name = 'josh' SET k.weight = 10.0, k.since = '2023-01-01' RETURN k.* @@ -106,7 +106,7 @@ The DELETE clause is used to remove nodes and edges from the graph. Delete a node from the graph. By default, you can only delete nodes that have no edge to avoid creating dangling edges. ```cypher -MATCH (a:person) +MATCH (a:Person) WHERE a.name = 'marko' DELETE a ``` @@ -116,7 +116,7 @@ DELETE a Use DETACH DELETE to forcibly delete a node and all its attached edges. This prevents errors when trying to delete nodes that have existing edges. ```cypher -MATCH (a:person) +MATCH (a:Person) WHERE a.name = 'marko' DETACH DELETE a ``` @@ -126,7 +126,7 @@ DETACH DELETE a Delete specific edges between nodes while keeping the nodes. ```cypher -MATCH (a:person)-[k:knows]->(b:person) +MATCH (a:Person)-[k:KNOWS]->(b:Person) WHERE a.name = 'marko' AND b.name = 'josh' DELETE k ``` diff --git a/doc/source/cypher_manual/expression/agg_func.md b/doc/source/cypher_manual/expression/agg_func.md index 50f28306b..f1b9f0c83 100644 --- a/doc/source/cypher_manual/expression/agg_func.md +++ b/doc/source/cypher_manual/expression/agg_func.md @@ -4,9 +4,9 @@ Aggregate Functions are primarily used to group current data and perform aggrega Function | Description | Can be used with DISTINCT | Example ---------|-------------|---------------------------|-------- -count | return the row counts | YES | Return count (a.name); -collect | collect the elements in a single list | YES | Return collect(a.name); -min | return the minimum value | NO | Return min(a.age); -max | return the maximum value | NO | Return max(a.age); -sum | sum up the value | NO | Return sum(a.age); -avg | return the average value | NO | Return avg(a.age); +count | return the row counts | YES | RETURN count(a.name); +collect | collect the elements in a single list | YES | RETURN collect(a.name); +min | return the minimum value | NO | RETURN min(a.age); +max | return the maximum value | NO | RETURN max(a.age); +sum | sum up the value | NO | RETURN sum(a.age); +avg | return the average value | NO | RETURN avg(a.age); diff --git a/doc/source/cypher_manual/expression/arithmetic_op.md b/doc/source/cypher_manual/expression/arithmetic_op.md index b0d6e7f1d..565b477fc 100644 --- a/doc/source/cypher_manual/expression/arithmetic_op.md +++ b/doc/source/cypher_manual/expression/arithmetic_op.md @@ -80,11 +80,11 @@ The following table details the error types that each operator may encounter: | Operator | Overflow | Underflow | DivideByZero | Example | |----------|----------|-----------|--------------|---------| -| + | YES | YES | N/A | Return CAST(2147483647, 'int32') + CAST(1, 'int32') | -| - | YES | YES | N/A | Return CAST(-2147483648, 'int32') - CAST(1, 'int32') | -| * | YES | YES | N/A | Return CAST(2147483647, 'int32') * CAST(2, 'int32') | -| / | NO | NO | YES | Return 5 / 0 | -| % | NO | NO | YES | Return 5 % 0 | +| + | YES | YES | N/A | RETURN CAST(2147483647, 'int32') + CAST(1, 'int32') | +| - | YES | YES | N/A | RETURN CAST(-2147483648, 'int32') - CAST(1, 'int32') | +| * | YES | YES | N/A | RETURN CAST(2147483647, 'int32') * CAST(2, 'int32') | +| / | NO | NO | YES | RETURN 5 / 0 | +| % | NO | NO | YES | RETURN 5 % 0 | ## Date Arithmetic diff --git a/doc/source/cypher_manual/expression/graph_func.md b/doc/source/cypher_manual/expression/graph_func.md index d26898787..d159335c0 100644 --- a/doc/source/cypher_manual/expression/graph_func.md +++ b/doc/source/cypher_manual/expression/graph_func.md @@ -6,8 +6,8 @@ In addition to the various relational data-based function operations introduced Function | Description | Example ---------|-------------|-------- -ID() | Get the Internal ID of a node/edge | Return (a) Return ID(a) -LABEL()/LABELS() | Get the label of a node/edge | Match (a) Return LABEL(a) +ID() | Get the Internal ID of a node/edge | MATCH (a) RETURN ID(a) +LABEL()/LABELS() | Get the label of a node/edge | MATCH (a) RETURN LABEL(a) ## Edge Function @@ -15,16 +15,16 @@ In addition to ID and LABEL functions, there are the following edge-based functi Function | Description | Example ---------|-------------|-------- -START_NODE() | Returns the starting node of edge data | Match ()-[b]->() Return START_NODE(b); -END_NODE() | Returns the ending node of edge data | Match ()-[b]->() Return END_NODE(b); +START_NODE() | Returns the starting node of edge data | MATCH ()-[b]->() RETURN START_NODE(b); +END_NODE() | Returns the ending node of edge data | MATCH ()-[b]->() RETURN END_NODE(b); ## Repeated Path Function Function | Description | Example ---------|-------------|-------- -NODES | Returns all nodes from a path | Match (a)-[b*2..3]->() Return NODES(b); -RELS | Returns all edges from a path | Match (a)-[b*2..3]->() Return RELS(b); -PROPERTIES | Returns given property from nodes/edges | Match (a)-[b*2..3]->() Return PROPERTIES(nodes(b), 'name'), PROPERTIES(rels(b), 'weight'); -IS_TRAIL | Checks if path contains repeated edges (`true` if no) | Match (a)-[b*2..3]->() Return IS_TRAIL(b); -IS_ACYCLIC | Checks if path contains repeated nodes (`true` if no) | Match (a)-[b*2..3]->() Return IS_ACYCLIC(b); -LENGTH | Returns the length of a path | Match (a)-[b*2..3]->() Return LENGTH(b); +NODES | Returns all nodes from a path | MATCH (a)-[b*2..3]->() RETURN NODES(b); +RELS | Returns all edges from a path | MATCH (a)-[b*2..3]->() RETURN RELS(b); +PROPERTIES | Returns given property from nodes/edges | MATCH (a)-[b*2..3]->() RETURN PROPERTIES(nodes(b), 'name'), PROPERTIES(rels(b), 'weight'); +IS_TRAIL | Checks if path contains repeated edges (`true` if no) | MATCH (a)-[b*2..3]->() RETURN IS_TRAIL(b); +IS_ACYCLIC | Checks if path contains repeated nodes (`true` if no) | MATCH (a)-[b*2..3]->() RETURN IS_ACYCLIC(b); +LENGTH | Returns the length of a path | MATCH (a)-[b*2..3]->() RETURN LENGTH(b); diff --git a/doc/source/cypher_manual/index.md b/doc/source/cypher_manual/index.md index e407e07ea..f37857913 100644 --- a/doc/source/cypher_manual/index.md +++ b/doc/source/cypher_manual/index.md @@ -18,7 +18,7 @@ While SQL is designed for relational databases with tables and rows, Cypher is o In NeuG, we refer to a Cypher query as a **Statement**. A Statement consists of multiple **Clauses**. For example, in the following query: ```cypher -MATCH (p:person) +MATCH (p:Person) WHERE p.age = '29' RETURN p.name as name; ``` @@ -39,43 +39,43 @@ The above schema graph can be created by the following statements: ```cypher // Example schema definition -CREATE NODE TABLE person ( +CREATE NODE TABLE Person ( name STRING, age INT32, PRIMARY KEY (name) ); -CREATE NODE TABLE software ( +CREATE NODE TABLE Software ( name STRING, lang STRING, PRIMARY KEY (name) ); -CREATE REL TABLE knows ( - FROM person TO person, +CREATE REL TABLE KNOWS ( + FROM Person TO Person, weight DOUBLE ); -CREATE REL TABLE created ( - FROM person TO software, +CREATE REL TABLE CREATED ( + FROM Person TO Software, weight DOUBLE ); ``` **Schema-compliant query:** -In the following query, the vertex label `person` and edge label `(person-knows->person)` both conform to the schema constraints defined above. The `person` node contains `age` and `name` properties, and the `age` property is of type INT32, which is comparable to the constant 18. Therefore, this query satisfies all schema constraints and is valid: +In the following query, the vertex label `Person` and edge label `(Person-KNOWS->Person)` both conform to the schema constraints defined above. The `Person` node contains `age` and `name` properties, and the `age` property is of type INT32, which is comparable to the constant 18. Therefore, this query satisfies all schema constraints and is valid: ```cypher -MATCH (p:person)-[:knows]->(f:person) +MATCH (p:Person)-[:KNOWS]->(f:Person) WHERE p.age > 18 RETURN p.name, f.name; ``` **Non-schema-compliant query (would fail):** -The edge label `(person-follows-> person)` specified in this query does not exist in the schema, making it invalid and resulting in a "Table `follows` does not exist" error. +The edge label `(Person-FOLLOWS->Person)` specified in this query does not exist in the schema, making it invalid and resulting in a "Table `FOLLOWS` does not exist" error. ```cypher -MATCH (p:person)-[:follows]->(m:person) +MATCH (p:Person)-[:FOLLOWS]->(m:Person) RETURN p.name; ``` @@ -88,9 +88,9 @@ We also define a set of query syntax that can satisfy both Transactional Process For example, you can query all triangle patterns in the graph database using the following query: ```cypher -MATCH (a:person)-[:created]->(b:software), - (c:person)-[:created]->(b:software), - (a:person)-[:knows]->(c:person) +MATCH (a:Person)-[:CREATED]->(b:Software), + (c:Person)-[:CREATED]->(b:Software), + (a:Person)-[:KNOWS]->(c:Person) WHERE a.name < c.name RETURN a.name, b.name, c.name; ``` @@ -105,8 +105,8 @@ In addition to DQL and DDL, NeuG also supports data update functionality, which **Bulk import example:** ```cypher -COPY person FROM "person.csv" (delim=','); -COPY knows FROM "knows.csv" (delim=','); +COPY Person FROM "person.csv" (delim=','); +COPY KNOWS FROM "knows.csv" (delim=','); ``` The above two Statements first bulk load node data with label `person` from person.csv, then bulk load edge data with label `person-[knows]->person` from knows.csv. @@ -117,18 +117,18 @@ We also provide incremental write syntax for incrementally updating graph data. **Node creation example:** ```cypher -CREATE (p:person {name: 'Bob', age: 30}); +CREATE (p:Person {name: 'Bob', age: 30}); ``` **Relationship creation example:** ```cypher -MATCH (a:person {name: 'Bob'}), (b:person {name: 'marko'}) -CREATE (a)-[:knows {weight: 3.0}]->(b); +MATCH (a:Person {name: 'Bob'}), (b:Person {name: 'marko'}) +CREATE (a)-[:KNOWS {weight: 3.0}]->(b); ``` **Node deletion example:** ```cypher -MATCH (p:person {name: 'Bob'}) +MATCH (p:Person {name: 'Bob'}) DELETE p; ``` @@ -162,12 +162,12 @@ For more complex graph-oriented analysis, external data can be loaded as a tempo ```cypher LOAD FROM "person.csv" (delim=',') -AS person; +AS Person; LOAD FROM "knows.csv" (delim=',') -AS knows; +AS KNOWS; -MATCH (p1:person)-[:knows*1..2]->(p2:person) +MATCH (p1:Person)-[:KNOWS*1..2]->(p2:Person) RETURN p1, p2; ``` diff --git a/doc/source/cypher_manual/query_clauses/limit_clause.md b/doc/source/cypher_manual/query_clauses/limit_clause.md index 08c96ae6f..5b47ac0d2 100644 --- a/doc/source/cypher_manual/query_clauses/limit_clause.md +++ b/doc/source/cypher_manual/query_clauses/limit_clause.md @@ -5,9 +5,9 @@ Limit is used to control the number of output results. In addition to being used ## Limit with Integer Value ``` -Match (a:person) -Return a.age -Limit 2; +MATCH (a:Person) +RETURN a.age +LIMIT 2; ``` Since there is no ordering of the output, the result may be any two results. @@ -25,9 +25,9 @@ output: ## Limit with Integer Expression ``` -Match (a:person) -Return a.age -Limit 1+1; +MATCH (a:Person) +RETURN a.age +LIMIT 1+1; ``` output: @@ -48,9 +48,9 @@ Limit controls the number of output results, which is equivalent to determining ## Skip with Integer Value ``` -Match (a:person) -Return a.age -Skip 2; +MATCH (a:Person) +RETURN a.age +SKIP 2; ``` The query is used to skip the first two rows of results. @@ -68,9 +68,9 @@ output: ## Skip with Integer Expression ``` -Match (a:person) -Return a.age -Skip 1+1; +MATCH (a:Person) +RETURN a.age +SKIP 1+1; ``` output: diff --git a/doc/source/cypher_manual/query_clauses/match_clause.md b/doc/source/cypher_manual/query_clauses/match_clause.md index 4083dfcaf..3df296c05 100644 --- a/doc/source/cypher_manual/query_clauses/match_clause.md +++ b/doc/source/cypher_manual/query_clauses/match_clause.md @@ -9,7 +9,7 @@ The `MATCH` clause is used to search for patterns in the graph database. It allo Find all nodes with a specific label. This query returns all nodes labeled as `person`. ```cypher -MATCH (p:person) RETURN p; +MATCH (p:Person) RETURN p; ``` output: @@ -17,13 +17,13 @@ output: +-------------------------------------------------------+ | p | +=======================================================+ -| {_ID: 0, _LABEL: person, name: marko, age: 29} | +| {_ID: 0, _LABEL: Person, name: marko, age: 29} | +-------------------------------------------------------+ -| {_ID: 1, _LABEL: person, name: vadas, age: 27} | +| {_ID: 1, _LABEL: Person, name: vadas, age: 27} | +-------------------------------------------------------+ -| {_ID: 2, _LABEL: person, name: josh, age: 32} | +| {_ID: 2, _LABEL: Person, name: josh, age: 32} | +-------------------------------------------------------+ -| {_ID: 3, _LABEL: person, name: peter, age: 35} | +| {_ID: 3, _LABEL: Person, name: peter, age: 35} | +-------------------------------------------------------+ ``` @@ -31,10 +31,10 @@ output: Find nodes with any of the specified labels. This query returns all nodes labeled as either `person` or `software`. -**Note**: Unlike Neo4j, NeuG does not support multi-label nodes. In Neo4j, `(p:person:software)` represents nodes that have both `person` and `software` labels simultaneously. In NeuG, this syntax represents a union of nodes with either `person` or `software` labels. +**Note**: Unlike Neo4j, NeuG does not support multi-label nodes. In Neo4j, `(p:Person:Software)` represents nodes that have both `person` and `software` labels simultaneously. In NeuG, this syntax represents a union of nodes with either `person` or `software` labels. ```cypher -MATCH (p:person:software) RETURN p; +MATCH (p:Person:Software) RETURN p; ``` output: @@ -42,17 +42,17 @@ output: +-----------------------------------------------------------------------------+ | p | +=============================================================================+ -| {_ID: 0, _LABEL: person, name: marko, age: 29} | +| {_ID: 0, _LABEL: Person, name: marko, age: 29} | +-----------------------------------------------------------------------------+ -| {_ID: 1, _LABEL: person, name: vadas, age: 27} | +| {_ID: 1, _LABEL: Person, name: vadas, age: 27} | +-----------------------------------------------------------------------------+ -| {_ID: 2, _LABEL: person, name: josh, age: 32} | +| {_ID: 2, _LABEL: Person, name: josh, age: 32} | +-----------------------------------------------------------------------------+ -| {_ID: 3, _LABEL: person, name: peter, age: 35} | +| {_ID: 3, _LABEL: Person, name: peter, age: 35} | +-----------------------------------------------------------------------------+ -| {_ID: 72057594037927936, _LABEL: software, name: lop, lang: java} | +| {_ID: 72057594037927936, _LABEL: Software, name: lop, lang: java} | +-----------------------------------------------------------------------------+ -| {_ID: 72057594037927937, _LABEL: software, name: ripple, lang: java} | +| {_ID: 72057594037927937, _LABEL: Software, name: ripple, lang: java} | +-----------------------------------------------------------------------------+ ``` @@ -69,17 +69,17 @@ output: +-----------------------------------------------------------------------------+ | p | +=============================================================================+ -| {_ID: 0, _LABEL: person, name: marko, age: 29} | +| {_ID: 0, _LABEL: Person, name: marko, age: 29} | +-----------------------------------------------------------------------------+ -| {_ID: 1, _LABEL: person, name: vadas, age: 27} | +| {_ID: 1, _LABEL: Person, name: vadas, age: 27} | +-----------------------------------------------------------------------------+ -| {_ID: 2, _LABEL: person, name: josh, age: 32} | +| {_ID: 2, _LABEL: Person, name: josh, age: 32} | +-----------------------------------------------------------------------------+ -| {_ID: 3, _LABEL: person, name: peter, age: 35} | +| {_ID: 3, _LABEL: Person, name: peter, age: 35} | +-----------------------------------------------------------------------------+ -| {_ID: 72057594037927936, _LABEL: software, name: lop, lang: java} | +| {_ID: 72057594037927936, _LABEL: Software, name: lop, lang: java} | +-----------------------------------------------------------------------------+ -| {_ID: 72057594037927937, _LABEL: software, name: ripple, lang: java} | +| {_ID: 72057594037927937, _LABEL: Software, name: ripple, lang: java} | +-----------------------------------------------------------------------------+ ``` @@ -88,7 +88,7 @@ output: In addition to label constraints, you can specify property-based filtering conditions. ```cypher -MATCH (p:person {name: 'marko'}) RETURN p; +MATCH (p:Person {name: 'marko'}) RETURN p; ``` output: @@ -96,7 +96,7 @@ output: +-------------------------------------------------------+ | p | +=======================================================+ -| {_ID: 0, _LABEL: person, name: marko, age: 29} | +| {_ID: 0, _LABEL: Person, name: marko, age: 29} | +-------------------------------------------------------+ ``` @@ -105,7 +105,7 @@ output: ### Match Edges with Single Label ```cypher -MATCH (p:person)-[k:knows]->(f:person) RETURN k; +MATCH (p:Person)-[k:KNOWS]->(f:Person) RETURN k; ``` output: @@ -113,16 +113,16 @@ output: +------------------------------------------------------------------------------------------------------+ | k | +======================================================================================================+ -| {_ID: 1, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 1, weight: 0.5} | +| {_ID: 1, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 1, weight: 0.5} | +------------------------------------------------------------------------------------------------------+ -| {_ID: 2, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +| {_ID: 2, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +------------------------------------------------------------------------------------------------------+ ``` ### Match Edges with Multiple Labels ```cypher -MATCH (p:person)-[k:knows|created]->(f) RETURN k; +MATCH (p:Person)-[k:KNOWS|CREATED]->(f) RETURN k; ``` output: @@ -130,24 +130,24 @@ output: +--------------------------------------------------------------------------------------------------------------------------------------+ | k | +======================================================================================================================================+ -| {_ID: 1, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 1, weight: 0.5} | +| {_ID: 1, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 1, weight: 0.5} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 2, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +| {_ID: 2, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103806595072, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 0, _DST_ID: 72057594037927936, weight: 0.4} | +| {_ID: 1103806595072, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 0, _DST_ID: 72057594037927936, weight: 0.4} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103808692224, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 2, _DST_ID: 72057594037927936, weight: 0.4} | +| {_ID: 1103808692224, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 2, _DST_ID: 72057594037927936, weight: 0.4} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103808692225, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 2, _DST_ID: 72057594037927937, weight: 1.0} | +| {_ID: 1103808692225, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 2, _DST_ID: 72057594037927937, weight: 1.0} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103809740800, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 3, _DST_ID: 72057594037927936, weight: 0.2} | +| {_ID: 1103809740800, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 3, _DST_ID: 72057594037927936, weight: 0.2} | +--------------------------------------------------------------------------------------------------------------------------------------+ ``` ### Match Edges with Any Label ```cypher -MATCH (p:person)-[k]->(f) RETURN k; +MATCH (p:Person)-[k]->(f) RETURN k; ``` output: @@ -155,17 +155,17 @@ output: +--------------------------------------------------------------------------------------------------------------------------------------+ | k | +======================================================================================================================================+ -| {_ID: 1, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 1, weight: 0.5} | +| {_ID: 1, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 1, weight: 0.5} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 2, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +| {_ID: 2, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103806595072, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 0, _DST_ID: 72057594037927936, weight: 0.4} | +| {_ID: 1103806595072, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 0, _DST_ID: 72057594037927936, weight: 0.4} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103808692224, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 2, _DST_ID: 72057594037927936, weight: 0.4} | +| {_ID: 1103808692224, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 2, _DST_ID: 72057594037927936, weight: 0.4} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103808692225, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 2, _DST_ID: 72057594037927937, weight: 1.0} | +| {_ID: 1103808692225, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 2, _DST_ID: 72057594037927937, weight: 1.0} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103809740800, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 3, _DST_ID: 72057594037927936, weight: 0.2} | +| {_ID: 1103809740800, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 3, _DST_ID: 72057594037927936, weight: 0.2} | +--------------------------------------------------------------------------------------------------------------------------------------+ ``` @@ -174,7 +174,7 @@ output: Filter edges based on their properties. ```cypher -MATCH (p:person)-[k:knows {weight: 1.0}]->(f:person) RETURN k; +MATCH (p:Person)-[k:KNOWS {weight: 1.0}]->(f:Person) RETURN k; ``` output: @@ -182,7 +182,7 @@ output: +------------------------------------------------------------------------------------------------------+ | k | +======================================================================================================+ -| {_ID: 2, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +| {_ID: 2, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +------------------------------------------------------------------------------------------------------+ ``` @@ -195,7 +195,7 @@ NeuG supports variable-length repeated path exploration, which is a common featu Find paths with a variable number of hops. This query returns all paths consisting of 1 or 2 edges. ```cypher -MATCH (p:person)-[k*1..2]->(f) RETURN k; +MATCH (p:Person)-[k*1..2]->(f) RETURN k; ``` @@ -205,7 +205,7 @@ MATCH (p:person)-[k*1..2]->(f) RETURN k; Specify filtering conditions based on the source node's properties. This query finds 1-hop or 2-hop paths starting from the node with name 'marko'. ```cypher -MATCH (p:person {name: 'marko'})-[k*1..2]->(f) RETURN k; +MATCH (p:Person {name: 'marko'})-[k*1..2]->(f) RETURN k; ``` @@ -215,7 +215,7 @@ MATCH (p:person {name: 'marko'})-[k*1..2]->(f) RETURN k; Specify filtering conditions based on the target node's properties. This query finds paths ending at the node with name 'josh'. ```cypher -MATCH (p:person {name: 'marko'})-[k*1..2]->(f {name: 'josh'}) RETURN k; +MATCH (p:Person {name: 'marko'})-[k*1..2]->(f {name: 'josh'}) RETURN k; ``` output: @@ -223,7 +223,7 @@ output: +---------------------------------------------------------------------------------------------------------------------------------------------------+ | k | +===================================================================================================================================================+ -| {_ID: 2, _LABEL: person}, {_ID: 2097152, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 2, _DST_ID: 0}, {_ID: 0, _LABEL: person} | +| {_ID: 2, _LABEL: Person}, {_ID: 2097152, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 2, _DST_ID: 0}, {_ID: 0, _LABEL: Person} | +---------------------------------------------------------------------------------------------------------------------------------------------------+ ``` @@ -234,8 +234,8 @@ Reference [Kuzu's specification](https://docs.kuzudb.com/cypher/query-clauses/ma This query requires each edge in the path to satisfy the constraint `r.weight < 1.0`. ```cypher -MATCH (p:person {name: 'marko'})-[k:knows*1..2 (r, _ | WHERE r.weight <= 1.0)]->(f:person) -Return k; +MATCH (p:Person {name: 'marko'})-[k:KNOWS*1..2 (r, _ | WHERE r.weight <= 1.0)]->(f:Person) +RETURN k; ``` @@ -245,8 +245,8 @@ Return k; Using the `TRAIL` option, you can further restrict repeated paths to ensure no edges are repeated, guaranteeing that path expansion iterations terminate without infinite loops. ```cypher -MATCH (p:person {name: 'marko'})-[k:knows* TRAIL 1..2]->(f:person) -Return k; +MATCH (p:Person {name: 'marko'})-[k:KNOWS* TRAIL 1..2]->(f:Person) +RETURN k; ``` ### Match Simple Path @@ -254,8 +254,8 @@ Return k; Using the `ACYCLIC` option, you can further restrict repeated paths to ensure no nodes are repeated, guaranteeing output of simple paths. ```cypher -MATCH (p:person {name: 'marko'})-[k:knows* ACYCLIC 1..2]->(f:person) -Return k; +MATCH (p:Person {name: 'marko'})-[k:KNOWS* ACYCLIC 1..2]->(f:Person) +RETURN k; ``` ### Match Unweighted Shortest Path @@ -263,7 +263,7 @@ Return k; Specify the `SHORTEST` option to output the unweighted shortest path between two given nodes. ```cypher -MATCH (p:person {name: 'marko'})-[k:knows* SHORTEST 1..2]->(f:person {name: 'josh'}) +MATCH (p:Person {name: 'marko'})-[k:KNOWS* SHORTEST 1..2]->(f:Person {name: 'josh'}) RETURN k; ``` @@ -274,45 +274,45 @@ The `MATCH` clause supports complex pattern matching that combines nodes, edges, Below are some classic graph query patterns that are widely used in various graph query benchmarks: - Triangle Pattern ``` -Match (a:person)-[:created]->(b:software), - (c:person)-[:created]->(b:software), - (a:person)-[:knows]->(c:person) -Where a.name <> b.name AND b.name <> c.name -Return count(*); +MATCH (a:Person)-[:CREATED]->(b:Software), + (c:Person)-[:CREATED]->(b:Software), + (a:Person)-[:KNOWS]->(c:Person) +WHERE a.name <> b.name AND b.name <> c.name +RETURN count(*); ``` - Square Pattern ``` -Match (a:person)-[:created]->(b:software), - (c:person)-[:created]->(b:software), - (a:person)-[:knows]->(d:person), - (c:person)<-[:knows]-(d:person) -Where a.name <> b.name AND b.name <> c.name AND c.name <> d.name -Return count(*); +MATCH (a:Person)-[:CREATED]->(b:Software), + (c:Person)-[:CREATED]->(b:Software), + (a:Person)-[:KNOWS]->(d:Person), + (c:Person)<-[:KNOWS]-(d:Person) +WHERE a.name <> b.name AND b.name <> c.name AND c.name <> d.name +RETURN count(*); ``` - Long Path ``` -Match (a:person)-[:knows]->(b:person), - (b:person)-[:knows]->(c:person), - (c:person)-[:created]->(d:software), - (d:software)<-[:created]-(e:person), - (e:person)-[:knows]->(f:person) -Where a.name <> b.name AND b.name <> c.name +MATCH (a:Person)-[:KNOWS]->(b:Person), + (b:Person)-[:KNOWS]->(c:Person), + (c:Person)-[:CREATED]->(d:Software), + (d:Software)<-[:CREATED]-(e:Person), + (e:Person)-[:KNOWS]->(f:Person) +WHERE a.name <> b.name AND b.name <> c.name AND c.name <> d.name AND d.name <> e.name -Return count(*); +RETURN count(*); ``` - Clique Path ``` -Match (a:person)-[:created]->(b:software), - (c:person)-[:created]->(b:software), - (a:person)-[:knows]->(d:person), - (c:person)<-[:knows]-(d:person), - (a:person)-[:knows]->(c:person), - (d:person)-[:created]->(b:software) -Where a.name <> b.name AND b.name <> c.name AND c.name <> d.name -Return count(*); +MATCH (a:Person)-[:CREATED]->(b:Software), + (c:Person)-[:CREATED]->(b:Software), + (a:Person)-[:KNOWS]->(d:Person), + (c:Person)<-[:KNOWS]-(d:Person), + (a:Person)-[:KNOWS]->(c:Person), + (d:Person)-[:CREATED]->(b:Software) +WHERE a.name <> b.name AND b.name <> c.name AND c.name <> d.name +RETURN count(*); ``` ## Optional Match @@ -322,8 +322,8 @@ The `OPTIONAL MATCH` clause allows you to match patterns that may or may not exi Here's how to use Optional Match: ```cypher -MATCH (a:person)-[:knows]->(b:person) -OPTIONAL MATCH (b:person)-[:created]->(c:software) +MATCH (a:Person)-[:KNOWS]->(b:Person) +OPTIONAL MATCH (b:Person)-[:CREATED]->(c:Software) RETURN a.name, b.name, c.name ``` diff --git a/doc/source/cypher_manual/query_clauses/merge_clause.md b/doc/source/cypher_manual/query_clauses/merge_clause.md index 602488f0f..6f6960dbb 100644 --- a/doc/source/cypher_manual/query_clauses/merge_clause.md +++ b/doc/source/cypher_manual/query_clauses/merge_clause.md @@ -34,32 +34,32 @@ When merging edges, the source and destination nodes must be matched first (typi ```cypher MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'marko'}) -MERGE (u1)-[e:follows {date: 2012}]->(u2) +MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) RETURN u1.name, e.date, u2.name; ``` -If an edge `follows` with `date=2012` already exists between Adam and marko, it is simply returned. +If an edge `FOLLOWS` with `date=2012` already exists between Adam and marko, it is simply returned. ### Create a New Edge ```cypher MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'Bob'}) -MERGE (u1)-[e:follows {date: 2012}]->(u2) +MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) RETURN u1.name, e.date, u2.name; ``` -If no `follows` edge with `date=2012` exists between Adam and Bob, a new edge is created. +If no `FOLLOWS` edge with `date=2012` exists between Adam and Bob, a new edge is created. ### MERGE Edge for Multiple Pairs ```cypher MATCH (u1:User), (u2:User) WHERE id(u1) < id(u2) -MERGE (u1)-[e:follows {date: 2012}]->(u2) +MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) RETURN u1.name, e.date, u2.name; ``` -For each `` pair, `MERGE` checks individually whether a `follows` edge with `date=2012` exists. If it does, the existing edge is returned; otherwise, a new edge is created for that pair. +For each `` pair, `MERGE` checks individually whether a `FOLLOWS` edge with `date=2012` exists. If it does, the existing edge is returned; otherwise, a new edge is created for that pair. ## ON CREATE / ON MATCH @@ -143,7 +143,7 @@ Merging a full path pattern is not supported: ```cypher -- NOT supported -MERGE (:User {name: 'A'})-[:follows {date: 2012}]->(:User {name: 'B'}) +MERGE (:User {name: 'A'})-[:FOLLOWS {date: 2012}]->(:User {name: 'B'}) ``` This would create all vertices and the edge as a whole without checking whether individual vertices already exist, potentially causing duplicates. Instead, merge the nodes and edge separately: @@ -152,5 +152,5 @@ This would create all vertices and the edge as a whole without checking whether -- Supported: merge nodes first, then merge the edge MERGE (u1:User {name: 'A'}) MERGE (u2:User {name: 'B'}) -MERGE (u1)-[:follows {date: 2012}]->(u2) +MERGE (u1)-[:FOLLOWS {date: 2012}]->(u2) ``` diff --git a/doc/source/cypher_manual/query_clauses/order_clause.md b/doc/source/cypher_manual/query_clauses/order_clause.md index 6d11da171..593e6f5e8 100644 --- a/doc/source/cypher_manual/query_clauses/order_clause.md +++ b/doc/source/cypher_manual/query_clauses/order_clause.md @@ -5,8 +5,8 @@ Order is used to sort the current results based on properties to ensure determin ## Order by Single Property ``` -Match (a) -Return a.name +MATCH (a) +RETURN a.name ORDER BY a.name ASC; ``` @@ -32,8 +32,8 @@ output: ## Order by Multiple Properties ``` -Match (a)-[b]->(c) -Return a.name, b.weight +MATCH (a)-[b]->(c) +RETURN a.name, b.weight ORDER BY a.name ASC, b.weight ASC; ``` @@ -63,8 +63,8 @@ In addition to sorting by properties directly, the Order BY key can also be more ### Order by Pre-computed Expression ``` -Match (a)-[b]->(c) -Return a.age, c.name +MATCH (a)-[b]->(c) +RETURN a.age, c.name ORDER BY a.age + 10 ASC, c.name; ``` @@ -90,9 +90,9 @@ output: ### Order by Scalar Function Results ``` -Match (a)-[b]->(c) -Return a.name, c.name -Order BY label(a); +MATCH (a)-[b]->(c) +RETURN a.name, c.name +ORDER BY label(a); ``` @@ -104,10 +104,10 @@ For more Scalar Function operations, see the [Function Section](../../expression Additionally, in BI (Business Intelligence) query scenarios, TopK is one of the most common operations, truncating and outputting only the most significant results. NeuG also supports such queries. ``` -Match (a)-[b]->(c) -Return a.age, c.name +MATCH (a)-[b]->(c) +RETURN a.age, c.name ORDER BY a.age + 10 ASC, c.name ASC -Limit 2; +LIMIT 2; ``` output: diff --git a/doc/source/cypher_manual/query_clauses/return_clause.md b/doc/source/cypher_manual/query_clauses/return_clause.md index 1a43bb3d4..420cc7a56 100644 --- a/doc/source/cypher_manual/query_clauses/return_clause.md +++ b/doc/source/cypher_manual/query_clauses/return_clause.md @@ -6,7 +6,7 @@ Return and With provide similar functionality, both for further aggregation or p ### Return Nodes with Single Label ``` -Match (a:person) Return a; +MATCH (a:Person) RETURN a; ``` The output shows each person node's internal ID (assigned by the graph database), label, and all properties: @@ -14,19 +14,19 @@ The output shows each person node's internal ID (assigned by the graph database) +-------------------------------------------------------+ | a | +=======================================================+ -| {_ID: 0, _LABEL: person, name: marko, age: 29} | +| {_ID: 0, _LABEL: Person, name: marko, age: 29} | +-------------------------------------------------------+ -| {_ID: 1, _LABEL: person, name: vadas, age: 27} | +| {_ID: 1, _LABEL: Person, name: vadas, age: 27} | +-------------------------------------------------------+ -| {_ID: 2, _LABEL: person, name: josh, age: 32} | +| {_ID: 2, _LABEL: Person, name: josh, age: 32} | +-------------------------------------------------------+ -| {_ID: 3, _LABEL: person, name: peter, age: 35} | +| {_ID: 3, _LABEL: Person, name: peter, age: 35} | +-------------------------------------------------------+ ``` ### Return Nodes with Multiple Labels ``` -Match (a) Return a; +MATCH (a) RETURN a; ``` The output shows each node's internal ID, label, and all properties in its own node type: @@ -34,25 +34,25 @@ The output shows each node's internal ID, label, and all properties in its own n +-----------------------------------------------------------------------------+ | a | +=============================================================================+ -| {_ID: 0, _LABEL: person, name: marko, age: 29} | +| {_ID: 0, _LABEL: Person, name: marko, age: 29} | +-----------------------------------------------------------------------------+ -| {_ID: 1, _LABEL: person, name: vadas, age: 27} | +| {_ID: 1, _LABEL: Person, name: vadas, age: 27} | +-----------------------------------------------------------------------------+ -| {_ID: 2, _LABEL: person, name: josh, age: 32} | +| {_ID: 2, _LABEL: Person, name: josh, age: 32} | +-----------------------------------------------------------------------------+ -| {_ID: 3, _LABEL: person, name: peter, age: 35} | +| {_ID: 3, _LABEL: Person, name: peter, age: 35} | +-----------------------------------------------------------------------------+ -| {_ID: 72057594037927936, _LABEL: software, name: lop, lang: java} | +| {_ID: 72057594037927936, _LABEL: Software, name: lop, lang: java} | +-----------------------------------------------------------------------------+ -| {_ID: 72057594037927937, _LABEL: software, name: ripple, lang: java} | +| {_ID: 72057594037927937, _LABEL: Software, name: ripple, lang: java} | +-----------------------------------------------------------------------------+ ``` ## Return Relationships ``` -Match (a:person)-[k]->(b) -Return k; +MATCH (a:Person)-[k]->(b) +RETURN k; ``` The output includes the relationship's internal ID, label, all properties, and the source and destination node labels and IDs: @@ -60,17 +60,17 @@ The output includes the relationship's internal ID, label, all properties, and t +--------------------------------------------------------------------------------------------------------------------------------------+ | k | +======================================================================================================================================+ -| {_ID: 1, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 1, weight: 0.5} | +| {_ID: 1, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 1, weight: 0.5} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 2, _LABEL: knows, _SRC_LABEL: person, _DST_LABEL: person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +| {_ID: 2, _LABEL: KNOWS, _SRC_LABEL: Person, _DST_LABEL: Person, _SRC_ID: 0, _DST_ID: 2, weight: 1.0} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103806595072, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 0, _DST_ID: 72057594037927936, weight: 0.4} | +| {_ID: 1103806595072, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 0, _DST_ID: 72057594037927936, weight: 0.4} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103808692224, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 2, _DST_ID: 72057594037927936, weight: 0.4} | +| {_ID: 1103808692224, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 2, _DST_ID: 72057594037927936, weight: 0.4} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103808692225, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 2, _DST_ID: 72057594037927937, weight: 1.0} | +| {_ID: 1103808692225, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 2, _DST_ID: 72057594037927937, weight: 1.0} | +--------------------------------------------------------------------------------------------------------------------------------------+ -| {_ID: 1103809740800, _LABEL: created, _SRC_LABEL: person, _DST_LABEL: software, _SRC_ID: 3, _DST_ID: 72057594037927936, weight: 0.2} | +| {_ID: 1103809740800, _LABEL: CREATED, _SRC_LABEL: Person, _DST_LABEL: Software, _SRC_ID: 3, _DST_ID: 72057594037927936, weight: 0.2} | +--------------------------------------------------------------------------------------------------------------------------------------+ ``` @@ -79,8 +79,8 @@ The output includes the relationship's internal ID, label, all properties, and t ### Return Repeated Paths ``` -Match (a:person)-[k*1..2]->(c) -Return k; +MATCH (a:Person)-[k*1..2]->(c) +RETURN k; ``` @@ -88,16 +88,16 @@ Return k; ### Return All Nodes/Rels in Paths ``` -Match (a:person)-[k*1..2]->(c) -Return nodes(k) as nodes, rels(k) as rels; +MATCH (a:Person)-[k*1..2]->(c) +RETURN nodes(k) AS nodes, rels(k) AS rels; ``` ### Return Properties of Node/Rels in Paths ``` -Match (a:person)-[k*1..2]->(c) -Return properties(nodes(k), 'name') as names, properties(rels(k), 'weight') as weights; +MATCH (a:Person)-[k*1..2]->(c) +RETURN properties(nodes(k), 'name') AS names, properties(rels(k), 'weight') AS weights; ``` @@ -106,10 +106,10 @@ Return properties(nodes(k), 'name') as names, properties(rels(k), 'weight') as w Return, OrderBy, Limit combination used for outputting TopK query results ``` -Match (a:person)-[:knows]->(b:person) -Return a.name, b.name -Order By a.name ASC, b.name ASC -Limit 2; +MATCH (a:Person)-[:KNOWS]->(b:Person) +RETURN a.name, b.name +ORDER BY a.name ASC, b.name ASC +LIMIT 2; ``` output: @@ -126,8 +126,8 @@ output: ## Return with Aggregation Output aggregation results ``` -Match (a:person)-[:knows]->(b:person) -Return label(a) as a_label, label(b) as b_label, count(*) as cnt; +MATCH (a:Person)-[:KNOWS]->(b:Person) +RETURN label(a) AS a_label, label(b) AS b_label, count(*) AS cnt; ``` @@ -135,8 +135,8 @@ Return label(a) as a_label, label(b) as b_label, count(*) as cnt; ## Return with Distinct Output non-duplicate results ``` -Match (a) -Return distinct label(a); +MATCH (a) +RETURN DISTINCT label(a); ``` \ No newline at end of file diff --git a/doc/source/cypher_manual/query_clauses/union_clause.md b/doc/source/cypher_manual/query_clauses/union_clause.md index bdf367783..ae1e11122 100644 --- a/doc/source/cypher_manual/query_clauses/union_clause.md +++ b/doc/source/cypher_manual/query_clauses/union_clause.md @@ -23,16 +23,16 @@ Inspired by [Neo4j](https://neo4j.com/docs/cypher-manual/current/subqueries/call Example: ```cypher -MATCH (person:person {id: 123}) +MATCH (person:Person {id: 123}) WITH person CALL (person) { - MATCH (person)-[k:knows]->(friend) + MATCH (person)-[k:KNOWS]->(friend) WHERE k.weight > 1.0 RETURN friend UNION ALL - MATCH (person)-[k:knows]->(friend) + MATCH (person)-[k:KNOWS]->(friend) WHERE k.weight < 1.0 RETURN friend } diff --git a/doc/source/cypher_manual/query_clauses/where_clause.md b/doc/source/cypher_manual/query_clauses/where_clause.md index 037a4ad59..2aa2e7440 100644 --- a/doc/source/cypher_manual/query_clauses/where_clause.md +++ b/doc/source/cypher_manual/query_clauses/where_clause.md @@ -4,11 +4,11 @@ The WHERE clause is used to further filter the results produced by previous quer ## Filter by Properties -In the previous chapter, we introduced how to restrict node and relationship property key-value pairs through expressions like `(a:person {name: 'marko'})`. Here we further supplement how to express the same effect through the WHERE clause. +In the previous chapter, we introduced how to restrict node and relationship property key-value pairs through expressions like `(a:Person {name: 'marko'})`. Here we further supplement how to express the same effect through the WHERE clause. ### Filter by Node Properties ```cypher -MATCH (a:person) +MATCH (a:Person) WHERE a.name = 'marko' OR a.age > 27 RETURN a.name, a.age; ``` @@ -28,7 +28,7 @@ output: ### Filter by Node/Relationship Properties ```cypher -MATCH (a:person)-[b:knows]->(c:person) +MATCH (a:Person)-[b:KNOWS]->(c:Person) WHERE a.name = 'marko' AND b.weight = 1.0 RETURN a.name, b.weight; ``` @@ -44,7 +44,7 @@ output: ### Filter by Correlated Properties ```cypher -MATCH (a:person)-[b:knows]->(c:person) +MATCH (a:Person)-[b:KNOWS]->(c:Person) WHERE a.name <> c.name AND a.age > c.age RETURN a.name, a.age, c.name, c.age; ``` @@ -82,7 +82,7 @@ RETURN a.name; ### Filter Optional Data with NULL ```cypher MATCH (a) -OPTIONAL MATCH (a)-[:knows]->(b) +OPTIONAL MATCH (a)-[:KNOWS]->(b) WHERE b IS NULL RETURN a.name; ``` @@ -92,7 +92,7 @@ RETURN a.name; ### Filter Out Optional Data with IS NOT NULL ```cypher MATCH (a) -OPTIONAL MATCH (a)-[:knows]->(b) +OPTIONAL MATCH (a)-[:KNOWS]->(b) WHERE b IS NOT NULL RETURN a.name; ``` @@ -104,7 +104,7 @@ The WHERE clause can also be used with subqueries to perform more complex filter ### Exists Pattern ```cypher MATCH (a) -WHERE (a)-[:knows]->(b) +WHERE (a)-[:KNOWS]->(b) RETURN a.name; ``` This query returns all `a.name` values that have a `knows` relationship. @@ -112,7 +112,7 @@ This query returns all `a.name` values that have a `knows` relationship. ### Not Exists Pattern ```cypher MATCH (a) -WHERE NOT (a)-[:knows]->(b) +WHERE NOT (a)-[:KNOWS]->(b) RETURN a.name; ``` This query returns all `a.name` values where there are no `knows` relationships, equivalent to the ANTI_JOIN semantics in SQL. diff --git a/doc/source/cypher_manual/query_clauses/with_clause.md b/doc/source/cypher_manual/query_clauses/with_clause.md index 7233791c5..2c43aa82b 100644 --- a/doc/source/cypher_manual/query_clauses/with_clause.md +++ b/doc/source/cypher_manual/query_clauses/with_clause.md @@ -17,25 +17,25 @@ We will introduce these functions in detail in the [Aggregate Function Section]( ### Aggregate by Single Property ``` -Match (a) With label(a) as label, count(a.name) as cnt Return label, cnt; +MATCH (a) WITH label(a) AS label, count(a.name) AS cnt RETURN label, cnt; ``` ### Aggregate by Multiple Properties ``` -Match (a)-[b:knows]->(c) With label(a) as a_label, label(c) as c_label, count(b) as cnt Return a_label, c_label, cnt; +MATCH (a)-[b:KNOWS]->(c) WITH label(a) AS a_label, label(c) AS c_label, count(b) AS cnt RETURN a_label, c_label, cnt; ``` ### Apply Multiple Aggregate Functions ``` -Match (a)-[b:knows]->(c) With label(a) as a_label, label(c) as c_label, count(b) as cnt, sum(b.weight) as weights Return a_label, c_label, cnt, weights; +MATCH (a)-[b:KNOWS]->(c) WITH label(a) AS a_label, label(c) AS c_label, count(b) AS cnt, sum(b.weight) AS weights RETURN a_label, c_label, cnt, weights; ``` ### Filter based on Aggregation Results ``` -Match (a:person) With label(a) as label, count(a.name) as cnt Where cnt > 2 Return label, cnt; +MATCH (a:Person) WITH label(a) AS label, count(a.name) AS cnt WHERE cnt > 2 RETURN label, cnt; ``` @@ -47,10 +47,10 @@ Another common usage of WITH is to further project current results by columns, w ## Project Node Data ``` -Match (a:person {name: 'marko'})-[:knows]->(b:person) -With b -Match (b)-[:created]->(c) -Return c.name; +MATCH (a:Person {name: 'marko'})-[:KNOWS]->(b:Person) +WITH b +MATCH (b)-[:CREATED]->(c) +RETURN c.name; ``` @@ -58,19 +58,19 @@ Return c.name; ## Project Node/Edge Data ``` -Match (a:person {name: 'marko'})-[k:knows]->(b:person) -With b, k -Match (b)-[:created]->(c) -Return k.weight, c.name; +MATCH (a:Person {name: 'marko'})-[k:KNOWS]->(b:Person) +WITH b, k +MATCH (b)-[:CREATED]->(c) +RETURN k.weight, c.name; ``` ## Project Properties ``` -Match (a:person {name: 'marko'})-[:knows]->(b:person) -With a, b.age as b_age -Match (a)-[:created]->(c) -Where b_age > 20 -Return c.name; +MATCH (a:Person {name: 'marko'})-[:KNOWS]->(b:Person) +WITH a, b.age AS b_age +MATCH (a)-[:CREATED]->(c) +WHERE b_age > 20 +RETURN c.name; ``` Project the properties of the b data generated by the first Match, filter the properties through Filter, and finally output all c.name that meet the conditions; \ No newline at end of file diff --git a/doc/source/data_io/export_data.md b/doc/source/data_io/export_data.md index 143bc7d4a..3774d98f9 100644 --- a/doc/source/data_io/export_data.md +++ b/doc/source/data_io/export_data.md @@ -6,7 +6,7 @@ The `COPY TO` command enables direct export of query results to various file for The COPY TO clause can export query results to a CSV file and is used as follows: ```cypher -COPY (MATCH (p:person) RETURN p.*) TO 'person.csv' (header=true); +COPY (MATCH (p:Person) RETURN p.*) TO 'person.csv' (header=true); ``` The CSV file consists of the following fields: @@ -28,13 +28,13 @@ Available parameters are: Another example is shown below. ```cypher -COPY (MATCH (:person)-[e:knows]->(:person) RETURN e) TO 'person_knows_person.csv' (header=true); +COPY (MATCH (:Person)-[e:KNOWS]->(:Person) RETURN e) TO 'person_knows_person.csv' (header=true); ``` This outputs the following results to `person_knows_person.csv`: ```csv e -{"_SRC":"0:0","_DST":"0:1","_SRC_LABEL":"person","_DST_LABEL":"person","_LABEL":"knows","weight":0.5} -{"_SRC":"0:0","_DST":"0:2","_SRC_LABEL":"person","_DST_LABEL":"person","_LABEL":"knows","weight":1.0} +{"_SRC":"0:0","_DST":"0:1","_SRC_LABEL":"Person","_DST_LABEL":"Person","_LABEL":"KNOWS","weight":0.5} +{"_SRC":"0:0","_DST":"0:2","_SRC_LABEL":"Person","_DST_LABEL":"Person","_LABEL":"KNOWS","weight":1.0} ``` ## Copy to JSON @@ -42,8 +42,8 @@ e Since NeuG v0.1.2, JSON export is a built-in feature. You can export query results to JSON or JSONL format: ```cypher -COPY (MATCH (p:person) RETURN p.*) TO 'person.json'; -COPY (MATCH (p:person) RETURN p.*) TO 'person.jsonl'; +COPY (MATCH (p:Person) RETURN p.*) TO 'person.json'; +COPY (MATCH (p:Person) RETURN p.*) TO 'person.jsonl'; ``` The output format is determined by the file extension: diff --git a/doc/source/data_io/import_data.md b/doc/source/data_io/import_data.md index c5b89ccb2..fda29dead 100644 --- a/doc/source/data_io/import_data.md +++ b/doc/source/data_io/import_data.md @@ -77,17 +77,17 @@ LIMIT 5; Create a node table and import data: ```cypher -CREATE NODE TABLE person(id INT64, name STRING, age INT64, PRIMARY KEY(id)); +CREATE NODE TABLE Person(id INT64, name STRING, age INT64, PRIMARY KEY(id)); ``` ```cypher -COPY person FROM "person.csv" (header=true); +COPY Person FROM "person.csv" (header=true); ``` If data is spread across multiple files, use wildcard characters: ```cypher -COPY person FROM "person*.csv" (header=true); +COPY Person FROM "person*.csv" (header=true); ``` > **Note:** The number and order of columns in the CSV file must match the properties defined in the node table exactly. @@ -97,11 +97,11 @@ COPY person FROM "person*.csv" (header=true); Create a relationship table and import data: ```cypher -CREATE REL TABLE knows(FROM person TO person, weight DOUBLE); +CREATE REL TABLE KNOWS(FROM Person TO Person, weight DOUBLE); ``` ```cypher -COPY knows FROM "person_knows_person.csv" (from="person", to="person", header=true); +COPY KNOWS FROM "person_knows_person.csv" (from="Person", to="Person", header=true); ``` > **Note:** NeuG assumes the first two columns are the primary keys of the `FROM` and `TO` nodes. The remaining columns correspond to relationship properties. The `from` and `to` parameters must be specified to identify the endpoint node tables. @@ -124,9 +124,9 @@ COPY User FROM "users.csv" (header=true, auto_detect=false); -- require table t ### Nodes (new label) ```cypher -// File has header: id,name,age +// File has header: id,name,age // id becomes PRIMARY KEY -COPY person FROM "person.csv" (header=true); +COPY Person FROM "person.csv" (header=true); ``` - The **first column** of the source is used as the **primary key** property, If the file column order is wrong for inference, reorder with a `LOAD FROM` subquery (first returned column = primary key), see [Column remapping with load from](#column-remapping-with-load-from) for reference. @@ -140,9 +140,9 @@ COPY person FROM "person.csv" (header=true); // src becomes source column // dst becomes destination column // other columns are edge properties -COPY knows FROM "person_knows_person.csv" ( - from="person", - to="person", +COPY KNOWS FROM "person_knows_person.csv" ( + from="Person", + to="Person", header=true, delimiter="," ); @@ -176,10 +176,10 @@ Since NeuG v0.1.2, JSON/JSONL is a built-in feature. You can use `COPY FROM` to ```cypher // JSON array file — schema auto-detected, // first column becomes primary key -COPY person FROM "person.json"; +COPY Person FROM "person.json"; // JSONL file — same auto-detection -COPY person FROM "person.jsonl"; +COPY Person FROM "person.jsonl"; ``` > **Version Note:** Since version v0.1.2, we made JSON support a built-in functionality, so you do not need to install the JSON extension before using it. For NeuG version < 0.1.2, JSON support was provided via the [JSON Extension](../extensions/load_json) and required `INSTALL json; LOAD json;` before use. @@ -196,7 +196,7 @@ LOAD parquet; ```cypher // Schema auto-detected from Parquet metadata // first column becomes primary key -COPY person FROM "person.parquet"; +COPY Person FROM "person.parquet"; ``` ## Column Remapping with LOAD FROM @@ -217,7 +217,7 @@ age,name,id ``` ```cypher -COPY person FROM ( +COPY Person FROM ( LOAD FROM "person_remap.csv" RETURN id, name, age ); @@ -234,7 +234,7 @@ peter,josh,0.8 ``` ```cypher -COPY knows FROM ( +COPY KNOWS FROM ( LOAD FROM "knows_remap.csv" RETURN src_name AS src, dst_name AS dst, weight ); @@ -245,7 +245,7 @@ COPY knows FROM ( You can filter rows before persisting, avoiding the need to clean the source file: ```cypher -COPY person FROM ( +COPY Person FROM ( LOAD FROM "person.csv" (delim=',') WHERE age >= 18 RETURN * diff --git a/doc/source/extensions/load_parquet.md b/doc/source/extensions/load_parquet.md index 6f1da10d9..daf9bc53e 100644 --- a/doc/source/extensions/load_parquet.md +++ b/doc/source/extensions/load_parquet.md @@ -133,7 +133,7 @@ COPY ( ```cypher COPY ( - MATCH (p:person)-[k:knows]->(p2:person) + MATCH (p:Person)-[k:KNOWS]->(p2:Person) RETURN p.fName, p2.fName, k.since ) TO 'relationships.parquet' (compression='none'); ``` @@ -142,7 +142,7 @@ COPY ( ```cypher COPY ( - MATCH (p:person) + MATCH (p:Person) RETURN p.fName, p.email ) TO 'contacts.parquet' (dictionary_encoding=false); ``` @@ -186,7 +186,7 @@ Export complete edge objects: ```cypher COPY ( - MATCH (p:person)-[k:knows]->(p2:person) + MATCH (p:Person)-[k:KNOWS]->(p2:Person) RETURN k ) TO 'edges.parquet'; ``` diff --git a/doc/source/overview/introduction.md b/doc/source/overview/introduction.md index a2b8ac7d2..53cdc2b3d 100644 --- a/doc/source/overview/introduction.md +++ b/doc/source/overview/introduction.md @@ -28,8 +28,8 @@ conn = db.connect() # Run analytics result = conn.execute(""" - MATCH (a:person)-[:knows]->(b:person)-[:knows]->(c:person), - (a)-[:knows]->(c) + MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person), + (a)-[:KNOWS]->(c) RETURN a.fName, b.fName, c.fName """) diff --git a/doc/source/reference/nodejs_api/connection.md b/doc/source/reference/nodejs_api/connection.md index 8e054853f..df38b8a87 100644 --- a/doc/source/reference/nodejs_api/connection.md +++ b/doc/source/reference/nodejs_api/connection.md @@ -64,7 +64,7 @@ execute(query, accessMode = '', parameters = null) -> QueryResult Execute a cypher query on the database. User could specify multiple queries in a single string, separated by semicolons. The query will be executed in the order they are specified. If any query fails, the whole execution will be rolled back. -If the query is a DDL query, such as `CREATE TABLE`, `DROP TABLE`, etc., the database will be +If the query is a DDL query, such as `CREATE NODE TABLE`, `CREATE REL TABLE`, `DROP TABLE`, etc., the database will be modified accordingly. For the details of the query syntax, please refer to the documentation of cypher manual. @@ -75,7 +75,7 @@ such as `hasNext()` and `getNext()`. If the query is a DDL or DML query, the result will be an empty `QueryResult` object. -Some of the cypher queries could change the state of the database, such as `CREATE TABLE`, `INSERT`, +Some of the cypher queries could change the state of the database, such as `CREATE NODE TABLE`, `INSERT`, `UPDATE`, `DELETE`, etc. Other queries, such as `MATCH(n) RETURN n.id`, will not change the state of the database, but will return the results of the query. @@ -88,18 +88,18 @@ database will be changed accordingly. const { Database } = require('neug'); const db = new Database({ databasePath: '/tmp/test.db', mode: 'w' }); const conn = db.connect(); - conn.execute('CREATE NODE TABLE person(id INT64, name STRING, PRIMARY KEY(id));'); - conn.execute('CREATE REL TABLE knows(FROM person TO person, weight DOUBLE);'); - conn.execute('COPY person FROM "person.csv"'); - conn.execute('COPY knows FROM "knows.csv" (from="person", to="person");'); + conn.execute('CREATE NODE TABLE Person(id INT64, name STRING, PRIMARY KEY(id));'); + conn.execute('CREATE REL TABLE KNOWS(FROM Person TO Person, weight DOUBLE);'); + conn.execute('COPY Person FROM "person.csv"'); + conn.execute('COPY KNOWS FROM "knows.csv" (from="Person", to="Person");'); const res = conn.execute('MATCH(n) RETURN n.id'); for (const row of res) { console.log(row); } - const res2 = conn.execute('MATCH(p:person)-[knows]->(q:person) RETURN p.id, q.id LIMIT 10;'); + const res2 = conn.execute('MATCH(p:Person)-[:KNOWS]->(q:Person) RETURN p.id, q.id LIMIT 10;'); // submitting query with parameters const res3 = conn.execute( - 'MATCH (n:person) WHERE n.id = $id RETURN n.name', 'read', { id: 12345 }); + 'MATCH (n:Person) WHERE n.id = $id RETURN n.name', 'read', { id: 12345 }); ``` diff --git a/doc/source/reference/nodejs_api/database.md b/doc/source/reference/nodejs_api/database.md index be0a4c0aa..7fc1f4c29 100644 --- a/doc/source/reference/nodejs_api/database.md +++ b/doc/source/reference/nodejs_api/database.md @@ -35,12 +35,12 @@ When the database is closed, all the connections to the database will be closed const conn = db.connect(); // Use the connection to interact with the database - conn.execute('CREATE NODE TABLE person(id INT64, name STRING, PRIMARY KEY(id));'); - conn.execute('CREATE REL TABLE knows(FROM person TO person, weight DOUBLE);'); + conn.execute('CREATE NODE TABLE Person(id INT64, name STRING, PRIMARY KEY(id));'); + conn.execute('CREATE REL TABLE KNOWS(FROM Person TO Person, weight DOUBLE);'); // Import data from csv file. - conn.execute('COPY person FROM "person.csv"'); - conn.execute('COPY knows FROM "knows.csv" (from="person", to="person");'); + conn.execute('COPY Person FROM "person.csv"'); + conn.execute('COPY KNOWS FROM "knows.csv" (from="Person", to="Person");'); const res = conn.execute('MATCH(n) RETURN n.id;'); for (const row of res) { diff --git a/doc/source/reference/nodejs_api/index.md b/doc/source/reference/nodejs_api/index.md index 3042cc105..23a47d651 100644 --- a/doc/source/reference/nodejs_api/index.md +++ b/doc/source/reference/nodejs_api/index.md @@ -58,10 +58,10 @@ const { Database } = require('neug'); const db = new Database({ databasePath: '', mode: 'w' }); const conn = db.connect(); -conn.execute('CREATE NODE TABLE person(id INT64, name STRING, age INT32, PRIMARY KEY(id));'); -conn.execute("CREATE (p:person {id: 1, name: 'Alice', age: 30});"); +conn.execute('CREATE NODE TABLE Person(id INT64, name STRING, age INT32, PRIMARY KEY(id));'); +conn.execute("CREATE (p:Person {id: 1, name: 'Alice', age: 30});"); -const result = conn.execute('MATCH (p:person) RETURN p.id, p.name, p.age;'); +const result = conn.execute('MATCH (p:Person) RETURN p.id, p.name, p.age;'); for (const row of result) { console.log(`id=${row[0]}, name=${row[1]}, age=${row[2]}`); } @@ -79,7 +79,7 @@ The `execute` method accepts an optional access mode to hint the query type: ```javascript // Specify access mode for the query const result = conn.execute( - 'MATCH (p:person) RETURN p.name, p.age', + 'MATCH (p:Person) RETURN p.name, p.age', 'read' ); ``` @@ -91,7 +91,7 @@ Supported modes: `'read'`/`'r'`, `'insert'`/`'i'`, `'update'`/`'u'`, `'schema'`/ ```javascript // Safe parameter passing const result = conn.execute( - 'MATCH (p:person) WHERE p.age > $min_age RETURN p.name, p.age', + 'MATCH (p:Person) WHERE p.age > $min_age RETURN p.name, p.age', 'read', { min_age: 25 } ); diff --git a/doc/source/reference/nodejs_api/query_result.md b/doc/source/reference/nodejs_api/query_result.md index 6077fba70..2b1b03f58 100644 --- a/doc/source/reference/nodejs_api/query_result.md +++ b/doc/source/reference/nodejs_api/query_result.md @@ -185,7 +185,7 @@ Close the query result and release resources. Makes QueryResult iterable with `for...of` loops. Each iteration yields a row as an array of values. ```javascript -const result = conn.execute('MATCH (p:person) RETURN p.name, p.age'); +const result = conn.execute('MATCH (p:Person) RETURN p.name, p.age'); for (const row of result) { console.log(`${row[0]}: ${row[1]}`); } diff --git a/doc/source/reference/python_api/connection.md b/doc/source/reference/python_api/connection.md index e241e40b0..02d362af1 100644 --- a/doc/source/reference/python_api/connection.md +++ b/doc/source/reference/python_api/connection.md @@ -67,7 +67,7 @@ def execute(query: str, Execute a cypher query on the database. User could specify multiple queries in a single string, separated by semicolons. The query will be executed in the order they are specified. If any query fails, the whole execution will be rolled back. -If the query is a DDL query, such as `CREATE TABLE`, `DROP TABLE`, etc., the database will be +If the query is a DDL query, such as `CREATE NODE TABLE`, `CREATE REL TABLE`, `DROP TABLE`, etc., the database will be modified accordingly. For the details of the query syntax, please refer to the documentation of cypher manual. @@ -78,7 +78,7 @@ such as `__iter__` and `__next__`. If the query is a DDL or DML query, the result will be an empty `QueryResult` object. -Some of the cypher queries could change the state of the database, such as `CREATE TABLE`, `INSERT`, +Some of the cypher queries could change the state of the database, such as `CREATE NODE TABLE`, `INSERT`, `UPDATE`, `DELETE`, etc. Other queries, such as `MATCH(n) RETURN n.id`, will not change the state of the database, but will return the results of the query. @@ -91,17 +91,17 @@ database will be changed accordingly. >>> from neug import Database >>> db = Database("/tmp/test.db", mode="w") >>> conn = db.connect() - >>> res = conn.execute('CREATE TABLE person(id INT64, name STRING);') - >>> res = conn.execute('CREATE TABLE knows(FROM person TO person, weight DOUBLE);') - >>> res = conn.execute('COPY person FROM "person.csv"') - >>> res = conn.execute('COPY knows FROM "knows.csv" (from="person", to="person");') + >>> res = conn.execute('CREATE NODE TABLE Person(id INT64, name STRING);') + >>> res = conn.execute('CREATE REL TABLE KNOWS(FROM Person TO Person, weight DOUBLE);') + >>> res = conn.execute('COPY Person FROM "person.csv"') + >>> res = conn.execute('COPY KNOWS FROM "knows.csv" (from="Person", to="Person");') >>> res = conn.execute('MATCH(n) RETURN n.id') >>> for record in res: >>> print(record) - >>> res = conn.execute('MATCH(p:person)-[knows]->(q:person) RETURN p.id, q.id LIMIT 10;') + >>> res = conn.execute('MATCH(p:Person)-[:KNOWS]->(q:Person) RETURN p.id, q.id LIMIT 10;') >>> # submitting query with parameters >>> res = conn.execute( - 'MATCH (n:person) WHERE n.id = $id RETURN n.name', access_mode='r', parameters={'id': 12345}) + 'MATCH (n:Person) WHERE n.id = $id RETURN n.name', access_mode='r', parameters={'id': 12345}) ``` diff --git a/doc/source/reference/python_api/database.md b/doc/source/reference/python_api/database.md index 50c18840c..371d5d6e6 100644 --- a/doc/source/reference/python_api/database.md +++ b/doc/source/reference/python_api/database.md @@ -35,12 +35,12 @@ When the database is closed, all the connections to the database will be closed >>> conn = db.connect() >>> # Use the connection to interact with the database - >>> conn.execute('CREATE TABLE person(id INT64, name STRING);') - >>> conn.execute('CREATE TABLE knows(FROM person TO person, weight DOUBLE);') + >>> conn.execute('CREATE NODE TABLE Person(id INT64, name STRING);') + >>> conn.execute('CREATE REL TABLE KNOWS(FROM Person TO Person, weight DOUBLE);') >>> # Import data from csv file. - >>> conn.execute('COPY person FROM "person.csv"') - >>> conn.execute('COPY knows FROM "knows.csv" (from="person", to="person");') + >>> conn.execute('COPY Person FROM "person.csv"') + >>> conn.execute('COPY KNOWS FROM "knows.csv" (from="Person", to="Person");') >>> res = conn.execute('MATCH(n) return n.id;) >>> for record in res: diff --git a/doc/source/transaction/transaction.md b/doc/source/transaction/transaction.md index 3fab17925..a83573414 100644 --- a/doc/source/transaction/transaction.md +++ b/doc/source/transaction/transaction.md @@ -298,7 +298,7 @@ conn = db.connect() # 1. Create schema and checkpoint conn.execute("CREATE NODE TABLE Person(id INT64, name STRING, PRIMARY KEY(id))") conn.execute("CREATE NODE TABLE Company(id INT64, name STRING, PRIMARY KEY(id))") -conn.execute("CREATE REL TABLE WorksAt(FROM Person TO Company)") +conn.execute("CREATE REL TABLE WORKS_AT(FROM Person TO Company)") conn.execute("CHECKPOINT") # 2. Load data in batches with periodic checkpoints @@ -307,11 +307,11 @@ conn.execute("COPY Person FROM 'employees_batch2.csv'") conn.execute("CHECKPOINT") conn.execute("COPY Company FROM 'companies.csv'") -conn.execute("COPY WorksAt FROM 'employment.csv'") +conn.execute("COPY WORKS_AT FROM 'employment.csv'") conn.execute("CHECKPOINT") # 3. Run analytical queries (read-only, high performance) -result = conn.execute("MATCH (p:Person)-[:WorksAt]->(c:Company) RETURN c.name, count(p)") +result = conn.execute("MATCH (p:Person)-[:WORKS_AT]->(c:Company) RETURN c.name, count(p)") conn.close() db.close() diff --git a/doc/source/tutorials/data-pipeline.md b/doc/source/tutorials/data-pipeline.md index a55109f4f..874ab54fd 100644 --- a/doc/source/tutorials/data-pipeline.md +++ b/doc/source/tutorials/data-pipeline.md @@ -78,7 +78,7 @@ Traditionally, importing data into a graph database requires you to first define ```python conn.execute(''' - COPY person FROM ( + COPY Person FROM ( LOAD FROM "https://graphscope.oss-cn-beijing.aliyuncs.com/neug/vPerson.parquet" RETURN * ) @@ -94,7 +94,7 @@ NeuG automatically: Verify: ```python -result = conn.execute("MATCH (p:person) RETURN count(p)") +result = conn.execute("MATCH (p:Person) RETURN count(p)") print(list(result)) # [[8]] ``` @@ -108,17 +108,17 @@ Edge tables work the same way, with one addition — you need to specify which n ```python conn.execute(''' - COPY meets FROM ( + COPY MEETS FROM ( LOAD FROM "https://graphscope.oss-cn-beijing.aliyuncs.com/neug/eMeets.parquet" RETURN * - ) (from="person", to="person") + ) (from="Person", to="Person") ''') ``` Verify: ```python -result = conn.execute("MATCH ()-[e:meets]->() RETURN count(e)") +result = conn.execute("MATCH ()-[e:MEETS]->() RETURN count(e)") print(list(result)) # [[7]] ``` @@ -130,7 +130,7 @@ Now you have a graph. Query it: ```python result = conn.execute(''' - MATCH (a:person)-[m:meets]->(b:person) + MATCH (a:Person)-[m:MEETS]->(b:Person) WHERE a.age > 30 RETURN a.fName, b.fName, m.location ''') @@ -147,7 +147,7 @@ Export filtered graph query results: ```python conn.execute(''' COPY ( - MATCH (a:person)-[m:meets]->(b:person) + MATCH (a:Person)-[m:MEETS]->(b:Person) WHERE a.age < 35 RETURN a.fName AS name, a.age AS age, b.fName AS met_person, m.location ) TO '/tmp/young_social.parquet' @@ -163,7 +163,7 @@ In production, you can write directly to OSS or S3 — no local disk involved: ```python conn.execute(''' COPY ( - MATCH (a:person)-[m:meets]->(b:person) + MATCH (a:Person)-[m:MEETS]->(b:Person) WHERE a.age < 35 RETURN a.fName AS name, a.age AS age, b.fName AS met_person, m.location ) TO "oss://my-bucket/output/young_social.parquet" ( @@ -183,7 +183,7 @@ If you want to continue analysis in Python (pandas, polars, DuckDB), convert que ```python result = conn.execute(''' - MATCH (p:person) + MATCH (p:Person) RETURN p.ID, p.fName, p.age ORDER BY p.ID ''') @@ -278,7 +278,7 @@ print("Preview:", list(result)) # Import nodes (no DDL) conn.execute(''' - COPY person FROM ( + COPY Person FROM ( LOAD FROM "https://graphscope.oss-cn-beijing.aliyuncs.com/neug/vPerson.parquet" RETURN * ) @@ -286,15 +286,15 @@ conn.execute(''' # Import edges (no DDL) conn.execute(''' - COPY meets FROM ( + COPY MEETS FROM ( LOAD FROM "https://graphscope.oss-cn-beijing.aliyuncs.com/neug/eMeets.parquet" RETURN * - ) (from="person", to="person") + ) (from="Person", to="Person") ''') # Query result = conn.execute(''' - MATCH (a:person)-[m:meets]->(b:person) + MATCH (a:Person)-[m:MEETS]->(b:Person) WHERE a.age > 30 RETURN a.fName, b.fName, m.location ''') @@ -304,7 +304,7 @@ print("Query results:", list(result)) out = os.path.join(tempfile.mkdtemp(), "young_social.parquet") conn.execute(f''' COPY ( - MATCH (a:person)-[m:meets]->(b:person) + MATCH (a:Person)-[m:MEETS]->(b:Person) WHERE a.age < 35 RETURN a.fName AS name, a.age AS age, b.fName AS met_person, m.location ) TO '{out}' @@ -312,7 +312,7 @@ conn.execute(f''' print(f"Exported to: {out} ({os.path.getsize(out)} bytes)") # to_arrow() -result = conn.execute("MATCH (p:person) RETURN p.ID, p.fName, p.age ORDER BY p.ID") +result = conn.execute("MATCH (p:Person) RETURN p.ID, p.fName, p.age ORDER BY p.ID") table = result.to_arrow() print(f"Arrow table: {table.num_rows} rows, columns: {table.column_names}") diff --git a/doc/source/tutorials/tinysnb_tutorial.md b/doc/source/tutorials/tinysnb_tutorial.md index d1896c266..ae0eb28e2 100644 --- a/doc/source/tutorials/tinysnb_tutorial.md +++ b/doc/source/tutorials/tinysnb_tutorial.md @@ -50,15 +50,15 @@ total_nodes = result[0][0] print(f"Total nodes in the graph: {total_nodes}") # Count nodes by type -result = list(conn.execute("MATCH (p:person) RETURN count(p) as people_count")) +result = list(conn.execute("MATCH (p:Person) RETURN count(p) as people_count")) people_count = result[0][0] print(f"Number of people: {people_count}") -result = list(conn.execute("MATCH (o:organisation) RETURN count(o) as org_count")) +result = list(conn.execute("MATCH (o:Organisation) RETURN count(o) as org_count")) org_count = result[0][0] print(f"Number of organizations: {org_count}") -result = list(conn.execute("MATCH (m:movies) RETURN count(m) as movie_count")) +result = list(conn.execute("MATCH (m:Movies) RETURN count(m) as movie_count")) movie_count = result[0][0] print(f"Number of movies: {movie_count}") ``` @@ -73,7 +73,7 @@ Let's start by exploring the people in our social network: # Get all people with their basic information print("=== People in our social network ===") result = conn.execute(""" - MATCH (p:person) + MATCH (p:Person) RETURN p.fName, p.age, p.isStudent, p.isWorker ORDER BY p.age """) @@ -95,7 +95,7 @@ for record in result: # Find all students print("\n=== Students in our network ===") result = conn.execute(""" - MATCH (p:person) + MATCH (p:Person) WHERE p.isStudent = true RETURN p.fName, p.age ORDER BY p.age @@ -107,7 +107,7 @@ for record in result: # Find working adults (workers who are not students) print("\n=== Working adults (non-students) ===") result = conn.execute(""" - MATCH (p:person) + MATCH (p:Person) WHERE p.isWorker = true AND p.isStudent = false RETURN p.fName, p.age ORDER BY p.age DESC @@ -119,7 +119,7 @@ for record in result: # Find people in their thirties print("\n=== People in their thirties ===") result = conn.execute(""" - MATCH (p:person) + MATCH (p:Person) WHERE p.age >= 30 AND p.age < 40 RETURN p.fName, p.age ORDER BY p.age @@ -139,7 +139,7 @@ Now let's explore the relationships between people - this is where graph databas # Explore the "knows" relationships print("=== Social connections (who knows whom) ===") result = conn.execute(""" - MATCH (p1:person)-[k:knows]->(p2:person) + MATCH (p1:Person)-[k:KNOWS]->(p2:Person) RETURN p1.fName, p2.fName, k.date ORDER BY p1.fName, p2.fName """) @@ -154,7 +154,7 @@ for record in result: # Who has the most connections? print("\n=== Most connected people ===") result = conn.execute(""" - MATCH (p:person)-[k:knows]->(friend:person) + MATCH (p:Person)-[k:KNOWS]->(friend:Person) RETURN p.fName, count(friend) as friend_count ORDER BY friend_count DESC LIMIT 5 @@ -166,7 +166,7 @@ for record in result: # Who is known by the most people? print("\n=== Most popular people (known by others) ===") result = conn.execute(""" - MATCH (p:person)<-[k:knows]-(friend:person) + MATCH (p:Person)<-[k:KNOWS]-(friend:Person) RETURN p.fName, count(friend) as known_by_count ORDER BY known_by_count DESC LIMIT 5 @@ -181,8 +181,8 @@ for record in result: ```python print("\n=== Mutual friendships ===") result = conn.execute(""" - MATCH (p1:person)-[k1:knows]->(p2:person), - (p2:person)-[k2:knows]->(p1:person) + MATCH (p1:Person)-[k1:KNOWS]->(p2:Person), + (p2:Person)-[k2:KNOWS]->(p1:Person) WHERE p1.id < p2.id // Avoid duplicates RETURN p1.fName, p2.fName ORDER BY p1.fName @@ -200,7 +200,7 @@ for record in result: # Who studies where? print("=== Academic affiliations ===") result = conn.execute(""" - MATCH (p:person)-[s:studyAt]->(o:organisation) + MATCH (p:Person)-[s:STUDY_AT]->(o:Organisation) RETURN p.fName, o.name, s.year ORDER BY s.year DESC """) @@ -211,7 +211,7 @@ for record in result: # Which organizations have the most students? print("\n=== Most popular educational institutions ===") result = conn.execute(""" - MATCH (p:person)-[s:studyAt]->(o:organisation) + MATCH (p:Person)-[s:STUDY_AT]->(o:Organisation) RETURN o.name, count(p) as student_count ORDER BY student_count DESC """) @@ -226,7 +226,7 @@ for record in result: # Who works where? print("\n=== Professional affiliations ===") result = conn.execute(""" - MATCH (p:person)-[w:workAt]->(o:organisation) + MATCH (p:Person)-[w:WORK_AT]->(o:Organisation) RETURN p.fName, o.name, w.year, w.rating ORDER BY w.year DESC """) @@ -244,9 +244,9 @@ for record in result: # Find friends of friends (2-degree connections) print("=== Friends of friends (2-degree connections) ===") result = conn.execute(""" - MATCH (p1:person)-[:knows]->(mutual:person)-[:knows]->(p2:person) + MATCH (p1:Person)-[:KNOWS]->(mutual:Person)-[:KNOWS]->(p2:Person) WHERE p1.id <> p2.id // Different people - AND NOT (p1)-[:knows]-(p2) // Not direct friends + AND NOT (p1)-[:KNOWS]-(p2) // Not direct friends RETURN p1.fName, p2.fName, mutual.fName ORDER BY p1.fName """) @@ -261,7 +261,7 @@ for record in result: # Find people who work at the same organization print("\n=== Colleagues (people working at the same organization) ===") result = conn.execute(""" - MATCH (p1:person)-[:workAt]->(o:organisation)<-[:workAt]-(p2:person) + MATCH (p1:Person)-[:WORK_AT]->(o:Organisation)<-[:WORK_AT]-(p2:Person) WHERE p1.id < p2.id // Avoid duplicates RETURN p1.fName, p2.fName, o.name ORDER BY o.name @@ -273,7 +273,7 @@ for record in result: # Find people who studied at the same organization print("\n=== Alumni/Classmates (people who studied at the same institution) ===") result = conn.execute(""" - MATCH (p1:person)-[s1:studyAt]->(o:organisation)<-[s2:studyAt]-(p2:person) + MATCH (p1:Person)-[s1:STUDY_AT]->(o:Organisation)<-[s2:STUDY_AT]-(p2:Person) WHERE p1.id < p2.id RETURN p1.fName, p2.fName, o.name, s1.year, s2.year ORDER BY o.name @@ -296,10 +296,10 @@ for record in result: print("=== Network Statistics ===") # Total possible connections vs actual connections -result = list(conn.execute("MATCH (p:person) RETURN count(p) as person_count")) +result = list(conn.execute("MATCH (p:Person) RETURN count(p) as person_count")) person_count = result[0][0] -result = list(conn.execute("MATCH ()-[k:knows]->() RETURN count(k) as connections")) +result = list(conn.execute("MATCH ()-[k:KNOWS]->() RETURN count(k) as connections")) actual_connections = result[0][0] max_possible = person_count * (person_count - 1) # Directed graph @@ -317,9 +317,9 @@ print(f"Network density: {density:.2f}%") # Find the most connected individuals (network hubs) print("\n=== Network Hubs (most connected individuals) ===") result = conn.execute(""" - MATCH (p:person) - OPTIONAL MATCH (p)-[out:knows]->() - OPTIONAL MATCH (p)<-[i:knows]-() + MATCH (p:Person) + OPTIONAL MATCH (p)-[out:KNOWS]->() + OPTIONAL MATCH (p)<-[i:KNOWS]-() RETURN p.fName, count(DISTINCT out) as outgoing, count(DISTINCT i) as incoming, @@ -338,7 +338,7 @@ for record in result: # Analyze social connections by age groups print("\n=== Social connections across age groups ===") result = conn.execute(""" - MATCH (p1:person)-[:knows]->(p2:person) + MATCH (p1:Person)-[:KNOWS]->(p2:Person) WITH p1, p2, CASE WHEN p1.age < 25 THEN "Young (< 25)" diff --git a/tools/python_bind/tests/test_data_io_docs.py b/tools/python_bind/tests/test_data_io_docs.py index f24183b91..896798b5d 100644 --- a/tools/python_bind/tests/test_data_io_docs.py +++ b/tools/python_bind/tests/test_data_io_docs.py @@ -25,7 +25,7 @@ - import_data.md (COPY FROM) - export_data.md (COPY TO) -The tests use the modern_graph dataset (person, knows, software) +The tests use the modern_graph dataset (Person, KNOWS, Software) which matches the examples in the documentation. """ @@ -235,23 +235,23 @@ def setup(self, tmp_path): shutil.rmtree(self.db_dir, ignore_errors=True) def _create_modern_graph_schema(self): - """Create the modern_graph schema (person + knows).""" + """Create the modern_graph schema (Person + KNOWS).""" self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) self.conn.execute( - "CREATE REL TABLE knows(" "FROM person TO person, weight DOUBLE);" + "CREATE REL TABLE KNOWS(" "FROM Person TO Person, weight DOUBLE);" ) def _load_modern_graph_data(self): - """Load person and knows data from modern_graph CSVs.""" + """Load Person and KNOWS data from modern_graph CSVs.""" person_csv = os.path.join(self.data_path, "person.csv") knows_csv = os.path.join(self.data_path, "person_knows_person.csv") - self.conn.execute(f'COPY person FROM "{person_csv}" (header=true);') + self.conn.execute(f'COPY Person FROM "{person_csv}" (header=true);') self.conn.execute( - f'COPY knows FROM "{knows_csv}" ' - f'(from="person", to="person", header=true);' + f'COPY KNOWS FROM "{knows_csv}" ' + f'(from="Person", to="Person", header=true);' ) def test_quick_start_complete_workflow(self): @@ -301,44 +301,44 @@ def test_quick_start_complete_workflow(self): assert len(records) == 3 def test_copy_from_node_table(self): - """import_data.md: COPY person FROM 'person.csv' (header=true).""" + """import_data.md: COPY Person FROM 'person.csv' (header=true).""" self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) csv_path = os.path.join(self.data_path, "person.csv") - self.conn.execute(f'COPY person FROM "{csv_path}" (header=true);') - res = self.conn.execute("MATCH (p:person) RETURN count(p);") + self.conn.execute(f'COPY Person FROM "{csv_path}" (header=true);') + res = self.conn.execute("MATCH (p:Person) RETURN count(p);") assert list(res)[0][0] == 4 def test_copy_from_wildcard(self): - """import_data.md: COPY person FROM 'person*.csv' (header=true).""" + """import_data.md: COPY Person FROM 'person*.csv' (header=true).""" self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) # modern_graph has person.part1.csv and person.part2.csv part_pattern = os.path.join(self.data_path, "test_data/person.part*.csv") - self.conn.execute(f'COPY person FROM "{part_pattern}" (header=true);') - res = self.conn.execute("MATCH (p:person) RETURN count(p);") + self.conn.execute(f'COPY Person FROM "{part_pattern}" (header=true);') + res = self.conn.execute("MATCH (p:Person) RETURN count(p);") assert list(res)[0][0] == 4 def test_copy_from_relationship_table(self): - """import_data.md: COPY knows FROM ... (from='person', to='person', + """import_data.md: COPY KNOWS FROM ... (from='Person', to='Person', header=true).""" self._create_modern_graph_schema() person_csv = os.path.join(self.data_path, "person.csv") knows_csv = os.path.join(self.data_path, "person_knows_person.csv") - self.conn.execute(f'COPY person FROM "{person_csv}" (header=true);') + self.conn.execute(f'COPY Person FROM "{person_csv}" (header=true);') self.conn.execute( - f'COPY knows FROM "{knows_csv}" ' - f'(from="person", to="person", header=true);' + f'COPY KNOWS FROM "{knows_csv}" ' + f'(from="Person", to="Person", header=true);' ) - res = self.conn.execute("MATCH ()-[k:knows]->() RETURN count(k);") + res = self.conn.execute("MATCH ()-[k:KNOWS]->() RETURN count(k);") assert list(res)[0][0] == 2 def test_copy_from_with_column_remapping(self): - """import_data.md: COPY person FROM (LOAD FROM ... RETURN id, name, age). + """import_data.md: COPY Person FROM (LOAD FROM ... RETURN id, name, age). Demonstrates column reordering via LOAD FROM subquery. The CSV has columns in a different order (age, name, id) from the @@ -351,24 +351,24 @@ def test_copy_from_with_column_remapping(self): ) self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) # Use LOAD FROM subquery to reorder columns to match table schema self.conn.execute( f""" - COPY person FROM ( + COPY Person FROM ( LOAD FROM "{remap_csv}" (header=true, delimiter=",") RETURN id, name, age ) """ ) - res = self.conn.execute("MATCH (p:person) RETURN count(p);") + res = self.conn.execute("MATCH (p:Person) RETURN count(p);") assert list(res)[0][0] == 4 def test_copy_from_with_filtering(self): - """import_data.md: COPY person FROM (LOAD FROM ... WHERE age >= 18 + """import_data.md: COPY Person FROM (LOAD FROM ... WHERE age >= 18 RETURN *).""" csv_path = self.tmp_path / "person_filter.csv" csv_path.write_text( @@ -376,19 +376,19 @@ def test_copy_from_with_filtering(self): ) self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) self.conn.execute( f""" - COPY person FROM ( + COPY Person FROM ( LOAD FROM "{csv_path}" (header=true) WHERE age >= 18 RETURN * ) """ ) - res = self.conn.execute("MATCH (p:person) RETURN p.name, p.age ORDER BY p.age;") + res = self.conn.execute("MATCH (p:Person) RETURN p.name, p.age ORDER BY p.age;") records = list(res) # Only Alice (30) and Carol (28) should be imported assert len(records) == 2 @@ -396,16 +396,16 @@ def test_copy_from_with_filtering(self): assert r[1] >= 18 def test_copy_from_parallel(self): - """import_data.md: COPY User FROM ... (header=true, parallel=true).""" + """import_data.md: COPY Person FROM ... (header=true, parallel=true).""" self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) csv_path = os.path.join(self.data_path, "person.csv") self.conn.execute( - f'COPY person FROM "{csv_path}" (header=true, parallel=true);' + f'COPY Person FROM "{csv_path}" (header=true, parallel=true);' ) - res = self.conn.execute("MATCH (p:person) RETURN count(p);") + res = self.conn.execute("MATCH (p:Person) RETURN count(p);") assert list(res)[0][0] == 4 def test_copy_from_batch_read(self): @@ -416,66 +416,66 @@ def test_copy_from_batch_read(self): downstream computation. """ self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) csv_path = os.path.join(self.data_path, "person.csv") self.conn.execute( f""" - COPY person FROM "{csv_path}" ( + COPY Person FROM "{csv_path}" ( header = true, batch_read = true, batch_size = 2097152 ); """ ) - res = self.conn.execute("MATCH (p:person) RETURN count(p);") + res = self.conn.execute("MATCH (p:Person) RETURN count(p);") assert list(res)[0][0] == 4 def test_import_order_nodes_before_edges(self): """import_data.md: always import nodes before relationships.""" self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) self.conn.execute( - "CREATE NODE TABLE software(" + "CREATE NODE TABLE Software(" "id INT64, name STRING, lang STRING, PRIMARY KEY(id));" ) self.conn.execute( - "CREATE REL TABLE knows(" "FROM person TO person, weight DOUBLE);" + "CREATE REL TABLE KNOWS(" "FROM Person TO Person, weight DOUBLE);" ) self.conn.execute( - "CREATE REL TABLE created(" - "FROM person TO software, weight DOUBLE, since INT64);" + "CREATE REL TABLE CREATED(" + "FROM Person TO Software, weight DOUBLE, since INT64);" ) # Import all nodes first person_csv = os.path.join(self.data_path, "person.csv") software_csv = os.path.join(self.data_path, "software.csv") - self.conn.execute(f'COPY person FROM "{person_csv}" (header=true);') - self.conn.execute(f'COPY software FROM "{software_csv}" (header=true);') + self.conn.execute(f'COPY Person FROM "{person_csv}" (header=true);') + self.conn.execute(f'COPY Software FROM "{software_csv}" (header=true);') # Then import edges knows_csv = os.path.join(self.data_path, "person_knows_person.csv") created_csv = os.path.join(self.data_path, "person_created_software.csv") self.conn.execute( - f'COPY knows FROM "{knows_csv}" ' - f'(from="person", to="person", header=true);' + f'COPY KNOWS FROM "{knows_csv}" ' + f'(from="Person", to="Person", header=true);' ) self.conn.execute( - f'COPY created FROM "{created_csv}" ' - f'(from="person", to="software", header=true);' + f'COPY CREATED FROM "{created_csv}" ' + f'(from="Person", to="Software", header=true);' ) # Verify - res = self.conn.execute("MATCH (p:person) RETURN count(p);") + res = self.conn.execute("MATCH (p:Person) RETURN count(p);") assert list(res)[0][0] == 4 - res = self.conn.execute("MATCH (s:software) RETURN count(s);") + res = self.conn.execute("MATCH (s:Software) RETURN count(s);") assert list(res)[0][0] == 2 - res = self.conn.execute("MATCH ()-[k:knows]->() RETURN count(k);") + res = self.conn.execute("MATCH ()-[k:KNOWS]->() RETURN count(k);") assert list(res)[0][0] == 2 - res = self.conn.execute("MATCH ()-[c:created]->() RETURN count(c);") + res = self.conn.execute("MATCH ()-[c:CREATED]->() RETURN count(c);") assert list(res)[0][0] == 4 @@ -501,18 +501,18 @@ def setup(self, tmp_path): # Load modern_graph data self.conn.execute( - "CREATE NODE TABLE person(" + "CREATE NODE TABLE Person(" "id INT64, name STRING, age INT64, PRIMARY KEY(id));" ) self.conn.execute( - "CREATE REL TABLE knows(" "FROM person TO person, weight DOUBLE);" + "CREATE REL TABLE KNOWS(" "FROM Person TO Person, weight DOUBLE);" ) person_csv = os.path.join(self.data_path, "person.csv") knows_csv = os.path.join(self.data_path, "person_knows_person.csv") - self.conn.execute(f'COPY person FROM "{person_csv}" (header=true);') + self.conn.execute(f'COPY Person FROM "{person_csv}" (header=true);') self.conn.execute( - f'COPY knows FROM "{knows_csv}" ' - f'(from="person", to="person", header=true);' + f'COPY KNOWS FROM "{knows_csv}" ' + f'(from="Person", to="Person", header=true);' ) yield self.conn.close() @@ -520,11 +520,11 @@ def setup(self, tmp_path): shutil.rmtree(self.db_dir, ignore_errors=True) def test_copy_to_csv_nodes(self): - """export_data.md: COPY (MATCH (p:person) RETURN p.*) TO + """export_data.md: COPY (MATCH (p:Person) RETURN p.*) TO 'person.csv' (header=true).""" out_path = self.tmp_path / "person_export.csv" self.conn.execute( - f"COPY (MATCH (p:person) RETURN p.*) " f"TO '{out_path}' (header=true);" + f"COPY (MATCH (p:Person) RETURN p.*) " f"TO '{out_path}' (header=true);" ) assert out_path.exists() content = out_path.read_text() @@ -536,11 +536,11 @@ def test_copy_to_csv_nodes(self): assert "id" in header.lower() or "p.id" in header.lower() def test_copy_to_csv_edges(self): - """export_data.md: COPY (MATCH (:person)-[e:knows]->(:person) + """export_data.md: COPY (MATCH (:Person)-[e:KNOWS]->(:Person) RETURN e) TO 'knows.csv' (header=true).""" out_path = self.tmp_path / "knows_export.csv" self.conn.execute( - f"COPY (MATCH (:person)-[e:knows]->(:person) RETURN e) " + f"COPY (MATCH (:Person)-[e:KNOWS]->(:Person) RETURN e) " f"TO '{out_path}' (header=true);" ) assert out_path.exists() @@ -553,7 +553,7 @@ def test_copy_to_csv_with_delimiter(self): """export_data.md: DELIMITER option.""" out_path = self.tmp_path / "person_comma.csv" self.conn.execute( - f"COPY (MATCH (p:person) RETURN p.id, p.name, p.age) " + f"COPY (MATCH (p:Person) RETURN p.id, p.name, p.age) " f"TO '{out_path}' (header=true, delimiter=',');" ) assert out_path.exists() @@ -567,7 +567,7 @@ def test_copy_to_csv_no_header(self): """export_data.md: HEADER=false (default).""" out_path = self.tmp_path / "person_no_header.csv" self.conn.execute( - f"COPY (MATCH (p:person) RETURN p.id, p.name) " + f"COPY (MATCH (p:Person) RETURN p.id, p.name) " f"TO '{out_path}' (HEADER = false);" ) assert out_path.exists() diff --git a/tools/python_bind/tests/test_merge.py b/tools/python_bind/tests/test_merge.py index 0692648f7..082352deb 100644 --- a/tools/python_bind/tests/test_merge.py +++ b/tools/python_bind/tests/test_merge.py @@ -45,7 +45,7 @@ def _open_merge_database(db_dir: str) -> tuple[Database, object]: conn = db.connect() conn.execute("CREATE NODE TABLE User(name STRING, age INT64, PRIMARY KEY(name));") conn.execute("CREATE NODE TABLE User2(name STRING, age INT64, PRIMARY KEY(name));") - conn.execute("CREATE REL TABLE follows(FROM User TO User, date INT64);") + conn.execute("CREATE REL TABLE FOLLOWS(FROM User TO User, date INT64);") return db, conn @@ -59,7 +59,7 @@ def _seed_follows_adam_marko_2012(conn) -> None: """follows.csv row: Adam -> marko, date 2012.""" conn.execute( "MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'marko'}) " - "CREATE (u1)-[:follows {date: 2012}]->(u2);" + "CREATE (u1)-[:FOLLOWS {date: 2012}]->(u2);" ) @@ -105,12 +105,12 @@ def test_merge_edge_match_existing_adam_marko(): rows = list( conn.execute( "MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'marko'}) " - "MERGE (u1)-[e:follows {date: 2012}]->(u2) " + "MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) " "RETURN u1.name, e.date, u2.name;" ) ) assert rows == [["Adam", 2012, "marko"]] - ecnt = list(conn.execute("MATCH ()-[e:follows]->() RETURN count(e);"))[0][0] + ecnt = list(conn.execute("MATCH ()-[e:FOLLOWS]->() RETURN count(e);"))[0][0] assert ecnt == 1 finally: conn.close() @@ -128,12 +128,12 @@ def test_merge_edge_create_adam_bob(): rows = list( conn.execute( "MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'Bob'}) " - "MERGE (u1)-[e:follows {date: 2012}]->(u2) " + "MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) " "RETURN u1.name, e.date, u2.name;" ) ) assert rows == [["Adam", 2012, "Bob"]] - ecnt = list(conn.execute("MATCH ()-[e:follows]->() RETURN count(e);"))[0][0] + ecnt = list(conn.execute("MATCH ()-[e:FOLLOWS]->() RETURN count(e);"))[0][0] assert ecnt == 2 finally: conn.close() @@ -154,18 +154,18 @@ def test_merge_edge_all_pairs_where_id_less_than(): conn.execute("CREATE (:User {name: 'Bob', age: 40});") conn.execute( "MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'marko'}) " - "CREATE (u1)-[e:follows {date: 2012}]->(u2);" + "CREATE (u1)-[e:FOLLOWS {date: 2012}]->(u2);" ) conn.execute( "MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'Bob'}) " - "CREATE (u1)-[:follows {date: 2012}]->(u2);" + "CREATE (u1)-[:FOLLOWS {date: 2012}]->(u2);" ) rows = sorted( list( conn.execute( "MATCH (u1:User), (u2:User) " "WHERE id(u1) < id(u2) " - "MERGE (u1)-[e:follows {date: 2012}]->(u2) " + "MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) " "RETURN u1.name, e.date, u2.name;" ) ), @@ -180,7 +180,7 @@ def test_merge_edge_all_pairs_where_id_less_than(): key=lambda r: (r[0], r[2]), ) assert rows == expected - ecnt = list(conn.execute("MATCH ()-[e:follows]->() RETURN count(e);"))[0][0] + ecnt = list(conn.execute("MATCH ()-[e:FOLLOWS]->() RETURN count(e);"))[0][0] assert ecnt == 3 finally: conn.close() @@ -197,7 +197,7 @@ def test_merge_edge_on_match_set_date(): rows = list( conn.execute( "MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'marko'}) " - "MERGE (u1)-[e:follows {date: 2012}]->(u2) " + "MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) " "ON MATCH SET e.date = 9000 " "RETURN e.date;" ) @@ -205,13 +205,13 @@ def test_merge_edge_on_match_set_date(): assert rows == [[9000]] stored = list( conn.execute( - "MATCH (u1:User {name: 'Adam'})-[e:follows]->" + "MATCH (u1:User {name: 'Adam'})-[e:FOLLOWS]->" "(u2:User {name: 'marko'}) RETURN e.date;" ) ) assert stored == [[9000]] assert ( - list(conn.execute("MATCH ()-[e:follows]->() RETURN count(e);"))[0][0] == 1 + list(conn.execute("MATCH ()-[e:FOLLOWS]->() RETURN count(e);"))[0][0] == 1 ) finally: conn.close() @@ -231,7 +231,7 @@ def test_merge_edge_on_create_set_date_for_new_edge(): rows = list( conn.execute( "MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'Bob'}) " - "MERGE (u1)-[e:follows {date: 2012}]->(u2) " + "MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) " "ON CREATE SET e.date = 7001 " "RETURN e.date;" ) @@ -239,7 +239,7 @@ def test_merge_edge_on_create_set_date_for_new_edge(): assert rows == [[7001]] stored = list( conn.execute( - "MATCH (u1:User {name: 'Adam'})-[e:follows]->" + "MATCH (u1:User {name: 'Adam'})-[e:FOLLOWS]->" "(u2:User {name: 'Bob'}) RETURN e.date;" ) ) @@ -259,7 +259,7 @@ def test_merge_edge_on_create_ignored_when_edge_exists(): rows = list( conn.execute( "MATCH (u1:User {name: 'Adam'}), (u2:User {name: 'marko'}) " - "MERGE (u1)-[e:follows {date: 2012}]->(u2) " + "MERGE (u1)-[e:FOLLOWS {date: 2012}]->(u2) " "ON CREATE SET e.date = 11111 " "RETURN u1.name, u2.name, e.date;" ) @@ -267,7 +267,7 @@ def test_merge_edge_on_create_ignored_when_edge_exists(): assert rows == [["Adam", "marko", 2012]] stored = list( conn.execute( - "MATCH (u1:User {name: 'Adam'})-[e:follows]->" + "MATCH (u1:User {name: 'Adam'})-[e:FOLLOWS]->" "(u2:User {name: 'marko'}) RETURN e.date;" ) ) @@ -354,7 +354,7 @@ def test_optional_match_follows_where_age_correlated_count(): _seed_follows_adam_marko_2012(conn) cnt = list( conn.execute( - "MATCH (a:User) OPTIONAL MATCH (a)-[:follows]->(b:User) " + "MATCH (a:User) OPTIONAL MATCH (a)-[:FOLLOWS]->(b:User) " "WHERE b.age > a.age RETURN COUNT(*);" ) )[0][0] @@ -390,7 +390,7 @@ def test_merge_multiple_nodes_unsupported(): def test_merge_path_unsupported(): """ - MERGE (:User {name:'A'})-[:follows {date: 2012}]->(:User {name:'B'}) + MERGE (:User {name:'A'})-[:FOLLOWS {date: 2012}]->(:User {name:'B'}) is NOT supported. Per spec: MERGE on a full path pattern would create all vertices and the @@ -403,7 +403,7 @@ def test_merge_path_unsupported(): _seed_users_adam_marko(conn) with pytest.raises(Exception): conn.execute( - "MERGE (:User {name: 'Adam'})-[:follows {date: 2012}]->(:User {name: 'marko'});" + "MERGE (:User {name: 'Adam'})-[:FOLLOWS {date: 2012}]->(:User {name: 'marko'});" ) finally: conn.close() @@ -421,7 +421,7 @@ def test_merge_path_with_new_nodes_unsupported(): _seed_users_adam_marko(conn) with pytest.raises(Exception): conn.execute( - "MERGE (:User {name: 'X'})-[:follows {date: 2023}]->(:User {name: 'Y'});" + "MERGE (:User {name: 'X'})-[:FOLLOWS {date: 2023}]->(:User {name: 'Y'});" ) finally: conn.close()