diff --git a/packages/graphql/src/SearchBy/Definitions/SearchByOperatorNotContainsDirective.php b/packages/graphql/src/SearchBy/Definitions/SearchByOperatorNotContainsDirective.php new file mode 100644 index 000000000..4d7edf940 --- /dev/null +++ b/packages/graphql/src/SearchBy/Definitions/SearchByOperatorNotContainsDirective.php @@ -0,0 +1,11 @@ +getEscapeCharacter(); $property = $builder->getGrammar()->wrap((string) $property->getParent()); $value = (string) Cast::toStringable($argument->toPlain()); - $character = $this->getEscapeCharacter(); + $not = $this->isNegated() ? ' NOT' : ''; $builder->whereRaw( - "{$property} LIKE ? ESCAPE '{$character}'", + "{$property}{$not} LIKE ? ESCAPE '{$character}'", [ $this->value($this->escape($builder, $value)), ], @@ -82,4 +83,8 @@ protected function escape(EloquentBuilder|QueryBuilder $builder, string $string) protected function getEscapeCharacter(): string { return '!'; } + + protected function isNegated(): bool { + return false; + } } diff --git a/packages/graphql/src/SearchBy/Operators/Comparison/NotContains.php b/packages/graphql/src/SearchBy/Operators/Comparison/NotContains.php new file mode 100644 index 000000000..1231df19a --- /dev/null +++ b/packages/graphql/src/SearchBy/Operators/Comparison/NotContains.php @@ -0,0 +1,17 @@ + + // ========================================================================= + /** + * @dataProvider dataProviderCall + * + * @param array{query: string, bindings: array} $expected + * @param BuilderFactory $builderFactory + * @param class-string $grammar + * @param Closure(static): Argument $argumentFactory + */ + public function testCall( + array $expected, + Closure $builderFactory, + string $grammar, + Property $property, + Closure $argumentFactory, + ): void { + $builder = $builderFactory($this); + $grammar = new $grammar(); + + if ($builder instanceof EloquentBuilder) { + $builder->getQuery()->grammar = $grammar; + } else { + $builder->grammar = $grammar; + } + + $operator = $this->app->make(NotContains::class); + $property = $property->getChild('operator name should be ignored'); + $argument = $argumentFactory($this); + $search = Mockery::mock(Handler::class); + $builder = $operator->call($search, $builder, $property, $argument); + + self::assertDatabaseQueryEquals($expected, $builder); + } + // + + // + // ========================================================================= + /** + * @return array + */ + public static function dataProviderCall(): array { + return (new CompositeDataProvider( + new BuilderDataProvider(), + new ArrayDataProvider([ + MySqlGrammar::class => [ + [ + 'query' => 'select * from `test_objects` where `property` NOT LIKE ? ESCAPE \'!\'', + 'bindings' => ['%!%a[!_]c!!!%%'], + ], + MySqlGrammar::class, + new Property('property'), + static function (self $test): Argument { + return $test->getGraphQLArgument('String!', '%a[_]c!%'); + }, + ], + SQLiteGrammar::class => [ + [ + 'query' => 'select * from "test_objects" where "property" NOT LIKE ? ESCAPE \'!\'', + 'bindings' => ['%!%a[!_]c!!!%%'], + ], + SQLiteGrammar::class, + new Property('property'), + static function (self $test): Argument { + return $test->getGraphQLArgument('String!', '%a[_]c!%'); + }, + ], + PostgresGrammar::class => [ + [ + 'query' => 'select * from "test_objects" where "property" NOT LIKE ? ESCAPE \'!\'', + 'bindings' => ['%!%a[!_]c!!!%%'], + ], + PostgresGrammar::class, + new Property('property'), + static function (self $test): Argument { + return $test->getGraphQLArgument('String!', '%a[_]c!%'); + }, + ], + SqlServerGrammar::class => [ + [ + 'query' => 'select * from [test_objects] where [property] NOT LIKE ? ESCAPE \'!\'', + 'bindings' => ['%!%a![!_!]c!!!%%'], + ], + SqlServerGrammar::class, + new Property('property'), + static function (self $test): Argument { + return $test->getGraphQLArgument('String!', '%a[_]c!%'); + }, + ], + ]), + ))->getData(); + } + // +} diff --git a/packages/graphql/src/SearchBy/Operators/Comparison/NotEndsWith.php b/packages/graphql/src/SearchBy/Operators/Comparison/NotEndsWith.php new file mode 100644 index 000000000..0835d708c --- /dev/null +++ b/packages/graphql/src/SearchBy/Operators/Comparison/NotEndsWith.php @@ -0,0 +1,17 @@ + + // ========================================================================= + /** + * @dataProvider dataProviderCall + * + * @param array{query: string, bindings: array} $expected + * @param BuilderFactory $builderFactory + * @param Closure(static): Argument $argumentFactory + */ + public function testCall( + array $expected, + Closure $builderFactory, + Property $property, + Closure $argumentFactory, + ): void { + $operator = $this->app->make(NotEndsWith::class); + $property = $property->getChild('operator name should be ignored'); + $argument = $argumentFactory($this); + $search = Mockery::mock(Handler::class); + $builder = $builderFactory($this); + $builder = $operator->call($search, $builder, $property, $argument); + + self::assertDatabaseQueryEquals($expected, $builder); + } + // + + // + // ========================================================================= + /** + * @return array + */ + public static function dataProviderCall(): array { + return (new CompositeDataProvider( + new BuilderDataProvider(), + new ArrayDataProvider([ + 'property' => [ + [ + 'query' => 'select * from "test_objects" where "property" NOT LIKE ? ESCAPE \'!\'', + 'bindings' => ['%!%a[!_]c!!!%'], + ], + new Property('property'), + static function (self $test): Argument { + return $test->getGraphQLArgument('String!', '%a[_]c!%'); + }, + ], + 'property.path' => [ + [ + 'query' => <<<'SQL' + select * from "test_objects" where "path"."to"."property" NOT LIKE ? ESCAPE '!' + SQL + , + 'bindings' => ['%abc'], + ], + new Property('path', 'to', 'property'), + static function (self $test): Argument { + return $test->getGraphQLArgument('String!', 'abc'); + }, + ], + ]), + ))->getData(); + } + // +} diff --git a/packages/graphql/src/SearchBy/Operators/Comparison/NotStartsWith.php b/packages/graphql/src/SearchBy/Operators/Comparison/NotStartsWith.php new file mode 100644 index 000000000..6c5ae0d4e --- /dev/null +++ b/packages/graphql/src/SearchBy/Operators/Comparison/NotStartsWith.php @@ -0,0 +1,17 @@ + + // ========================================================================= + /** + * @dataProvider dataProviderCall + * + * @param array{query: string, bindings: array} $expected + * @param BuilderFactory $builderFactory + * @param Closure(static): Argument $argumentFactory + */ + public function testCall( + array $expected, + Closure $builderFactory, + Property $property, + Closure $argumentFactory, + ): void { + $operator = $this->app->make(NotStartsWith::class); + $property = $property->getChild('operator name should be ignored'); + $argument = $argumentFactory($this); + $search = Mockery::mock(Handler::class); + $builder = $builderFactory($this); + $builder = $operator->call($search, $builder, $property, $argument); + + self::assertDatabaseQueryEquals($expected, $builder); + } + // + + // + // ========================================================================= + /** + * @return array + */ + public static function dataProviderCall(): array { + return (new CompositeDataProvider( + new BuilderDataProvider(), + new ArrayDataProvider([ + 'property' => [ + [ + 'query' => 'select * from "test_objects" where "property" NOT LIKE ? ESCAPE \'!\'', + 'bindings' => ['!%a[!_]c!!!%%'], + ], + new Property('property'), + static function (self $test): Argument { + return $test->getGraphQLArgument('String!', '%a[_]c!%'); + }, + ], + 'property.path' => [ + [ + 'query' => <<<'SQL' + select * from "test_objects" where "path"."to"."property" NOT LIKE ? ESCAPE '!' + SQL + , + 'bindings' => ['abc%'], + ], + new Property('path', 'to', 'property'), + static function (self $test): Argument { + return $test->getGraphQLArgument('String!', 'abc'); + }, + ], + ]), + ))->getData(); + } + // +}