Skip to content

Commit 9ffa5ad

Browse files
Merge branch 'main' into 5.0.0-typing-and-consistency
2 parents 453b4bd + 2fd1194 commit 9ffa5ad

File tree

5 files changed

+58
-76
lines changed

5 files changed

+58
-76
lines changed

README.md

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,13 @@ composer require "wikibase-solutions/php-cypher-dsl"
2727
To construct a query to find all of Tom Hanks' co-actors, you can use the following code:
2828

2929
```php
30-
$tom = Query::variable("tom");
31-
$tomNode = Query::node("Person")->withProperties([
32-
"name" => Query::literal("Tom Hanks")
33-
])->named($tom);
34-
35-
$movie = Query::variable("m");
36-
$movieNode = Query::node()->named($movie);
37-
38-
$coActors = Query::variable("coActors");
39-
$coActorsNode = Query::node()->named($coActors);
30+
$tom = Query::node("Person")->withProperties(["name" => Query::literal("Tom Hanks")]);
31+
$coActors = Query::node();
4032

4133
$statement = Query::new()
42-
->match($tomNode->relationshipTo($movieNode)->withType("ACTED_IN")->relationshipFrom($coActorsNode)->withType("ACTED_IN"))
43-
->returning($coActors->property("name"))
44-
->build();
34+
->match($tom->relationshipTo(Query::node(), "ACTED_IN")->relationshipFrom($coActors, "ACTED_IN"))
35+
->returning($coActors->property("name"))
36+
->build();
4537

46-
$this->assertSame("MATCH (tom:Person {name: 'Tom Hanks'})-[:`ACTED_IN`]->(m)<-[:`ACTED_IN`]-(coActors) RETURN coActors.name", $statement);
38+
$this->assertStringMatchesFormat("MATCH (:Person {name: 'Tom Hanks'})-[:`ACTED_IN`]->()<-[:`ACTED_IN`]-(%s) RETURN %s.name", $statement);
4739
```
48-
49-
## Roadmap
50-
51-
Below are some things that still need to be implemented.
52-
53-
- Add missing clauses
54-
- Add missing function definitions
55-
- Add missing expressions
56-
- Improve documentation

src/Traits/EscapeTrait.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
namespace WikibaseSolutions\CypherDSL\Traits;
2323

24-
use InvalidArgumentException;
24+
use function preg_match;
25+
use function sprintf;
26+
use function str_replace;
2527

2628
/**
2729
* Trait for encoding certain structures that are used in multiple clauses in a
@@ -30,25 +32,31 @@
3032
trait EscapeTrait
3133
{
3234
/**
33-
* Escapes the given 'name'. A name is an unquoted literal in a Cypher query, such as variables,
34-
* types or property names.
35+
* Escapes a 'name' if it needs to be escaped.
36+
* @see https://neo4j.com/docs/cypher-manual/4.4/syntax/naming
37+
* A 'name' in cypher is any string that should be included directly in a cypher query,
38+
* such as variable names, labels, property names and relation types
3539
*
3640
* @param string $name
3741
* @return string
3842
*/
3943
private static function escape(string $name): string
4044
{
41-
if ($name === "") {
42-
return "";
43-
}
44-
45-
if (ctype_alnum($name) && !ctype_digit($name)) {
45+
if (preg_match('/^\p{L}[\p{L}\d_]*$/u', $name)) {
4646
return $name;
4747
}
4848

49-
if (strpos($name, '`') !== false) {
50-
throw new InvalidArgumentException("A name must not contain a backtick (`)");
51-
}
49+
return self::escapeRaw($name);
50+
}
51+
52+
/**
53+
* Escapes the given $name to be used directly in a CYPHER query.
54+
* Note: according to https://github.com/neo4j/neo4j/issues/12901 backslashes might give problems in some Neo4j versions.
55+
*/
56+
public static function escapeRaw($name)
57+
{
58+
// Escape backticks that are included in $name by doubling them.
59+
$name = str_replace('`', '``', $name);
5260

5361
return sprintf("`%s`", $name);
5462
}

tests/Unit/Expressions/ParameterTest.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ public function provideThrowsExceptionOnInvalidData(): array
6868
{
6969
return [
7070
[""],
71-
["@"],
72-
["!"],
73-
["-"],
74-
[''],
71+
[str_repeat('a', 65535)],
7572
];
7673
}
7774
}

tests/Unit/Patterns/NodeTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
namespace WikibaseSolutions\CypherDSL\Tests\Unit\Patterns;
2323

24-
use InvalidArgumentException;
2524
use PHPUnit\Framework\TestCase;
2625
use TypeError;
2726
use WikibaseSolutions\CypherDSL\Expressions\Literals\Number;
@@ -44,13 +43,19 @@ public function testEmptyNode(): void
4443
$this->assertSame("()", $node->toQuery());
4544
}
4645

47-
public function testBacktickThrowsException(): void
46+
// Further tests can be found in Traits/EscapeTraitTest
47+
public function testBacktickIsEscaped(): void
4848
{
4949
$node = new Node();
5050

51+
<<<<<<< HEAD
5152
$this->expectException(InvalidArgumentException::class);
5253
$this->expectDeprecationMessage('A name can only contain alphanumeric characters and underscores');
5354
$node->withVariable('abcdr`eer');
55+
=======
56+
$node->named('abcdr`eer');
57+
$this->assertEquals('(`abcdr``eer`)', $node->toQuery());
58+
>>>>>>> main
5459
}
5560

5661
/**

tests/Unit/Traits/EscapeTraitTest.php

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
namespace WikibaseSolutions\CypherDSL\Tests\Unit\Traits;
2323

24-
use InvalidArgumentException;
2524
use PHPUnit\Framework\TestCase;
2625
use WikibaseSolutions\CypherDSL\Traits\EscapeTrait;
2726

@@ -66,58 +65,29 @@ public function testUnsafeValueIsEscaped(string $value)
6665
$this->assertSame($expected, $actual);
6766
}
6867

69-
public function testValueWithBacktickThrowsException()
70-
{
71-
$this->expectException(InvalidArgumentException::class);
72-
73-
$this->trait->escape("foo`bar");
74-
}
75-
7668
public function provideSafeValueIsNotEscapedData(): array
7769
{
7870
return [
7971
['foobar'],
8072
['fooBar'],
8173
['FOOBAR'],
74+
['foo_bar'],
75+
['FOO_BAR'],
8276
['aaa'],
83-
['bbb'],
84-
['ccc'],
85-
['ddd'],
86-
['eee'],
87-
['fff'],
88-
['ggg'],
89-
['hhh'],
90-
['iii'],
91-
['jjj'],
92-
['kkk'],
93-
['lll'],
94-
['mmm'],
95-
['nnn'],
96-
['ooo'],
97-
['ppp'],
98-
['qqq'],
99-
['rrr'],
100-
['sss'],
101-
['ttt'],
102-
['uuu'],
103-
['vvv'],
104-
['www'],
105-
['xxx'],
106-
['yyy'],
107-
['zzz'],
108-
[''],
10977
['aaa100'],
11078
['a0'],
11179
['z10'],
11280
['z99'],
81+
['ça'],
82+
[''],
11383
];
11484
}
11585

11686
public function provideUnsafeValueIsEscapedData(): array
11787
{
11888
return [
119-
['foo_bar'],
120-
['FOO_BAR'],
89+
[''],
90+
['__FooBar__'],
12191
['_'],
12292
['__'],
12393
['\''],
@@ -129,4 +99,23 @@ public function provideUnsafeValueIsEscapedData(): array
12999
['2'],
130100
];
131101
}
102+
103+
/**
104+
* @dataProvider provideValueWithBacktickIsProperlyEscapedData
105+
*/
106+
public function testValueWithBacktickIsProperlyEscaped($input, $expected)
107+
{
108+
$this->assertSame('`foo``bar`', $this->trait->escape("foo`bar"));
109+
}
110+
111+
public function provideValueWithBacktickIsProperlyEscapedData(): array
112+
{
113+
return [
114+
['foo`bar','`foo``bar`'],
115+
['`foo','```foo`'],
116+
['foo`','`foo```'],
117+
['foo``bar','`foo````bar`'],
118+
['`foo`','```foo```'],
119+
];
120+
}
132121
}

0 commit comments

Comments
 (0)