Coder Social home page Coder Social logo

php-cypher-dsl's People

Contributors

26 avatar akkermans99 avatar marijnvanwezel avatar transistive avatar wgevaert avatar wikibasejesse avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

php-cypher-dsl's Issues

Use static functions instead of a trait for casts

Currently, cast methods are inserted into a class using the CastTrait trait. This has a few problems:

  • It requires an additional use statement which can get left behind after refactoring;
  • It clutters up the private interface;
  • It does not allow casts to be used outside classes.

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.

Add "NOT" clause

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:

  • Implement a BooleanType clause notClause that accepts any BooleanType.
  • Implement a method Query::notExists with the same input & output as Query::exists (possibly just a wrapper of Query::exists in a notClause)

Cheers!
wgevaert

Add the list concatenation operator expression

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.

Support comparison of dates

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?

Use PHP 8.0 union types

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:

  • It is not understood by IDE's, which makes refactoring hard;
  • It requires an additional function call;
  • Type-checking is only done at the deepest call;
  • It does not work for interfaces or abstract methods.

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:

  1. Add union types where needed to return types;
  2. Add union types where needed to argument types;
  3. Add union types where needed to class parameter types;
  4. Remove any references to the 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:

  • Use PHPDoc to specify which types the array allows and use PHP's native array type in signatures, or
  • Keep supporting the type-checking of arrays.

I 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.

Use nanoseconds without milli- or microseconds

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.

Update documentation

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.

Restrict where clauses can be inserted into the query

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:

  1. An invalid query may be executed on a database, leading to a runtime error;
  2. It weakens the static analysis capabilities of the DSL, since there is no way for your IDE to warn you about the invalid query or to give hints about which clauses can follow the previous.

Possible solution

My proposal to fix this is as follows:

  1. Create an interface for each clause that only contains the signatures for the builder methods that make sense on that clause (for example, the ReturnQuery interface would have the signature for orderBy, but the CreateQuery interface would not).
  2. Have each builder method specify as a type hint the interface of the clause it adds to the query. That is, the returning method specific it returns a ReturnQuery, the call method a CallQuery and so on.
  3. Have the 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.

Rename traits to be more consistent with names of other type traits

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

Better support for datatypes

Currently when I make a Cypher query, I use the Query::literal function to make my datatypes. However, this has some disadvantages:

  • Integers larger than 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.
    • I believe the preferred way to do this is by using toInteger( <string_of_numbers> ) in the Cypher query.
    • Note that integers larger than 2^63-1 are problematic for Neo4j as well.
  • There does not seem to be support for date-like properties.
  • There does not seem to be support for point-like properties.

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:

  • Make big integers / force Int type instead of e.g. float type:
    $intRepresentation = FunctionCall::raw('toInteger', [$bigNumberString]);
  • Make floats / force float type instead of e.g. int type:
    $floatRepresentation = FunctionCall::raw('toFloat', [$floatString]);
  • Make datetime-like representations (Note: The possible functions here are the 20 functions listed in this part of the Neo4j manual):
    • Due to the vast number of possibilities for the propertyMap to be used, I think it's best to let the user of the package make that themselves, maybe save for one simple case.
    $datePropertyMap = new PropertyMap([
        "year" => 1984,
        "month" => 10,
        "day"=>11
    ]);
    $date = FunctionCall::raw('date', [$datePropertyMap->toQuery()]);
  • Make point-representations
$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:

  • Implement the above functions 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).
  • Make some datatype-interface, and let every one of the above functions implement only the required types. (Date/Time/LocalTime/DateTime/LocalDateTime/Geo2DPoint/Geo3DPoint/Cartesian2DPoint/Cartesian3DPoint)

(Note: I skipped over "Duration" but maybe you want that as well).

TypeHint for structural types with properties

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.

Add support for indexing operator `[ ]`

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).

Add the string concatenation operator expression

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.

Improve clarity of API with regards to object construction

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.

How to use labels(n)[0] in where?

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 

Reusing nodes in CREATE clause leads to unexpected behaviour

Bug report

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).

Code snippet that reproduces the problem

$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'})

Expected output

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();

How to build a WHERE IN property query

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!

  • Ghlen

Drop support for PHP 7.4

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.

Add support for the `allShortestPath` and `shortestPath` constructs

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;"

Add support for map projections

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.

Add support for comparison chains

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.

Fix deprecation warnings for PHP >8.0

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

Is it possible to clean up the API?

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();

Make more use of variadic functions

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)
    ...

Add subqueries and UNIONS

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

Insert parentheses based on precedence

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:

  1. IN, STARTS WITH, ENDS WITH, CONTAINS, =~, IS NULL, IS NOT NULL
  2. +, - (unary)
  3. ^
  4. *, /, %
  5. +, - (binary)
  6. =, <>, <, >, <=, >=
  7. NOT
  8. AND
  9. XOR
  10. OR

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.