From 1b038a857170da0ff470b60b7a3273ea3a71b87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Vil=C3=A0?= Date: Fri, 10 May 2024 09:56:45 +0200 Subject: [PATCH 1/6] List query-builder-ts front-end implementation package (#925) --- docs/advanced-usage/front-end-implementation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/advanced-usage/front-end-implementation.md b/docs/advanced-usage/front-end-implementation.md index b776a59f..fe90478f 100644 --- a/docs/advanced-usage/front-end-implementation.md +++ b/docs/advanced-usage/front-end-implementation.md @@ -10,3 +10,4 @@ If you're interested in building query urls on the front-end to match this packa - Vue + Inertia.js: [inertiajs-tables-laravel-query-builder](https://github.com/protonemedia/inertiajs-tables-laravel-query-builder) by [ Pascal Baljet](https://github.com/pascalbaljet). - React: [cogent-js package](https://www.npmjs.com/package/cogent-js) by [Joel Male](https://github.com/joelwmale). +- Typescript: [query-builder-ts package](https://www.npmjs.com/package/@vortechron/query-builder-ts) by [Amirul Adli](https://www.npmjs.com/~vortechron) From 769f3c40f70e210b0857931fb6a5a7b044bf6c87 Mon Sep 17 00:00:00 2001 From: Talpx1 <69934344+Talpx1@users.noreply.github.com> Date: Fri, 10 May 2024 10:06:31 +0200 Subject: [PATCH 2/6] fixed incorrect sql LIKE escaping for some db drivers while performing partial filter (#927) --- src/Filters/FiltersPartial.php | 12 +++++++++++- tests/FilterTest.php | 25 ++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Filters/FiltersPartial.php b/src/Filters/FiltersPartial.php index e0106549..8a85401e 100644 --- a/src/Filters/FiltersPartial.php +++ b/src/Filters/FiltersPartial.php @@ -3,6 +3,7 @@ namespace Spatie\QueryBuilder\Filters; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\DB; /** * @template TModelClass of \Illuminate\Database\Eloquent\Model @@ -47,7 +48,7 @@ protected function getWhereRawParameters($value, string $property): array $value = mb_strtolower((string) $value, 'UTF8'); return [ - "LOWER({$property}) LIKE ?", + "LOWER({$property}) LIKE ?".self::maybeSpecifyEscapeChar(), ['%'.self::escapeLike($value).'%'], ]; } @@ -60,4 +61,13 @@ private static function escapeLike(string $value): string $value, ); } + + private static function maybeSpecifyEscapeChar(): string + { + if(!in_array(DB::getDriverName(), ["sqlite","pgsql","sqlsrv"])){ + return ""; + } + + return " ESCAPE '\'"; + } } diff --git a/tests/FilterTest.php b/tests/FilterTest.php index c9f9f0c1..354ce834 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -5,6 +5,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Pest\Expectation; use function PHPUnit\Framework\assertObjectHasProperty; use Spatie\QueryBuilder\AllowedFilter; @@ -86,9 +87,31 @@ ->where(DB::raw('LOWER(`test_models`.`name`)'), 'LIKE', 'john') ->toSql(); - expect($queryBuilderSql)->toEqual($expectedSql); + expect($queryBuilderSql)->toContain($expectedSql); }); +it('specifies escape character in supported databases', function (string $dbDriver) { + $fakeConnection = "test_{$dbDriver}"; + + DB::connectUsing($fakeConnection, [ + 'driver' => $dbDriver, + 'database' => null, + ]); + + DB::usingConnection($fakeConnection, function() use ($dbDriver){ + $request = new Request([ + 'filter' => ['name' => 'to_find'], + ]); + + $queryBuilderSql = QueryBuilder::for(TestModel::select('id', 'name'), $request) + ->allowedFilters('name', 'id') + ->toSql(); + + expect($queryBuilderSql)->when(in_array($dbDriver, ["sqlite","pgsql","sqlsrv"]), fn(Expectation $query) => $query->toContain("ESCAPE '\'")); + expect($queryBuilderSql)->when($dbDriver === 'mysql', fn(Expectation $query) => $query->not->toContain("ESCAPE '\'")); + }); +})->with(['sqlite', 'mysql', 'pgsql', 'sqlsrv']); + it('can filter results based on the existence of a property in an array', function () { $results = createQueryFromFilterRequest([ 'id' => '1,2', From cf51acb683bc35817bed8c97e4d17ca6a6a36813 Mon Sep 17 00:00:00 2001 From: AlexVanderbist Date: Fri, 10 May 2024 08:06:50 +0000 Subject: [PATCH 3/6] Fix styling --- src/Filters/FiltersPartial.php | 4 ++-- tests/FilterTest.php | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Filters/FiltersPartial.php b/src/Filters/FiltersPartial.php index 8a85401e..5c0bf5ff 100644 --- a/src/Filters/FiltersPartial.php +++ b/src/Filters/FiltersPartial.php @@ -64,10 +64,10 @@ private static function escapeLike(string $value): string private static function maybeSpecifyEscapeChar(): string { - if(!in_array(DB::getDriverName(), ["sqlite","pgsql","sqlsrv"])){ + if(! in_array(DB::getDriverName(), ["sqlite","pgsql","sqlsrv"])) { return ""; } - return " ESCAPE '\'"; + return " ESCAPE '\'"; } } diff --git a/tests/FilterTest.php b/tests/FilterTest.php index 354ce834..80b971be 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\DB; use Pest\Expectation; + use function PHPUnit\Framework\assertObjectHasProperty; use Spatie\QueryBuilder\AllowedFilter; @@ -92,13 +93,13 @@ it('specifies escape character in supported databases', function (string $dbDriver) { $fakeConnection = "test_{$dbDriver}"; - + DB::connectUsing($fakeConnection, [ 'driver' => $dbDriver, 'database' => null, ]); - DB::usingConnection($fakeConnection, function() use ($dbDriver){ + DB::usingConnection($fakeConnection, function () use ($dbDriver) { $request = new Request([ 'filter' => ['name' => 'to_find'], ]); @@ -107,8 +108,8 @@ ->allowedFilters('name', 'id') ->toSql(); - expect($queryBuilderSql)->when(in_array($dbDriver, ["sqlite","pgsql","sqlsrv"]), fn(Expectation $query) => $query->toContain("ESCAPE '\'")); - expect($queryBuilderSql)->when($dbDriver === 'mysql', fn(Expectation $query) => $query->not->toContain("ESCAPE '\'")); + expect($queryBuilderSql)->when(in_array($dbDriver, ["sqlite","pgsql","sqlsrv"]), fn (Expectation $query) => $query->toContain("ESCAPE '\'")); + expect($queryBuilderSql)->when($dbDriver === 'mysql', fn (Expectation $query) => $query->not->toContain("ESCAPE '\'")); }); })->with(['sqlite', 'mysql', 'pgsql', 'sqlsrv']); From a749843394a158ff999a6a0ccfafc49c98035a48 Mon Sep 17 00:00:00 2001 From: Alex Vanderbist Date: Fri, 10 May 2024 10:18:33 +0200 Subject: [PATCH 4/6] Respect model db driver --- .phpunit.cache/test-results | 1 + src/Filters/FiltersBeginsWithStrict.php | 6 +++--- src/Filters/FiltersEndsWithStrict.php | 7 ++++--- src/Filters/FiltersPartial.php | 28 +++++++++++++++++-------- 4 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 .phpunit.cache/test-results diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results new file mode 100644 index 00000000..ecb9e20c --- /dev/null +++ b/.phpunit.cache/test-results @@ -0,0 +1 @@ +{"version":"pest_2.34.7","defects":[],"times":{"P\\Tests\\FieldsTest::__pest_evaluable_it_fetches_all_columns_if_no_field_was_requested":0.007,"P\\Tests\\FieldsTest::__pest_evaluable_it_fetches_all_columns_if_no_field_was_requested_but_allowed_fields_were_specified":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_replaces_selected_array_columns_on_the_query":0.003,"P\\Tests\\FieldsTest::__pest_evaluable_it_replaces_selected_string_columns_on_the_query":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_fetch_specific_array_columns":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_fetch_specific_string_columns":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_wont_fetch_a_specific_array_column_if_its_not_allowed":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_wont_fetch_a_specific_string_column_if_its_not_allowed":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_fetch_sketchy_array_columns_if_they_are_allowed_fields":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_fetch_sketchy_string_columns_if_they_are_allowed_fields":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_guards_against_not_allowed_array_fields":0.003,"P\\Tests\\FieldsTest::__pest_evaluable_it_guards_against_not_allowed_string_fields":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_guards_against_not_allowed_array_fields_from_an_included_resource":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_guards_against_not_allowed_string_fields_from_an_included_resource":0.003,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_fetch_only_requested_array_columns_from_an_included_model":0.005,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_fetch_only_requested_string_columns_from_an_included_model":0.006,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_fetch_requested_array_columns_from_included_models_up_to_two_levels_deep":0.004,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_fetch_requested_string_columns_from_included_models_up_to_two_levels_deep":0.004,"P\\Tests\\FieldsTest::__pest_evaluable_it_throws_an_exception_when_calling_allowed_includes_before_allowed_fields":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_throws_an_exception_when_calling_allowed_includes_before_allowed_fields_but_with_requested_fields":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_throws_an_exception_when_requesting_fields_for_an_allowed_included_without_any_allowed_fields":0.002,"P\\Tests\\FieldsTest::__pest_evaluable_it_can_allow_specific_fields_on_an_included_model":0.003,"P\\Tests\\FieldsTest::__pest_evaluable_it_wont_use_sketchy_field_requests":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_models_by_partial_property_by_default":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_models_by_an_array_as_filter_value":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_partially_and_case_insensitive":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_based_on_the_partial_existence_of_a_property_in_an_array":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_models_and_return_an_empty_collection":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_a_custom_base_query_with_select":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_specifies_escape_character_in_supported_databases#('sqlite')":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_specifies_escape_character_in_supported_databases#('mysql')":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_specifies_escape_character_in_supported_databases#('pgsql')":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_specifies_escape_character_in_supported_databases#('sqlsrv')":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_based_on_the_existence_of_a_property_in_an_array":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_ignores_empty_values_in_an_array_partial_filter":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_ignores_an_empty_array_partial_filter":0.002,"P\\Tests\\FilterTest::__pest_evaluable_falsy_values_are_not_ignored_when_applying_a_partial_filter":0.003,"P\\Tests\\FilterTest::__pest_evaluable_falsy_values_are_not_ignored_when_applying_a_begins_with_strict_filter":0.002,"P\\Tests\\FilterTest::__pest_evaluable_falsy_values_are_not_ignored_when_applying_a_ends_with_strict_filter":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_partial_using_begins_with_strict":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_partial_using_ends_with_strict":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_and_match_results_by_exact_property":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_and_reject_results_by_exact_property":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_by_scope":0.004,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_by_nested_relation_scope":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_by_type_hinted_scope":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_by_regular_and_type_hinted_scope":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_by_scope_with_multiple_parameters":0.01,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_by_scope_with_multiple_parameters_in_an_associative_array":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_results_by_a_custom_filter_class":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_allow_multiple_filters":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_allow_multiple_filters_as_an_array":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_by_multiple_filters":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_guards_against_invalid_filters":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_does_not_throw_invalid_filter_exception_when_disable_in_config":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_can_create_a_custom_filter_with_an_instantiated_filter":0.003,"P\\Tests\\FilterTest::__pest_evaluable_an_invalid_filter_query_exception_contains_the_unknown_and_allowed_filters":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_allows_for_adding_ignorable_values":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_should_not_apply_a_filter_if_the_supplied_value_is_ignored":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_should_apply_the_filter_on_the_subset_of_allowed_values":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_should_apply_the_filter_on_the_subset_of_allowed_values_regardless_of_the_keys_order":0.006,"P\\Tests\\FilterTest::__pest_evaluable_it_can_take_an_argument_for_custom_column_name_resolution":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_sets_property_column_name_to_property_name_by_default":0.004,"P\\Tests\\FilterTest::__pest_evaluable_it_resolves_queries_using_property_column_name":0.006,"P\\Tests\\FilterTest::__pest_evaluable_it_can_filter_using_boolean_flags":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_should_apply_a_default_filter_value_if_nothing_in_request":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_does_not_apply_default_filter_when_filter_exists_and_default_is_set":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_should_apply_a_null_default_filter_value_if_nothing_in_request":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_does_not_apply_default_filter_when_filter_exists_and_default_null_is_set":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_should_apply_a_nullable_filter_when_filter_exists_and_is_null":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_should_apply_a_nullable_filter_when_filter_exists_and_is_set":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_should_filter_by_query_parameters_if_a_default_value_is_set_and_unset_afterwards":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_should_not_filter_at_all_if_a_default_value_is_set_and_unset_afterwards":0.002,"P\\Tests\\FilterTest::__pest_evaluable_it_should_apply_a_filter_with_a_multi_dimensional_array_value":0.003,"P\\Tests\\FilterTest::__pest_evaluable_it_can_override_the_array_value_delimiter_for_single_filters":0.004,"P\\Tests\\FiltersCallbackTest::__pest_evaluable_it_should_filter_by_closure":0.002,"P\\Tests\\FiltersCallbackTest::__pest_evaluable_it_should_filter_by_array_callback":0.002,"P\\Tests\\FiltersTrashedTest::__pest_evaluable_it_should_filter_not_trashed_by_default":0.005,"P\\Tests\\FiltersTrashedTest::__pest_evaluable_it_can_filter_only_trashed":0.002,"P\\Tests\\FiltersTrashedTest::__pest_evaluable_it_can_filter_only_trashed_by_scope_directly":0.003,"P\\Tests\\FiltersTrashedTest::__pest_evaluable_it_can_filter_with_trashed":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_does_not_require_includes":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_handle_empty_includes":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_model_relations":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_model_relations_by_alias":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_an_includes_callback":0.004,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_an_includes_count":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_allowing_an_include_also_allows_the_include_count":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_an_includes_exists":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_allowing_an_include_also_allows_the_include_exists":0.004,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_nested_model_relations":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_nested_model_relations_by_alias":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_model_relations_from_nested_model_relations":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_allowing_a_nested_include_only_allows_the_include_count_for_the_first_level":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_allowing_a_nested_include_only_allows_the_include_exists_for_the_first_level":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_morph_model_relations":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_reverse_morph_model_relations":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_camel_case_includes":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_models_on_an_empty_collection":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_guards_against_invalid_includes":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_does_not_throw_invalid_include_query_exception_when_disable_in_config":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_allow_multiple_includes":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_allow_multiple_includes_as_an_array":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_remove_duplicate_includes_from_nested_includes":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_multiple_model_relations":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_query_included_many_to_many_relationships":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_returns_correct_id_when_including_many_to_many_relationship":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_an_invalid_include_query_exception_contains_the_unknown_and_allowed_includes":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_alias_multiple_allowed_includes":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_custom_include_class":0.003,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_custom_include_class_by_alias":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_take_an_argument_for_custom_column_name_resolution":0.002,"P\\Tests\\IncludeTest::__pest_evaluable_it_can_include_a_custom_base_query_with_select":0.003,"P\\Tests\\QueryBuilderEndpointTest::__pest_evaluable_it_can_instantiate_the_query_builder_and_filter_the_query_for_an_actual_api_request":0.017,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_filter_nested_arrays":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_empty_filters_recursively":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_map_true_and_false_as_booleans_recursively":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_the_sort_query_param_from_the_request":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_the_sort_query_param_from_the_request_body":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_different_sort_query_parameter_name":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_return_an_empty_collection_when_no_sort_query_param_is_specified":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_multiple_sort_parameters_from_the_request":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_return_an_empty_collection_when_no_sort_query_params_are_specified":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_the_filter_query_params_from_the_request":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_the_filter_query_params_from_the_request_body":0.008,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_use_different_filter_query_parameter_name":0.004,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_use_null_as_the_filter_query_parameter_name":0.005,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_empty_filters":0.004,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_return_an_empty_collection_when_no_filter_query_params_are_specified":0.005,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_map_true_and_false_as_booleans_when_given_in_a_filter_query_string":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_map_comma_separated_values_as_arrays_when_given_in_a_filter_query_string":0.004,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_map_array_in_filter_recursively_when_given_in_a_filter_query_string":0.005,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_map_comma_separated_values_as_arrays_when_given_in_a_filter_query_string_and_get_those_by_key":0.005,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_the_include_query_params_from_the_request":0.004,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_the_include_from_the_request_body":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_different_include_query_parameter_name":0.002,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_return_an_empty_collection_when_no_include_query_params_are_specified":0.007,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_requested_fields":0.004,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_requested_fields_without_a_table_name":0.003,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_nested_fields":0.004,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_nested_fields_from_a_string_fields_request":0.005,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_requested_fields_from_the_request_body":0.006,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_different_fields_parameter_name":0.011,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_the_append_query_params_from_the_request":0.004,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_different_append_query_parameter_name":0.004,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_will_return_an_empty_collection_when_no_append_query_params_are_specified":0.005,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_can_get_the_append_query_params_from_the_request_body":0.006,"P\\Tests\\QueryBuilderRequestTest::__pest_evaluable_it_takes_custom_delimiters_for_splitting_request_parameters":0.004,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_be_given_an_eloquent_query_using_where":0.002,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_be_given_an_eloquent_query_using_select":0.002,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_be_given_a_belongs_to_many_relation_query":0.004,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_be_given_a_belongs_to_many_relation_query_with_pivot":0.003,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_be_given_a_model_class_name":0.002,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_not_be_given_a_string_that_is_not_a_class_name":0.002,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_not_be_given_an_object_that_is_neither_relation_nor_eloquent_builder":0.002,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_will_determine_the_request_when_its_not_given":0.003,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_query_soft_deletes":0.007,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_query_global_scopes":0.004,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_keeps_eager_loaded_relationships_from_the_base_query":0.008,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_keeps_local_macros_added_to_the_base_query":0.005,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_keeps_the_on_delete_callback_added_to_the_base_query":0.004,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_query_local_scopes":0.002,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_executes_the_same_query_regardless_of_the_order_of_applied_filters_or_sorts":0.002,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_can_filter_when_sorting_by_joining_a_related_model_which_contains_the_same_field_name":0.003,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_queries_the_correct_data_for_a_relationship_query":0.005,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_does_not_lose_pivot_values_with_belongs_to_many_relation":0.005,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_clones_the_subject_upon_cloning":0.004,"P\\Tests\\QueryBuilderTest::__pest_evaluable_it_supports_clone_as_method":0.005,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_filter_related_model_property":0.003,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_filter_results_based_on_the_partial_existence_of_a_property_in_an_array":0.003,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_filter_models_and_return_an_empty_collection":0.002,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_filter_related_nested_model_property":0.002,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_filter_related_model_and_related_nested_model_property":0.003,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_filter_results_based_on_the_existence_of_a_property_in_an_array":0.003,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_filter_and_reject_results_by_exact_property":0.003,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_disable_exact_filtering_based_on_related_model_properties":0.002,"P\\Tests\\RelationFilterTest::__pest_evaluable_it_can_disable_partial_filtering_based_on_related_model_properties":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_a_query_ascending":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_has_the_allowed_sorts_property_set_even_if_no_sorts_are_requested":0.005,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_a_query_descending":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_a_query_by_alias":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_wont_sort_by_columns_that_werent_allowed_first":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_can_allow_a_descending_sort_by_still_sort_ascending":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_a_query_by_a_related_property":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_by_json_property_if_its_an_allowed_sort":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_by_sketchy_alias_if_its_an_allowed_sort":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_a_query_with_custom_select":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_a_chunk_query":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_can_guard_against_sorts_that_are_not_allowed":0.005,"P\\Tests\\SortTest::__pest_evaluable_it_will_throw_an_exception_if_a_sort_property_is_not_allowed":0.007,"P\\Tests\\SortTest::__pest_evaluable_it_does_not_throw_invalid_sort_query_exception_when_disable_in_config":0.002,"P\\Tests\\SortTest::__pest_evaluable_an_invalid_sort_query_exception_contains_the_unknown_and_allowed_sorts":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_wont_sort_if_no_sort_query_parameter_is_given":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_wont_sort_sketchy_sort_requests":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_uses_default_sort_parameter_when_no_sort_was_requested":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_doesnt_use_the_default_sort_parameter_when_a_sort_was_requested":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_allows_default_custom_sort_class_parameter":0.004,"P\\Tests\\SortTest::__pest_evaluable_it_uses_default_descending_sort_parameter":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_allows_multiple_default_sort_parameters":0.004,"P\\Tests\\SortTest::__pest_evaluable_it_allows_multiple_default_sort_parameters_in_an_array":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_can_allow_multiple_sort_parameters":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_can_allow_multiple_sort_parameters_as_an_array":0.004,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_by_multiple_columns":0.007,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_by_a_custom_sort_class":0.004,"P\\Tests\\SortTest::__pest_evaluable_it_can_take_an_argument_for_custom_column_name_resolution":0.004,"P\\Tests\\SortTest::__pest_evaluable_it_sets_property_column_name_to_property_name_by_default":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_resolves_queries_using_property_column_name":0.004,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_descending_with_an_alias":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_does_not_add_sort_clauses_multiple_times":0.002,"P\\Tests\\SortTest::__pest_evaluable_given_a_default_sort_a_sort_alias_will_still_be_resolved":0.002,"P\\Tests\\SortTest::__pest_evaluable_late_specified_sorts_still_check_for_allowance":0.002,"P\\Tests\\SortTest::__pest_evaluable_it_can_sort_and_use_scoped_filters_at_the_same_time":0.003,"P\\Tests\\SortTest::__pest_evaluable_it_ignores_non_existing_sorts_before_adding_them_as_an_alias":0.002,"P\\Tests\\SortTest::__pest_evaluable_raw_sorts_do_not_get_purged_when_specifying_allowed_sorts":0.002,"P\\Tests\\SortTest::__pest_evaluable_the_default_direction_of_an_allow_sort_can_be_set":0.002,"P\\Tests\\SortsCallbackTest::__pest_evaluable_it_should_sort_by_closure":0.003,"P\\Tests\\SortsCallbackTest::__pest_evaluable_it_should_sort_by_array_callback":0.002}} \ No newline at end of file diff --git a/src/Filters/FiltersBeginsWithStrict.php b/src/Filters/FiltersBeginsWithStrict.php index 79beb83d..2b88dfa7 100644 --- a/src/Filters/FiltersBeginsWithStrict.php +++ b/src/Filters/FiltersBeginsWithStrict.php @@ -8,11 +8,11 @@ */ class FiltersBeginsWithStrict extends FiltersPartial implements Filter { - protected function getWhereRawParameters($value, string $property): array + protected function getWhereRawParameters($value, string $property, string $driver): array { return [ - "{$property} LIKE ?", - ["{$value}%"], + "{$property} LIKE ?".static::maybeSpecifyEscapeChar($driver), + [static::escapeLike($value).'%'], ]; } } diff --git a/src/Filters/FiltersEndsWithStrict.php b/src/Filters/FiltersEndsWithStrict.php index f4322702..6dc7cf80 100644 --- a/src/Filters/FiltersEndsWithStrict.php +++ b/src/Filters/FiltersEndsWithStrict.php @@ -8,11 +8,12 @@ */ class FiltersEndsWithStrict extends FiltersPartial implements Filter { - protected function getWhereRawParameters($value, string $property): array + protected function getWhereRawParameters($value, string $property, string $driver): array { + return [ - "{$property} LIKE ?", - ["%{$value}"], + "{$property} LIKE ?".static::maybeSpecifyEscapeChar($driver), + ['%'.static::escapeLike($value)], ]; } } diff --git a/src/Filters/FiltersPartial.php b/src/Filters/FiltersPartial.php index 5c0bf5ff..28e8496f 100644 --- a/src/Filters/FiltersPartial.php +++ b/src/Filters/FiltersPartial.php @@ -23,15 +23,16 @@ public function __invoke(Builder $query, $value, string $property) } $wrappedProperty = $query->getQuery()->getGrammar()->wrap($query->qualifyColumn($property)); + $databaseDriver = $this->getDatabaseDriver($query); if (is_array($value)) { if (count(array_filter($value, 'strlen')) === 0) { return $query; } - $query->where(function (Builder $query) use ($value, $wrappedProperty) { + $query->where(function (Builder $query) use ($databaseDriver, $value, $wrappedProperty) { foreach (array_filter($value, 'strlen') as $partialValue) { - [$sql, $bindings] = $this->getWhereRawParameters($partialValue, $wrappedProperty); + [$sql, $bindings] = $this->getWhereRawParameters($partialValue, $wrappedProperty, $databaseDriver); $query->orWhereRaw($sql, $bindings); } }); @@ -39,21 +40,26 @@ public function __invoke(Builder $query, $value, string $property) return; } - [$sql, $bindings] = $this->getWhereRawParameters($value, $wrappedProperty); + [$sql, $bindings] = $this->getWhereRawParameters($value, $wrappedProperty, $databaseDriver); $query->whereRaw($sql, $bindings); } - protected function getWhereRawParameters($value, string $property): array + protected function getDatabaseDriver(Builder $query): string + { + return $query->getConnection()->getDriverName(); + } + + protected function getWhereRawParameters($value, string $property, string $driver): array { $value = mb_strtolower((string) $value, 'UTF8'); return [ - "LOWER({$property}) LIKE ?".self::maybeSpecifyEscapeChar(), + "LOWER({$property}) LIKE ?".self::maybeSpecifyEscapeChar($driver), ['%'.self::escapeLike($value).'%'], ]; } - private static function escapeLike(string $value): string + protected static function escapeLike(string $value): string { return str_replace( ['\\', '_', '%'], @@ -62,10 +68,14 @@ private static function escapeLike(string $value): string ); } - private static function maybeSpecifyEscapeChar(): string + /** + * @param 'sqlite'|'pgsql'|'sqlsrc'|'mysql' $driver + * @return string + */ + protected static function maybeSpecifyEscapeChar(string $driver): string { - if(! in_array(DB::getDriverName(), ["sqlite","pgsql","sqlsrv"])) { - return ""; + if(!in_array($driver, ['sqlite','pgsql','sqlsrv'])){ + return ''; } return " ESCAPE '\'"; From caa8467fa9e127ba7ea9e0aac93c71324365bae2 Mon Sep 17 00:00:00 2001 From: AlexVanderbist Date: Fri, 10 May 2024 08:19:35 +0000 Subject: [PATCH 5/6] Fix styling --- src/Filters/FiltersPartial.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Filters/FiltersPartial.php b/src/Filters/FiltersPartial.php index 28e8496f..21953ac5 100644 --- a/src/Filters/FiltersPartial.php +++ b/src/Filters/FiltersPartial.php @@ -3,7 +3,6 @@ namespace Spatie\QueryBuilder\Filters; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\DB; /** * @template TModelClass of \Illuminate\Database\Eloquent\Model @@ -74,7 +73,7 @@ protected static function escapeLike(string $value): string */ protected static function maybeSpecifyEscapeChar(string $driver): string { - if(!in_array($driver, ['sqlite','pgsql','sqlsrv'])){ + if(! in_array($driver, ['sqlite','pgsql','sqlsrv'])) { return ''; } From 47d9561d18927cde602edb51954ced32e3dcb8fe Mon Sep 17 00:00:00 2001 From: AlexVanderbist Date: Fri, 10 May 2024 08:22:34 +0000 Subject: [PATCH 6/6] Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ebfe784..66046c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to `laravel-query-builder` will be documented in this file +## 5.8.1 - 2024-05-10 + +### What's Changed + +* Fix typo by @justinkekeocha in https://github.com/spatie/laravel-query-builder/pull/926 +* List query-builder-ts front-end implementation package by @rogervila in https://github.com/spatie/laravel-query-builder/pull/925 +* Fix incorrect escape character in SQL for LIKE query in partial filter by @Talpx1 in https://github.com/spatie/laravel-query-builder/pull/927 + +### New Contributors + +* @justinkekeocha made their first contribution in https://github.com/spatie/laravel-query-builder/pull/926 +* @rogervila made their first contribution in https://github.com/spatie/laravel-query-builder/pull/925 +* @Talpx1 made their first contribution in https://github.com/spatie/laravel-query-builder/pull/927 + +**Full Changelog**: https://github.com/spatie/laravel-query-builder/compare/5.8.0...5.8.1 + ## 5.8.0 - 2024-02-06 ### What's Changed