neo4j-php / php-cypher-dsl Goto Github PK
View Code? Open in Web Editor NEWA low-level query builder for Cypher written in PHP
License: MIT License
A low-level query builder for Cypher written in PHP
License: MIT License
Currently, cast methods are inserted into a class using the CastTrait
trait. This has a few problems:
use
statement which can get left behind after refactoring;The trait should instead be written as an abstract class with final static functions, that can then be used throughout the project. Because all casts are currently private functions, this does not have impact on backwards compatibility.
Hello!
I am trying to write a query of the following form (with some extra conditions):
MATCH (n)<-(m) WHERE NOT EXISTS { MATCH (m)<-() } DELETE m;
How do I do this nicely using this package?
Using the raw clause, I have done it like so:
$innerQuery = Query::new()->match(Query::node()->named($m)->relationshipFrom(Query::node()));
$query = Query::new()->match(
Query::node()->named($n)->relationshipFrom(Query::node()->named($m))
)->raw(
'NOT EXISTS {',
$innerQuery->build() . '}'
)->delete(
$m
)->build();
But the . '}'
at the end of the raw clause seems a bit awkward to me. Would it be possible to either:
notClause
that accepts any BooleanType.Query::notExists
with the same input & output as Query::exists
(possibly just a wrapper of Query::exists
in a notClause
)Cheers!
wgevaert
Currently, list concatenation is not supported by the type system. That is because Addition
only accepts NumeralType
. We need to add support for list concatenation by adding a new expression (ListConcatenation
), that takes two lists.
Trying to make a query like
MATCH (n:Article) WHERE n.creation > datetime({year: 2020, month: 12, day: 11, hour: 10, minute: 9}) AND n.creation < datetime({year: 2021, month: 12, day: 11, hour: 10, minute: 9}) RETURN n.title
is difficult. For example, doing this does not work:
include_once __DIR__ . '/vendor/autoload.php';
use WikibaseSolutions\CypherDSL\Query;
use WikibaseSolutions\CypherDSL\AndOperator;
$n = Query::variable('n');
$query = Query::new()->match(
Query::node('Article')->named($n)
)->where(
new AndOperator(
$n->property('creation')->gt(
Query::literal()::dateTimeYMD(2020,12,11,10,9)
),
$n->property('creation')->lt(
Query::literal()::dateTimeYMD(2021,12,11,10,9)
)
)
)->returning($n->property('title'))->build();
echo $query;
because one gets the following error:
PHP Fatal error: Uncaught TypeError: Argument 1 passed to WikibaseSolutions\CypherDSL\Property::gt() must implement interface WikibaseSolutions\CypherDSL\Types\PropertyTypes\NumeralType, instance of WikibaseSolutions\CypherDSL\Functions\DateTime given, called in /path/to/php-cypher-dsl/test.php on line 13 and defined in /path/to/php-cypher-dsl/src/Traits/NumeralTypeTrait.php:88
Stack trace:
#0 /path/to/php-cypher-dsl/test.php(13): WikibaseSolutions\CypherDSL\Property->gt(Object(WikibaseSolutions\CypherDSL\Functions\DateTime))
#1 {main}
thrown in /path/to/php-cypher-dsl/src/Traits/NumeralTypeTrait.php on line 88
Can this be fixed?
PHP 8.0 introduces the concept of union types, which allows us to explicitly type a function with multiple different types. php-cypher-dsl currently polyfills this feature through the ErrorTrait
trait. However, there are a few problems with this polyfill approach:
Therefore, I want to propose to drop support for PHP 7.4 and start using PHP 8.0 union types. Implementing this is relatively straightforward:
ErrorTrait
trait.The ErrorTrait
trait also has a method for checking whether the type of each value in an array is allowed. This is currently not supported through PHP's type system. There are two options to fix this:
array
type in signatures, orI think the first option is best. Type checking of arrays is only used in a few places (five times throughout the entire library), and it adds complexity and overhead. Most usages can also be replaced by typed variadic parameters.
This package blocks me when I try to make a temporal value by providing hour, minute, second and nanoseconds, but this should be possible.
I think I will make a PR to solve this shortly.
The current documentation is very out of date. It should be updated to document the latest features. Also, some breaking changes causes the examples to no longer work correctly.
The current version has no built-in mechanism to prevent invalid clause orders, as nothing prevents or warns the user from doing the following:
$n = Query::node()->withVariable('n');
$query = Query::new()
->match($n)
->orderBy($n->property('age'))
->returning($n)
->build();
// MATCH (n) ORDER BY n.age RETURN n
The above query is invalid, since ORDER BY
may only be used after a RETURN
or WITH
clause.
The fact that the DSL allows this is undesirable for two reasons:
My proposal to fix this is as follows:
ReturnQuery
interface would have the signature for orderBy
, but the CreateQuery
interface would not).returning
method specific it returns a ReturnQuery
, the call
method a CallQuery
and so on.Query
class implement all interfaces.If a user now tries to build the query above, their IDE should warn them that the orderBy
method is not part of the MatchQuery
interface.
The following traits utilise a different naming scheme than the other type traits:
DateTimeTrait
=> DateTimeTypeTrait
DateTrait
=> DateTypeTrait
LocalDateTimeTrait
=> LocalDateTimeTypeTrait
LocalTimeTrait
=> LocalTimeTypeTrait
PointTrait
=> PointTypeTrait
TimeTrait
=> TimeTypeTrait
Currently when I make a Cypher query, I use the Query::literal
function to make my datatypes. However, this has some disadvantages:
PHP_INT_MAX
but smaller than 2^63-1
(e.g. on 32-bit systems) might be difficult to put in the query using this package.
toInteger( <string_of_numbers> )
in the Cypher query.2^63-1
are problematic for Neo4j as well.I think this package should be somewhat-low-level, and the (higher-level) task of determining what datatype should be used (except for basic cases such as php-datatype -> Cypher datatype) rests on the user of this package.
Currently, one can do the following:
$intRepresentation = FunctionCall::raw('toInteger', [$bigNumberString]);
$floatRepresentation = FunctionCall::raw('toFloat', [$floatString]);
$datePropertyMap = new PropertyMap([
"year" => 1984,
"month" => 10,
"day"=>11
]);
$date = FunctionCall::raw('date', [$datePropertyMap->toQuery()]);
$geo2D = FunctionCall::raw('point', [new PropertyMap(['latitude' => 48.8583, 'longitude' => 2.2945])]);
$geo3D = FunctionCall::raw('point', [new PropertyMap(['latitude' => 48.8583, 'longitude' => 2.2945, 'height' => 280])]);
$cart2D = FunctionCall::raw('point', [new PropertyMap(['x' => 3, 'y' => 4])]);
$cart3D = FunctionCall::raw('point', [new PropertyMap(['x' => 3, 'y' => 4, 'z' => 5])]);
However, the return type of the above functions is rawFunction
, and thus they all implement e.g. PathType
, which does not make sense.
Would it be possible to:
date
, date.transaction
, date.statement
, date.realtime
, time
, time.transaction
, time.statement
, time.realtime
, localtime
, localtime.transaction
, localtime.statement
, localtime.realtime
, datetime
, datetime.transaction
, datetime.statement
, datetime.realtime
, localdatetime
, localdatetime.transaction
, localdatetime.statement
, localdatetime.realtime
, toFloat
, toInteger
and point
(Maybe for point
you do want to have 4 separate functions for the 4 different point types).(Note: I skipped over "Duration" but maybe you want that as well).
I would like to make code like this:
public function addProperty(
string $key,
PropertyType $value,
SomeSpecificTypeHint $nodeOrRelation
): SomeSpecificTypeHint {
return $nodeOrRelation->addProperty($key, $value);
}
$relationWithProperty = addProperty('someKey', Query::string('someValue'), new Relationship(Relationship::DIR_RIGHT));
$nodeWithProperty = addProperty('someKey', Query::string('someValue'), new Node());
The problem is that SomeSpecificTypeHint
cannot be specific without php8 union types. I would like to specifically hint that I want a class that uses Traits\HasPropertiesTrait
.
Before merging this pr, I could do it with a StructuralType
typehint, but now I cannot.
Furthermore, in the StructuralType
docblock it states that The structural types are: node, relationship, path
, but relationshipType
does not extend StructuralType, which is odd.
See this pr I just made for how I would like to solve it.
CYPHER has the indexing operator []
.
Simple examples of its use are WITH {a:'Hello World!'} AS map RETURN map['a']
and WITH ['Hello World!'] AS list RETURN list[0]
.
More complicated examples are WITH 2 AS a, ['a','b','Hello World!'] AS list RETURN list[a]
, WITH {a:'Hello World!',c:'a'} AS map RETURN map[map['c']]
and
WITH ['a','Hello World!','b'] AS list, {ab:1} AS map RETURN list[map[list[0]+list[2]]]
All of these should return Hello World!
I think []
should be implemented on things of CompositeType
and CompositeTypeTrait
should have this implemented like ->index($index)
where $index
is either a string-like thing or integer-like thing. (In particular, lists only accept integers and maps only accept strings if I'm correct).
Currently, string concatenation is not supported by the type system. That is because Addition
only accepts NumeralType
. We need to add support for string concatenation by adding a new expression (StringConcatenation
), that takes two strings.
Currently, the following clauses from the openCypher standard (https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf) are missing:
MANDATORY MATCH
UNWIND
In the current implementation, it is often very unclear when calling a function whether it changes the object instance, or whether it returns a new object.
For example, if you have a $node :: Node
and $variable :: Variable
, the functions labeled
behave very differently:
$node->labeled('foo'); // Updates the Node and adds the label
$variable->labeled('foo'); // Returns a new Label object and leaves the Variable unchanged
It should be clear when calling a function whether the instance is updated or whether a new object is returned.
I have this code, but am confused about how to convert it! even ChatGPT doesn't know LOL
BTW: the documentation does not cover all aspects of this great lib, is there another doc?
Thank you
MATCH (n)
where (n.status=0 or n.status="0") and (n.lableb is null)
and (labels(n)[0]="CharactourM" or labels(n)[0]="CompanyM" )
RETURN distinct labels(n)[0] as label,count(n) as count
The CASE
expression is currently not supported by the DSL. See https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf for documentation.
Cypher's CREATE
clause supports reusing variables that were matched elsewhere in the query to create new relationships. For example:
MATCH (charlie:Person {name: 'Charlie Sheen'}), (oliver:Person {name: 'Oliver Stone'})
CREATE (charlie)-[:ACTED_IN {role: 'Bud Fox'}]->(wallStreet:Movie {title: 'Wall Street'})<-[:DIRECTED]-(oliver)
However, this is not properly supported in php-cypher-dsl (see example below).
$charlie = node("Person")->withProperties(["name" => "Charlie Sheen"]);
$oliver = node("Person")->withProperties(["name" => "Oliver Stone"]);
$wallStreet = node("Movie")->withProperties(["title" => "Wall Street"]);
$query = query()
->match([$charlie, $oliver])
->create($charlie->relationshipTo($wallStreet, type: "ACTED_IN", properties: ["role" => "Bud Fox"])->relationshipFrom($oliver))
->build();
This gives:
MATCH (:Person {name: 'Charlie Sheen'}), (:Person {name: 'Oliver Stone'})
CREATE (:Person {name: 'Charlie Sheen'})-[:ACTED_IN {role: 'Bud Fox'}]->(:Movie {title: 'Wall Street'})<--(:Person {name: 'Oliver Stone'})
MATCH (charlie:Person {name: 'Charlie Sheen'}), (oliver:Person {name: 'Oliver Stone'})
CREATE (charlie)-[:ACTED_IN {role: 'Bud Fox'}]->(:Movie {title: 'Wall Street'})<--(oliver)
A workaround exists by first retrieving the variable of the node, and then creating a new node with that variable. This is syntactically rather ugly:
$query = query()
->match([$charlie, $oliver])
->create(node()->withVariable($charlie->getVariable())->relationshipTo($wallStreet, type: "ACTED_IN", properties: ["role" => "Bud Fox"])->relationshipFrom(node()->withVariable($oliver->getVariable())))
->build();
Hello!
I am starting to your DSL in some of my projects and I love it! I am stuck however how to build a query like this:
MATCH (x:Node) WHERE x.id IN $ids RETURN x
I am stuck here:
Query::new()->match(Query::node('Node')->named('x'))
->where(Query::variable('x.id')-> STUCK HERE )
->toQuery()
Ideally, I would love to be able to do something like this:
Query::new()->match(Query::node('Node')->named('x'))
->where(Query::variable('x')->property('id')->in(Query::param('ids')))
->returning(Query::variable('x'))
->toQuery()
Is this already possible? I can't find it.
Thanks in advance!
Support for PHP 7.4 was dropped last November by the PHP developers.
PHP 7.4 doesn't support certain features, such as union types, that make supporting it increasingly difficult. Dropping support for it will allow us to remove a number of ugly polyfills, which would increase the readability and usability of the library and make it more type-safe.
The allShortestPath
and shortestPath
constructs are currently not supported. Note that these are not functions, but rather a special kind "path":
Invalid input 'RETURN': expected "(", "allShortestPaths" or "shortestPath" (line 1, column 7 (offset: 6))
"MATCH RETURN n;"
It currently only takes a procedure name and some arguments.
The following is currently not possible: { .name, .realName, movies: collect(movie { .title, .year })}
.
This is called a map projection, and is part of the openCypher standard. See page 21 of openCypher9.
Currently, all comparison operators only implement BooleanType
. This means that the following is not allowed:
$a->gt($b)->gt($c)
That is because $a->gt($b)
returns a BooleanType, which does not implement the gt
method. We would like to support this, since it is supported in Cypher. That is, the following two expressions are equivalent:
a > b > c
and a > b AND b > c
This extends to all comparison operators and is not limited to three terms.
PHP 8.2 has been released and should be added to the pipeline for automatic testing.
When running the unit tests, the following deprecation warning is emitted:
Deprecated: Return type of Iterator@anonymous::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /__w/php-cypher-dsl/php-cypher-dsl/php-cypher-dsl/tests/Unit/QueryTest.php on line 152
@transistive suggested to add php-cs-fixer to the github Actions ci thingy.
I made a first attempt but the github actions do not seem to be triggered for some reason.
I started using this package last few days, moving away from string concatenation, and I've found it really difficult to read the code once it's done. It seems to me the expectation around certain objects for arguments into the method calls is what makes it so verbose and difficult to read. It would be great if it could accept strings where necessary, at least then we could use object chaining whilst keeping the code readable.
Compare:
$deck = Query::node("Deck")->named($d = new Variable('d'));
$query = Query::new()->match($deck);
if ($format->raw() !== 'all') {
$query->where($d->property('format')->equals(Query::parameter('format')));
}
$statement = $query->returning(Query::rawExpression('count(d) AS total'))->build();
if ($format->raw() !== 'all') {
$result = $this->client()->run($statement, ['format' => $format->raw()]);
} else {
$result = $this->client()->run($statement);
}
return $result->first()->get('total');
To:
if ($format->raw() !== 'all') {
$result = $this->client()->run('MATCH (d:Deck) WHERE d.format = $format RETURN count(d) AS total', ['format' => $format->raw()]);
} else {
$result = $this->client()->run('MATCH (d:Deck) RETURN count(d) AS total');
}
return $result->first()->get('total');
As it stands, I am really struggling to see the benefit of using this API to generate queries.
I would like to propose an API such as the following, which would help to move away from string concatenation (which is admittedly quite ugly):
$statement = Query::new()
->match('(d:deck)')
->where('d.format', '$format')
->return('count(d) AS total')
->build();
Variadic functions provide slightly improved readability as opposed to arrays:
Without variadic functions:
$query = query()
->match([$a, $b])
...
With variadic functions:
$query = query()
->match($a, $b)
...
Hello,
I would love to see support for subqueries and unions
It will make it trivial to combine multiple queries.
Right now, I was thinking about the Query
class. It should just need to have two extra methods: call
/subQuery
and union
Both methods should expect the Query
class as their only parameter. This way we can simply combine queries and add them as subqueries.
What do you think?
Kind regards,
Ghlen
Convertable
is not a word.
Currently, parentheses are always inserted when creating an expression (unless $insertParentheses
is false
). This leads to less readable expressions: compare ((a AND b) OR (c AND d))
to a AND b OR c AND d
.
I propose to only insert parentheses when this is required by precedence rules. There is no explicit document on the precedence of operators in Cypher AFAIK, but it can be derived from the grammar:
IN
, STARTS WITH
, ENDS WITH
, CONTAINS
, =~
, IS NULL
, IS NOT NULL
+
, -
(unary)^
*
, /
, %
+
, -
(binary)=
, <>
, <
, >
, <=
, >=
NOT
AND
XOR
OR
The regular expression operator is missing from the DSL and needs to be added. For more information, see the Neo4j documentation.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.