diff --git a/composer.json b/composer.json
index 97007233277cd3e14e8060bd726e706f47b4fdb0..c3bfb4082f7c3039f04fc2d6905e846113225750 100644
--- a/composer.json
+++ b/composer.json
@@ -162,8 +162,8 @@
         "drupal/redis": "1.0",
         "drupal/roleassign": "1.0.0-beta1",
         "drupal/scheduler": "1.3",
-        "drupal/search_api": "1.18",
-        "drupal/search_api_db": "1.18",
+        "drupal/search_api": "1.19",
+        "drupal/search_api_db": "1.19",
         "drupal/simple_gmap": "3.0",
         "drupal/simple_megamenu": "1.0-beta3",
         "drupal/simple_sitemap": "3.8",
diff --git a/composer.lock b/composer.lock
index 3f5bcca64b133e977bdca278117bcf288ceda350..84b2db9f87cf9e0a04147bf7f9d417ffaa546744 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "cf9dbe7bb1771287f68ffec2f43f12ce",
+    "content-hash": "f6c9d59e64838eb70131caf7be60b0d1",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -7275,17 +7275,17 @@
         },
         {
             "name": "drupal/search_api",
-            "version": "1.18.0",
+            "version": "1.19.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/search_api.git",
-                "reference": "8.x-1.18"
+                "reference": "8.x-1.19"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/search_api-8.x-1.18.zip",
-                "reference": "8.x-1.18",
-                "shasum": "6cf1d6820ba55891e204bac40b6031ed15db482a"
+                "url": "https://ftp.drupal.org/files/projects/search_api-8.x-1.19.zip",
+                "reference": "8.x-1.19",
+                "shasum": "5654e9d02117e28c585d89a25ea3cc40d20c5019"
             },
             "require": {
                 "drupal/core": "^8.8 || ^9"
@@ -7306,8 +7306,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.18",
-                    "datestamp": "1605204423",
+                    "version": "8.x-1.19",
+                    "datestamp": "1612192040",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -7347,7 +7347,7 @@
         },
         {
             "name": "drupal/search_api_db",
-            "version": "1.18.0",
+            "version": "1.19.0",
             "require": {
                 "drupal/core": "^8.8 || ^9",
                 "drupal/search_api": "*"
@@ -7355,8 +7355,8 @@
             "type": "metapackage",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.18",
-                    "datestamp": "1605204423",
+                    "version": "8.x-1.19",
+                    "datestamp": "1612192040",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index f42a9ad2d804fd18186a8fe5567ba185e91b3695..7c7d590d761c56d020d209bf598286b6074609c2 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -7499,18 +7499,18 @@
     },
     {
         "name": "drupal/search_api",
-        "version": "1.18.0",
-        "version_normalized": "1.18.0.0",
+        "version": "1.19.0",
+        "version_normalized": "1.19.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/search_api.git",
-            "reference": "8.x-1.18"
+            "reference": "8.x-1.19"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/search_api-8.x-1.18.zip",
-            "reference": "8.x-1.18",
-            "shasum": "6cf1d6820ba55891e204bac40b6031ed15db482a"
+            "url": "https://ftp.drupal.org/files/projects/search_api-8.x-1.19.zip",
+            "reference": "8.x-1.19",
+            "shasum": "5654e9d02117e28c585d89a25ea3cc40d20c5019"
         },
         "require": {
             "drupal/core": "^8.8 || ^9"
@@ -7531,8 +7531,8 @@
         "type": "drupal-module",
         "extra": {
             "drupal": {
-                "version": "8.x-1.18",
-                "datestamp": "1605204423",
+                "version": "8.x-1.19",
+                "datestamp": "1612192040",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
@@ -7573,8 +7573,8 @@
     },
     {
         "name": "drupal/search_api_db",
-        "version": "1.18.0",
-        "version_normalized": "1.18.0.0",
+        "version": "1.19.0",
+        "version_normalized": "1.19.0.0",
         "require": {
             "drupal/core": "^8.8 || ^9",
             "drupal/search_api": "*"
@@ -7582,8 +7582,8 @@
         "type": "metapackage",
         "extra": {
             "drupal": {
-                "version": "8.x-1.18",
-                "datestamp": "1605204423",
+                "version": "8.x-1.19",
+                "datestamp": "1612192040",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
diff --git a/web/modules/search_api/CHANGELOG.txt b/web/modules/search_api/CHANGELOG.txt
index d4b5c72b49f0df2f2c59a7b9f7f9f6efc45d9a1c..55608ea088571f192614480464d581e4a2d5e04b 100644
--- a/web/modules/search_api/CHANGELOG.txt
+++ b/web/modules/search_api/CHANGELOG.txt
@@ -1,3 +1,31 @@
+Search API 1.19 (2021-02-01):
+-----------------------------
+- #3182306 by drunken monkey: Improved output from the "rebuild tracker" batch
+  job.
+- #3111383 by liquidcms, drunken monkey: Fixed problems with exposed, grouped
+  Views date filters.
+- #3127099 by Grimreaper, drunken monkey: Added option to expose searched
+  fields in Views fulltext filter.
+- #2944371 by Upchuk, jasonschweb, drunken monkey: Fixed random sort for DB
+  backend.
+- Issue #3194016 by mkalkbrenner, borisson_: Fixed PHP 8 compatibility.
+- #3181827 by brunodbo, drunken monkey: Fixed wrong @var type doc in
+  ContentEntityTrackingManager.
+- #3179045 by sebish, joelpittet, drunken monkey: Fixed parameter type hint in
+  ContentEntityTrackingManager.
+- #3178417 by SivaprasadC, drunken monkey: Fixed two typos in the Database
+  backend plugin class.
+- #3174778 by drunken monkey: Fixed some encoding errors.
+- #3178941 by drunken monkey, cspitzlay, MegaChriz, ooziedie, bakulahluwalia:
+  Fixed fatal error when saving entities with certain setups.
+- #3036504 by kfritsche, drunken monkey: Fixed memory issues during indexing
+  via Drush.
+- #3136277 by drunken monkey, pfrenssen, Berdir, dwinters, calmforce: Fixed
+  issue with modifying condition group via pre-execute event/hook.
+- #3174657 by drunken monkey: Fixed "keep facets" feature for search views when
+  facet source wasn't saved.
+- #3181936 by drunken monkey: Fixed tests on Drupal 9.1.x HEAD.
+
 Search API 1.18 (2020-10-22):
 -----------------------------
 - #3153153 by mkalkbrenner, cristiroma, drunken monkey: Fixed serialization of
diff --git a/web/modules/search_api/config/schema/search_api.views.schema.yml b/web/modules/search_api/config/schema/search_api.views.schema.yml
index 55de809f20134ffe94a5e6297b5aa1d360d6c08e..94511cb8522ee42d9cf2772cb07a7b3ccc53cd42 100644
--- a/web/modules/search_api/config/schema/search_api.views.schema.yml
+++ b/web/modules/search_api/config/schema/search_api.views.schema.yml
@@ -256,9 +256,15 @@ views.filter.search_api_fulltext:
       type: mapping
       label: 'Exposed'
       mapping:
+        expose_fields:
+          type: boolean
+          label: 'Expose the list of searched fields'
         placeholder:
           type: label
           label: 'Placeholder'
+        searched_fields_id:
+          type: string
+          label: 'Searched fields identifier'
 
 views.filter.search_api_language:
   type: views.filter.language
diff --git a/web/modules/search_api/modules/search_api_db/search_api_db.info.yml b/web/modules/search_api/modules/search_api_db/search_api_db.info.yml
index 4141fde16c0dac18b4491fa4af96f816813ccd1d..dd719947e80b24a6334374deb1bbc9f902171a48 100644
--- a/web/modules/search_api/modules/search_api_db/search_api_db.info.yml
+++ b/web/modules/search_api/modules/search_api_db/search_api_db.info.yml
@@ -6,7 +6,7 @@ core_version_requirement: ^8.8 || ^9
 dependencies:
   - search_api:search_api
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.info.yml b/web/modules/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.info.yml
index 86b46a70d3fe9f138c35fa3bdb430d60829a63f8..7164db811cd4c406d79986810fd8295ec3b47a95 100644
--- a/web/modules/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.info.yml
+++ b/web/modules/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.info.yml
@@ -13,7 +13,7 @@ dependencies:
   - drupal:views
   - search_api:search_api_db
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/DatabaseCompatibilityHandlerInterface.php b/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/DatabaseCompatibilityHandlerInterface.php
index d96431d5a924ea1f1fda08c6522ed7e1d04acc72..e13211caa08d0103114006b583c26de51a4046c5 100644
--- a/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/DatabaseCompatibilityHandlerInterface.php
+++ b/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/DatabaseCompatibilityHandlerInterface.php
@@ -3,6 +3,7 @@
 namespace Drupal\search_api_db\DatabaseCompatibility;
 
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Query\SelectInterface;
 
 /**
  * Bundles methods for resolving DBMS-specific differences.
@@ -70,4 +71,12 @@ public function alterNewTable($table, $type = 'text');
    */
   public function preprocessIndexValue($value, $type = 'text');
 
+  /**
+   * Applies a random sort to the query.
+   *
+   * @param \Drupal\Core\Database\Query\SelectInterface $query
+   *   The search query.
+   */
+  public function orderByRandom(SelectInterface $query);
+
 }
diff --git a/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/GenericDatabase.php b/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/GenericDatabase.php
index 5db84b2f9f095c85deb837a0c97ef8c1fdbce7d0..b083899f87bc4f0d2ad123602f85d23b8256e13b 100644
--- a/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/GenericDatabase.php
+++ b/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/GenericDatabase.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Transliteration\TransliterationInterface;
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Query\SelectInterface;
 
 /**
  * Represents any database for which no specifics are known.
@@ -68,4 +69,12 @@ public function preprocessIndexValue($value, $type = 'text') {
     return mb_strtolower($this->transliterator->transliterate($value));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function orderByRandom(SelectInterface $query) {
+    $alias = $query->addExpression('random()', 'random_order_field');
+    $query->orderBy($alias);
+  }
+
 }
diff --git a/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/MySql.php b/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/MySql.php
index 08112f206deacd3442619e389929d66525736067..8ce1a069988cccda6bb6b5b629876db1be175d93 100644
--- a/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/MySql.php
+++ b/web/modules/search_api/modules/search_api_db/src/DatabaseCompatibility/MySql.php
@@ -3,6 +3,7 @@
 namespace Drupal\search_api_db\DatabaseCompatibility;
 
 use Drupal\Core\Database\DatabaseException;
+use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\search_api\SearchApiException;
 
 /**
@@ -36,4 +37,12 @@ public function alterNewTable($table, $type = 'text') {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function orderByRandom(SelectInterface $query) {
+    $alias = $query->addExpression('rand()', 'random_order_field');
+    $query->orderBy($alias);
+  }
+
 }
diff --git a/web/modules/search_api/modules/search_api_db/src/Plugin/search_api/backend/Database.php b/web/modules/search_api/modules/search_api_db/src/Plugin/search_api/backend/Database.php
index be0254cdbd1b9e58a24f3743d08554e9d1130b07..bbc13bacfbc77f08095bac97cfd998b9ccce50e0 100644
--- a/web/modules/search_api/modules/search_api_db/src/Plugin/search_api/backend/Database.php
+++ b/web/modules/search_api/modules/search_api_db/src/Plugin/search_api/backend/Database.php
@@ -591,6 +591,7 @@ public function getSupportedFeatures() {
       'search_api_autocomplete',
       'search_api_facets',
       'search_api_facets_operator_or',
+      'search_api_random_sort',
     ];
   }
 
@@ -850,7 +851,7 @@ protected function createFieldTable(FieldInterface $field = NULL, array $db = []
     // index with the same as the first table, which conflicts in SQLite.
     //
     // The core issue addressing this (https://www.drupal.org/node/1008128) was
-    // closed as it fixed the PostgresSQL part. The SQLite fix is added in
+    // closed as it fixed the PostgreSQL part. The SQLite fix is added in
     // https://www.drupal.org/node/2625664
     // We prevent this by adding an extra underscore (which is also the proposed
     // solution in the original core issue).
@@ -2356,7 +2357,7 @@ protected function getTableAlias(array $field, SelectInterface $db_query, $new_j
   protected function preQuery(SelectInterface &$db_query, QueryInterface $query) {}
 
   /**
-   * Adds the approiate "ORDER BY" statements to a search database query.
+   * Adds the appropriate "ORDER BY" statements to a search database query.
    *
    * @param \Drupal\search_api\Query\QueryInterface $query
    *   The search query whose sorts should be applied.
@@ -2387,6 +2388,11 @@ protected function setQuerySort(QueryInterface $query, SelectInterface $db_query
           continue;
         }
 
+        if ($field_name == 'search_api_random') {
+          $this->dbmsCompatibility->orderByRandom($db_query);
+          continue;
+        }
+
         if (!isset($fields[$field_name])) {
           throw new SearchApiException("Trying to sort on unknown field '$field_name'.");
         }
diff --git a/web/modules/search_api/modules/search_api_db/tests/search_api_db_test_autocomplete/search_api_db_test_autocomplete.info.yml b/web/modules/search_api/modules/search_api_db/tests/search_api_db_test_autocomplete/search_api_db_test_autocomplete.info.yml
index 41ebc400d4cb772751204d2b3b94b8258a1dc61b..5a1670123a911c2bfef935b33fbb35b5b1f3f070 100644
--- a/web/modules/search_api/modules/search_api_db/tests/search_api_db_test_autocomplete/search_api_db_test_autocomplete.info.yml
+++ b/web/modules/search_api/modules/search_api_db/tests/search_api_db_test_autocomplete/search_api_db_test_autocomplete.info.yml
@@ -8,7 +8,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/modules/search_api_db/tests/src/Kernel/BackendTest.php b/web/modules/search_api/modules/search_api_db/tests/src/Kernel/BackendTest.php
index 0d47c6e2a9d9f7c59eb740dac26d5be716921ed5..cbbc19e9b9e46a8a29cedd399bacd0c111efeb9f 100644
--- a/web/modules/search_api/modules/search_api_db/tests/src/Kernel/BackendTest.php
+++ b/web/modules/search_api/modules/search_api_db/tests/src/Kernel/BackendTest.php
@@ -99,6 +99,7 @@ public function setUp() {
    */
   protected function checkBackendSpecificFeatures() {
     $this->checkMultiValuedInfo();
+    $this->searchWithRandom();
     $this->setServerMatchMode();
     $this->searchSuccessPartial();
     $this->setServerMatchMode('prefix');
@@ -259,6 +260,36 @@ protected function setServerMatchMode($match_mode = 'partial') {
     $this->resetEntityCache();
   }
 
+  /**
+   * Tests whether random searches work.
+   */
+  protected function searchWithRandom() {
+    // Run the query 5 times, using random sorting as the first sort and verify
+    // that the results are not always the same.
+    $first_result = NULL;
+    $second_result = NULL;
+    for ($i = 1; $i <= 5; $i++) {
+      $results = $this->buildSearch('foo', [], NULL, FALSE)
+        ->sort('search_api_random')
+        ->sort('id')
+        ->execute();
+
+      $result_ids = array_keys($results->getResultItems());
+      if ($first_result === NULL) {
+        $first_result = $second_result = $result_ids;
+      }
+      elseif ($result_ids !== $first_result) {
+        $second_result = $result_ids;
+      }
+
+      // Make sure the search still returned the expected items.
+      $this->assertCount(4, $result_ids);
+      sort($result_ids);
+      $this->assertEquals($this->getItemIds([1, 2, 4, 5]), $result_ids);
+    }
+    $this->assertNotEquals($first_result, $second_result);
+  }
+
   /**
    * Tests whether partial searches work.
    */
diff --git a/web/modules/search_api/search_api.info.yml b/web/modules/search_api/search_api.info.yml
index 0b7ad670ee6a94b7100772df533b871bd891388f..9c0c3ebef770359ab7b3f2a71089023f5f30c5a1 100644
--- a/web/modules/search_api/search_api.info.yml
+++ b/web/modules/search_api/search_api.info.yml
@@ -5,7 +5,7 @@ package: Search
 core_version_requirement: ^8.8 || ^9
 configure: search_api.overview
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/search_api.module b/web/modules/search_api/search_api.module
index e10bc0bf4695bd2d07dd1ab3265e7806c160f8e3..c68c16f0dc7806c124d497469122ab2fc9258678 100644
--- a/web/modules/search_api/search_api.module
+++ b/web/modules/search_api/search_api.module
@@ -576,19 +576,23 @@ function search_api_form_views_exposed_form_alter(&$form, FormStateInterface $fo
     && $query_plugin instanceof SearchApiQuery
     && \Drupal::moduleHandler()->moduleExists('facets');
   if ($preserve_facets) {
-    // Retrieve the facet source.
+    $filter_key = 'f';
+
+    // Attempt to retrieve the facet source to use the actual facets filter
+    // parameter as configured by the admin. (Facet source config entities are
+    // not always actually saved in the storage, if the admin didn't change
+    // their settings.)
     $query = $query_plugin->getSearchApiQuery();
     $display_id = $query->getSearchId(FALSE);
     $facet_source_id = str_replace(':', '__', 'search_api:' . $display_id);
     $facet_source = \Drupal::entityTypeManager()
       ->getStorage('facets_facet_source')
       ->load($facet_source_id);
-    if (!$facet_source) {
-      return;
+    if ($facet_source) {
+      $filter_key = $facet_source->getFilterKey() ?: 'f';
     }
 
     // Get the active facet filters from the query parameters.
-    $filter_key = $facet_source->getFilterKey() ?: 'f';
     $filters = \Drupal::request()->query->get($filter_key, []);
 
     // Do not iterate over facet filters if the parameter is not an array.
diff --git a/web/modules/search_api/src/Entity/Index.php b/web/modules/search_api/src/Entity/Index.php
index 221b3fb904dc746ff49d93efb6f50c01dfb7aa8f..090131de722526a908c3e77db56441c3c222ccb7 100644
--- a/web/modules/search_api/src/Entity/Index.php
+++ b/web/modules/search_api/src/Entity/Index.php
@@ -1008,7 +1008,6 @@ public function indexSpecificItems(array $search_objects) {
       $description = 'This hook is deprecated in search_api:8.x-1.14 and is removed from search_api:2.0.0. Please use the "search_api.items_indexed" event instead. See https://www.drupal.org/node/3059866';
       \Drupal::moduleHandler()->invokeAllDeprecated($description, 'search_api_items_indexed', [$this, $processed_ids]);
 
-      /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
       $dispatcher = \Drupal::getContainer()->get('event_dispatcher');
       $dispatcher->dispatch(SearchApiEvents::ITEMS_INDEXED, new ItemsIndexedEvent($this, $processed_ids));
 
@@ -1016,6 +1015,13 @@ public function indexSpecificItems(array $search_objects) {
       Cache::invalidateTags(['search_api_list:' . $this->id]);
     }
 
+    // When indexing via Drush, multiple iterations of a batch will happen in
+    // the same PHP process, so the static cache will quickly fill up. To
+    // prevent this, clear it after each batch of items gets indexed.
+    if (function_exists('drush_backend_batch_process') && batch_get()) {
+      \Drupal::getContainer()->get('entity.memory_cache')->deleteAll();
+    }
+
     return $processed_ids;
   }
 
diff --git a/web/modules/search_api/src/Event/MappingForeignRelationshipsEvent.php b/web/modules/search_api/src/Event/MappingForeignRelationshipsEvent.php
index d700ac6c016b182f2dce8f8208353319626b1203..17216973ae992febdac7e49f523fd65147dc1218 100644
--- a/web/modules/search_api/src/Event/MappingForeignRelationshipsEvent.php
+++ b/web/modules/search_api/src/Event/MappingForeignRelationshipsEvent.php
@@ -67,6 +67,8 @@ public function getIndex(): IndexInterface {
    *   A (numerically keyed) array of foreign relationship mappings. Each
    *   sub-array here represents a single known relationship. Such sub-arrays
    *   will have the following structure:
+   *   - datasource: (string) The ID of the datasource which contains this
+   *     relationship.
    *   - entity_type: (string) Entity type that is referred to from the index.
    *   - bundles: (array) Optional array of particular entity bundles that are
    *     referred to from the index. Empty array here means index refers to
diff --git a/web/modules/search_api/src/Plugin/search_api/datasource/ContentEntity.php b/web/modules/search_api/src/Plugin/search_api/datasource/ContentEntity.php
index 06cceafc427bdbaf07f6df9ec4ab6252698c3306..deb86a82c67b0760d9df0f9b28dab53a2859c4c7 100644
--- a/web/modules/search_api/src/Plugin/search_api/datasource/ContentEntity.php
+++ b/web/modules/search_api/src/Plugin/search_api/datasource/ContentEntity.php
@@ -18,6 +18,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Logger\RfcLogLevel;
 use Drupal\Core\Plugin\PluginFormInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\State\StateInterface;
@@ -28,8 +29,9 @@
 use Drupal\field\FieldStorageConfigInterface;
 use Drupal\search_api\Datasource\DatasourcePluginBase;
 use Drupal\search_api\IndexInterface;
-use Drupal\search_api\Utility\Dependencies;
+use Drupal\search_api\LoggerTrait;
 use Drupal\search_api\Plugin\PluginFormTrait;
+use Drupal\search_api\Utility\Dependencies;
 use Drupal\search_api\Utility\FieldsHelperInterface;
 use Drupal\search_api\Utility\Utility;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -44,6 +46,7 @@
  */
 class ContentEntity extends DatasourcePluginBase implements PluginFormInterface {
 
+  use LoggerTrait;
   use PluginFormTrait;
 
   /**
@@ -157,6 +160,7 @@ public static function create(ContainerInterface $container, array $configuratio
     $datasource->setFieldsHelper($container->get('search_api.fields_helper'));
     $datasource->setState($container->get('state'));
     $datasource->setEntityMemoryCache($container->get('entity.memory_cache'));
+    $datasource->setLogger($container->get('logger.channel.search_api'));
 
     return $datasource;
   }
@@ -1039,6 +1043,11 @@ public function getAffectedItemsForEntityChange(EntityInterface $entity, array $
     $ids_to_reindex = [];
     $path_separator = IndexInterface::PROPERTY_PATH_SEPARATOR;
     foreach ($foreign_entity_relationship_map as $relation_info) {
+      // Ignore relationships belonging to other datasources.
+      if (!empty($relation_info['datasource'])
+          && $relation_info['datasource'] !== $this->getPluginId()) {
+        continue;
+      }
       // Check whether entity type and (if specified) bundles match the entity.
       if ($relation_info['entity_type'] !== $entity->getEntityTypeId()) {
         continue;
@@ -1073,7 +1082,29 @@ public function getAffectedItemsForEntityChange(EntityInterface $entity, array $
         try {
           $entity_ids = array_values($query->execute());
         }
-        catch (\Exception $e) {
+        // @todo Switch back to \Exception once Core bug #2893747 is fixed.
+        catch (\Throwable $e) {
+          // We don't want to catch all PHP \Error objects thrown, but just the
+          // ones caused by #2893747.
+          if (!($e instanceof \Exception)
+              && (get_class($e) !== \Error::class || $e->getMessage() !== 'Call to a member function getColumns() on bool')) {
+            throw $e;
+          }
+          $vars = [
+            '%index' => $this->index->label(),
+            '%entity_type' => $entity->getEntityType()->getLabel(),
+            '@entity_id' => $entity->id(),
+          ];
+          try {
+            $link = $entity->toLink($this->t('Go to changed %entity_type with ID "@entity_id"', $vars))
+              ->toString()->getGeneratedLink();
+          }
+          catch (\Throwable $e) {
+            // Ignore any errors here, it's not that important that the log
+            // message contains a link.
+            $link = NULL;
+          }
+          $this->logException($e, '%type while attempting to find indexed entities referencing changed %entity_type with ID "@entity_id" for index %index: @message in %function (line %line of %file).', $vars, RfcLogLevel::ERROR, $link);
           continue;
         }
         foreach ($entity_ids as $entity_id) {
diff --git a/web/modules/search_api/src/Plugin/search_api/datasource/ContentEntityTrackingManager.php b/web/modules/search_api/src/Plugin/search_api/datasource/ContentEntityTrackingManager.php
index d918a8ab973c176ec403cd9b264692d9ff8bd830..551d211652e93fdc34855063f22bbd348cd7c2bc 100644
--- a/web/modules/search_api/src/Plugin/search_api/datasource/ContentEntityTrackingManager.php
+++ b/web/modules/search_api/src/Plugin/search_api/datasource/ContentEntityTrackingManager.php
@@ -8,7 +8,7 @@
 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeManager;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\search_api\IndexInterface;
@@ -26,7 +26,7 @@ class ContentEntityTrackingManager {
   /**
    * The entity type manager.
    *
-   * @var \Drupal\Core\Entity\EntityTypeManager
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
   protected $entityTypeManager;
 
@@ -47,14 +47,14 @@ class ContentEntityTrackingManager {
   /**
    * Constructs a new class instance.
    *
-   * @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
    *   The entity type manager.
    * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
    *   The language manager.
    * @param \Drupal\search_api\Task\TaskManagerInterface $taskManager
    *   The task manager.
    */
-  public function __construct(EntityTypeManager $entityTypeManager, LanguageManagerInterface $languageManager, TaskManagerInterface $taskManager) {
+  public function __construct(EntityTypeManagerInterface $entityTypeManager, LanguageManagerInterface $languageManager, TaskManagerInterface $taskManager) {
     $this->entityTypeManager = $entityTypeManager;
     $this->languageManager = $languageManager;
     $this->taskManager = $taskManager;
@@ -366,7 +366,7 @@ public function indexUpdate(IndexInterface $index) {
    * Filters a set of datasource-specific item IDs.
    *
    * Returns only those item IDs that are valid for the given datasource and
-   * index. This method only checks the item language, though – whether an
+   * index. This method only checks the item language, though – whether an
    * entity with that ID actually exists, or whether it has a bundle included
    * for that datasource, is not verified.
    *
diff --git a/web/modules/search_api/src/Plugin/search_api/processor/Highlight.php b/web/modules/search_api/src/Plugin/search_api/processor/Highlight.php
index 975495616342c93ec6e6c1139c3a9d90ce1338ca..b13e805a6a60865b2a9f561f5ca23a80e7e8ef05 100644
--- a/web/modules/search_api/src/Plugin/search_api/processor/Highlight.php
+++ b/web/modules/search_api/src/Plugin/search_api/processor/Highlight.php
@@ -280,7 +280,7 @@ protected function addExcerpts(array $results, array $fulltext_fields, array $ke
       // We call array_merge() using call_user_func_array() to prevent having to
       // use it in a loop because it is a resource greedy construction.
       // @see https://github.com/kalessil/phpinspectionsea/blob/master/docs/performance.md#slow-array-function-used-in-loop
-      $text = call_user_func_array('array_merge', $item);
+      $text = call_user_func_array('array_merge', array_values($item));
       $item_keys = $keys;
 
       // If the backend already did highlighting and told us the exact keys it
diff --git a/web/modules/search_api/src/Plugin/views/cache/SearchApiTagCache.php b/web/modules/search_api/src/Plugin/views/cache/SearchApiTagCache.php
index 9527c82a117da22e073f32c1b223ad6396488f32..906d6c86c2ce91ef28da2685d36b6974d8e71f4f 100644
--- a/web/modules/search_api/src/Plugin/views/cache/SearchApiTagCache.php
+++ b/web/modules/search_api/src/Plugin/views/cache/SearchApiTagCache.php
@@ -3,6 +3,7 @@
 namespace Drupal\search_api\Plugin\views\cache;
 
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\views\Plugin\views\cache\Tag;
@@ -115,4 +116,15 @@ public function getRowCacheTags(ResultRow $row) {
     return $tags;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function alterCacheMetadata(CacheableMetadata $cache_metadata) {
+    // Allow modules that alter the query to add their cache metadata to the
+    // view.
+    $query = $this->getQuery()->getSearchApiQuery();
+    $query->preExecute();
+    $cache_metadata->addCacheableDependency($query);
+  }
+
 }
diff --git a/web/modules/search_api/src/Plugin/views/filter/SearchApiDate.php b/web/modules/search_api/src/Plugin/views/filter/SearchApiDate.php
index 494f965ed1099e36b4f8ec940deee840276f0940..dc8482c6390dabac8b0ebe771cf939af08db90ff 100644
--- a/web/modules/search_api/src/Plugin/views/filter/SearchApiDate.php
+++ b/web/modules/search_api/src/Plugin/views/filter/SearchApiDate.php
@@ -63,21 +63,6 @@ public function acceptExposedInput($input) {
       return TRUE;
     }
 
-    // Unfortunately, this is necessary due to a bug in our parent filter. See
-    // #2704077.
-    if (!empty($this->options['expose']['identifier'])) {
-      $value = &$input[$this->options['expose']['identifier']];
-      if (!is_array($value)) {
-        $value = [
-          'value' => $value,
-        ];
-      }
-      $value += [
-        'min' => '',
-        'max' => '',
-      ];
-    }
-
     $return = parent::acceptExposedInput($input);
 
     if (!$return) {
diff --git a/web/modules/search_api/src/Plugin/views/filter/SearchApiFulltext.php b/web/modules/search_api/src/Plugin/views/filter/SearchApiFulltext.php
index df5f705e75be0773cc9641d406fe16d3fc693fc4..f0c15bea36964dfb0221ed0bdb1e305d83cc6626 100644
--- a/web/modules/search_api/src/Plugin/views/filter/SearchApiFulltext.php
+++ b/web/modules/search_api/src/Plugin/views/filter/SearchApiFulltext.php
@@ -19,6 +19,13 @@ class SearchApiFulltext extends FilterPluginBase {
 
   use SearchApiFilterTrait;
 
+  /**
+   * The list of fields selected for the search.
+   *
+   * @var array
+   */
+  public $searchedFields = [];
+
   /**
    * The parse mode manager.
    *
@@ -126,10 +133,21 @@ public function defineOptions() {
     $options['min_length'] = ['default' => ''];
     $options['fields'] = ['default' => []];
     $options['expose']['contains']['placeholder'] = ['default' => ''];
+    $options['expose']['contains']['expose_fields'] = ['default' => FALSE];
+    $options['expose']['contains']['searched_fields_id'] = ['default' => ''];
 
     return $options;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultExposeOptions() {
+    parent::defaultExposeOptions();
+
+    $this->options['expose']['searched_fields_id'] = $this->options['id'] . '_searched_fields';
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -203,6 +221,58 @@ public function buildExposeForm(&$form, FormStateInterface $form_state) {
       '#size' => 40,
       '#description' => $this->t('Hint text that appears inside the field when empty.'),
     ];
+
+    $form['expose']['expose_fields'] = [
+      '#type' => 'checkbox',
+      '#default_value' => $this->options['expose']['expose_fields'],
+      '#title' => $this->t('Expose searched fields'),
+      '#description' => $this->t('Expose the list of searched fields. This allows users to narrow the search to the desired fields.'),
+    ];
+    $form['expose']['searched_fields_id'] = [
+      '#type' => 'textfield',
+      '#default_value' => $this->options['expose']['searched_fields_id'],
+      '#title' => $this->t('Searched fields identifier'),
+      '#size' => 40,
+      '#description' => $this->t('This will appear in the URL after the ? to identify this searched fields form element.'),
+      '#states' => [
+        'visible' => [
+          ':input[name="options[expose][expose_fields]"]' => ['checked' => TRUE],
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildExposedForm(&$form, FormStateInterface $form_state) {
+    parent::buildExposedForm($form, $form_state);
+
+    if (empty($this->options['exposed'])) {
+      return;
+    }
+
+    if ($this->options['expose']['expose_fields']) {
+      $fields = $this->getFulltextFields();
+      $configured_fields = $this->options['fields'];
+      // Only keep the configured fields.
+      if (!empty($configured_fields)) {
+        $configured_fields = array_flip($configured_fields);
+        $fields = array_intersect_key($fields, $configured_fields);
+      }
+
+      $searched_fields_identifier = $this->options['id'] . '_searched_fields';
+      if (!empty($this->options['expose']['searched_fields_id'])) {
+        $searched_fields_identifier = $this->options['expose']['searched_fields_id'];
+      }
+      $form[$searched_fields_identifier] = [
+        '#type' => 'select',
+        '#title' => $this->t('Search fields'),
+        '#options' => $fields,
+        '#multiple' => TRUE,
+        '#size' => min(count($fields), 5),
+      ];
+    }
   }
 
   /**
@@ -243,6 +313,13 @@ public function validateExposed(&$form, FormStateInterface $form_state) {
       return;
     }
 
+    // Store searched fields.
+    $searched_fields_identifier = $this->options['id'] . '_searched_fields';
+    if (!empty($this->options['expose']['searched_fields_id'])) {
+      $searched_fields_identifier = $this->options['expose']['searched_fields_id'];
+    }
+    $this->searchedFields = $form_state->getValue($searched_fields_identifier, []);
+
     $identifier = $this->options['expose']['identifier'];
     $input = &$form_state->getValue($identifier, '');
 
@@ -299,6 +376,10 @@ public function query() {
     }
     $fields = $this->options['fields'];
     $fields = $fields ?: array_keys($this->getFulltextFields());
+    // Override the search fields, if exposed.
+    if (!empty($this->searchedFields)) {
+      $fields = array_intersect($fields, $this->searchedFields);
+    }
     $query = $this->getQuery();
 
     // Save any keywords that were already set.
diff --git a/web/modules/search_api/src/Query/Query.php b/web/modules/search_api/src/Query/Query.php
index f6b419ae81ab58947e48d87e3ab1f06da984d124..3049a1a0509ddd21aecaff2bfcdfeec687b2e7a0 100644
--- a/web/modules/search_api/src/Query/Query.php
+++ b/web/modules/search_api/src/Query/Query.php
@@ -756,10 +756,6 @@ public function getOriginalQuery() {
    * {@inheritdoc}
    */
   public function getCacheContexts() {
-    // Call the pre-execute method to ensure that processors and modules have
-    // had the chance to alter the query and modify the cacheability metadata.
-    $this->preExecute();
-
     $contexts = $this->cacheContexts;
 
     foreach ($this->getIndex()->getDatasources() as $datasource) {
@@ -773,10 +769,6 @@ public function getCacheContexts() {
    * {@inheritdoc}
    */
   public function getCacheTags() {
-    // Call the pre-execute method to ensure that processors and modules have
-    // had the chance to alter the query and modify the cacheability metadata.
-    $this->preExecute();
-
     $tags = $this->cacheTags;
 
     // If the configuration of the search index changes we should invalidate the
@@ -791,10 +783,6 @@ public function getCacheTags() {
    * {@inheritdoc}
    */
   public function getCacheMaxAge() {
-    // Call the pre-execute method to ensure that processors and modules have
-    // had the chance to alter the query and modify the cacheability metadata.
-    $this->preExecute();
-
     return $this->cacheMaxAge;
   }
 
diff --git a/web/modules/search_api/src/Task/TaskManager.php b/web/modules/search_api/src/Task/TaskManager.php
index 78e80f107f5ec893a069a9a8bcae80f46004e0ba..a2595ee208f92716bbe443571a7f5e1c8be6608d 100644
--- a/web/modules/search_api/src/Task/TaskManager.php
+++ b/web/modules/search_api/src/Task/TaskManager.php
@@ -352,11 +352,13 @@ public function processBatch(array $task_ids, array $conditions, &$context) {
     $pending = $this->getTasksCount($conditions);
     $context['finished'] = 1 - $pending / $context['results']['total'];
     $executed = $context['results']['total'] - $pending;
-    $context['message'] = $this->formatPlural(
-      $executed,
-      'Successfully executed @count pending task.',
-      'Successfully executed @count pending tasks.'
-    );
+    if ($executed > 0) {
+      $context['message'] = $this->formatPlural(
+        $executed,
+        'Successfully executed @count pending task.',
+        'Successfully executed @count pending tasks.'
+      );
+    }
   }
 
   /**
diff --git a/web/modules/search_api/src/Utility/TrackingHelper.php b/web/modules/search_api/src/Utility/TrackingHelper.php
index 9d9ac87c4a69cd60676c3e958b33c8f5ae4402cb..088211ac0e92352e4714bd5a211a6ecfcf904907 100644
--- a/web/modules/search_api/src/Utility/TrackingHelper.php
+++ b/web/modules/search_api/src/Utility/TrackingHelper.php
@@ -102,10 +102,11 @@ public function trackReferencedEntityUpdate(EntityInterface $entity, bool $delet
       // Can't really happen, but play it safe to appease static code analysis.
     }
 
-    // Map of foreign entity relations. Will get lazily populated as soon as we
-    // actually need it.
-    $original = $deleted ? NULL : $entity->original ?? NULL;
+    // Original entity, if available.
+    $original = $deleted ? NULL : ($entity->original ?? NULL);
     foreach ($indexes as $index) {
+      // Map of foreign entity relations. Will get lazily populated as soon as
+      // we actually need it.
       $map = NULL;
       foreach ($index->getDatasources() as $datasource_id => $datasource) {
         if (!$datasource->canContainEntityReferences()) {
@@ -142,6 +143,8 @@ public function trackReferencedEntityUpdate(EntityInterface $entity, bool $delet
    *   A (numerically keyed) array of foreign relationship mappings. Each
    *   sub-array represents a single known relationship. Such sub-arrays will
    *   have the following structure:
+   *   - datasource: (string) The ID of the datasource which contains this
+   *     relationship.
    *   - entity_type: (string) The entity type that is referenced from the
    *     index.
    *   - bundles: (string[]) An optional array of particular entity bundles that
@@ -175,6 +178,7 @@ protected function getForeignEntityRelationsMap(IndexInterface $index): array {
       }
 
       $relation_info = [
+        'datasource' => $datasource->getPluginId(),
         'entity_type' => NULL,
         'bundles' => NULL,
         'property_path_to_foreign_entity' => NULL,
@@ -216,6 +220,7 @@ protected function getForeignEntityRelationsMap(IndexInterface $index): array {
             && $relation_info['entity_type'] !== $entity_reference['entity_type']) {
           $relation_info = $entity_reference;
           $relation_info['property_path_to_foreign_entity'] = implode(IndexInterface::PROPERTY_PATH_SEPARATOR, $seen_path_chunks);
+          $relation_info['datasource'] = $datasource->getPluginId();
         }
 
         if ($property_definition instanceof ComplexDataDefinitionInterface) {
diff --git a/web/modules/search_api/tests/search_api_test/search_api_test.info.yml b/web/modules/search_api/tests/search_api_test/search_api_test.info.yml
index 08921e779d5e89065f05a77affd86cff43ffcf43..2335bffafab53899da388a396be4cb13ca1a3c7d 100644
--- a/web/modules/search_api/tests/search_api_test/search_api_test.info.yml
+++ b/web/modules/search_api/tests/search_api_test/search_api_test.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_bulk_form/search_api_test_bulk_form.info.yml b/web/modules/search_api/tests/search_api_test_bulk_form/search_api_test_bulk_form.info.yml
index cdc5011309df62dba5e8644af80308d380189db6..c1e2e3d925062d21772248829d7bcbbe388e52ac 100644
--- a/web/modules/search_api/tests/search_api_test_bulk_form/search_api_test_bulk_form.info.yml
+++ b/web/modules/search_api/tests/search_api_test_bulk_form/search_api_test_bulk_form.info.yml
@@ -10,7 +10,7 @@ dependencies:
 core_version_requirement: ^8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_db/search_api_test_db.info.yml b/web/modules/search_api/tests/search_api_test_db/search_api_test_db.info.yml
index ad96200c3bc0335e80d9eb313c765e241fdc2f29..b859571a7433e7041ec4788f860247ff7fdae721 100644
--- a/web/modules/search_api/tests/search_api_test_db/search_api_test_db.info.yml
+++ b/web/modules/search_api/tests/search_api_test_db/search_api_test_db.info.yml
@@ -8,7 +8,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_events/search_api_test_events.info.yml b/web/modules/search_api/tests/search_api_test_events/search_api_test_events.info.yml
index 2528bdc72a47f341c1ea521bd6ee11c9e2ddfad0..88accc5a7103cfeeb9267d84a989b7f9335f399c 100644
--- a/web/modules/search_api/tests/search_api_test_events/search_api_test_events.info.yml
+++ b/web/modules/search_api/tests/search_api_test_events/search_api_test_events.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_example_content/search_api_test_example_content.info.yml b/web/modules/search_api/tests/search_api_test_example_content/search_api_test_example_content.info.yml
index 4b5356a2283901d9839fefe6ee0c4ba7d0ab8c1c..15cd563884e79c9fc8494f13f23d350400ddf9e3 100644
--- a/web/modules/search_api/tests/search_api_test_example_content/search_api_test_example_content.info.yml
+++ b/web/modules/search_api/tests/search_api_test_example_content/search_api_test_example_content.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_example_content_references/search_api_test_example_content_references.info.yml b/web/modules/search_api/tests/search_api_test_example_content_references/search_api_test_example_content_references.info.yml
index e87cf643630158a53b653e8541578c7713f22cf4..6e096ca44afff79934c2d32fc6805de01d5b0a1d 100644
--- a/web/modules/search_api/tests/search_api_test_example_content_references/search_api_test_example_content_references.info.yml
+++ b/web/modules/search_api/tests/search_api_test_example_content_references/search_api_test_example_content_references.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_excerpt_field/config/install/views.view.search_api_test_excerpt_field.yml b/web/modules/search_api/tests/search_api_test_excerpt_field/config/install/views.view.search_api_test_excerpt_field.yml
index b19e1871c241e702076fed3bd450ae3ba7f11d38..03184ab3f901dbfafdc6031b0e4fc59bfabe3f79 100644
--- a/web/modules/search_api/tests/search_api_test_excerpt_field/config/install/views.view.search_api_test_excerpt_field.yml
+++ b/web/modules/search_api/tests/search_api_test_excerpt_field/config/install/views.view.search_api_test_excerpt_field.yml
@@ -57,8 +57,8 @@ display:
             offset: false
             offset_label: Offset
           tags:
-            previous: ‹‹
-            next: ››
+            previous: '«'
+            next: '»'
       style:
         type: default
         options:
diff --git a/web/modules/search_api/tests/search_api_test_excerpt_field/search_api_test_excerpt_field.info.yml b/web/modules/search_api/tests/search_api_test_excerpt_field/search_api_test_excerpt_field.info.yml
index 5a079c3ddee373ef38108b57014153d7212c721d..3830f96784054d8b1c1bf9f024927d8f8ecfb47d 100644
--- a/web/modules/search_api/tests/search_api_test_excerpt_field/search_api_test_excerpt_field.info.yml
+++ b/web/modules/search_api/tests/search_api_test_excerpt_field/search_api_test_excerpt_field.info.yml
@@ -9,7 +9,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_extraction/search_api_test_extraction.info.yml b/web/modules/search_api/tests/search_api_test_extraction/search_api_test_extraction.info.yml
index e6386b6354fb851e3984f0c6a4a745911c3c7c34..af3c3baa60534806c70c663879de0bb93fa25d53 100644
--- a/web/modules/search_api/tests/search_api_test_extraction/search_api_test_extraction.info.yml
+++ b/web/modules/search_api/tests/search_api_test_extraction/search_api_test_extraction.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_hooks/search_api_test_hooks.info.yml b/web/modules/search_api/tests/search_api_test_hooks/search_api_test_hooks.info.yml
index cb9eb68b4e43bc42d67f46316414a5ad221f62fd..02bd3080f159db74edf3a8ef2b6d93d005774b90 100644
--- a/web/modules/search_api/tests/search_api_test_hooks/search_api_test_hooks.info.yml
+++ b/web/modules/search_api/tests/search_api_test_hooks/search_api_test_hooks.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_inconsistent_config/search_api_test_inconsistent_config.info.yml b/web/modules/search_api/tests/search_api_test_inconsistent_config/search_api_test_inconsistent_config.info.yml
index 3fe52b63a7ae81ab26b182c2543ce4d5524b5f58..6e946b778785bde08f46710d2344d7b8ec3952cf 100644
--- a/web/modules/search_api/tests/search_api_test_inconsistent_config/search_api_test_inconsistent_config.info.yml
+++ b/web/modules/search_api/tests/search_api_test_inconsistent_config/search_api_test_inconsistent_config.info.yml
@@ -8,7 +8,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_language_fallback/search_api_test_language_fallback.info.yml b/web/modules/search_api/tests/search_api_test_language_fallback/search_api_test_language_fallback.info.yml
index 0daaefa1383b44f1a3d1993358931137067c6108..946a8b91f2d54389fce549acf3e648138a208f89 100644
--- a/web/modules/search_api/tests/search_api_test_language_fallback/search_api_test_language_fallback.info.yml
+++ b/web/modules/search_api/tests/search_api_test_language_fallback/search_api_test_language_fallback.info.yml
@@ -5,7 +5,7 @@ package: 'Search API'
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_no_ui/search_api_test_no_ui.info.yml b/web/modules/search_api/tests/search_api_test_no_ui/search_api_test_no_ui.info.yml
index 26efc29333b9ef9c759192a63604018a3a201302..2d49a90e205e1f18e029fd67037460b1ad9ddd3c 100644
--- a/web/modules/search_api/tests/search_api_test_no_ui/search_api_test_no_ui.info.yml
+++ b/web/modules/search_api/tests/search_api_test_no_ui/search_api_test_no_ui.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_node_indexing/search_api_test_node_indexing.info.yml b/web/modules/search_api/tests/search_api_test_node_indexing/search_api_test_node_indexing.info.yml
index c26492cbec40f66efca433663f8d7f01f93b6c5b..1112004e2ddb4e3ada2229d1f2ac4b6509312df8 100644
--- a/web/modules/search_api/tests/search_api_test_node_indexing/search_api_test_node_indexing.info.yml
+++ b/web/modules/search_api/tests/search_api_test_node_indexing/search_api_test_node_indexing.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_tasks/search_api_test_tasks.info.yml b/web/modules/search_api/tests/search_api_test_tasks/search_api_test_tasks.info.yml
index 49d04312cefa41c16ab91449b0b524229757d46d..ca678d7ca61554f64d0871e24651f13ec61ae7f7 100644
--- a/web/modules/search_api/tests/search_api_test_tasks/search_api_test_tasks.info.yml
+++ b/web/modules/search_api/tests/search_api_test_tasks/search_api_test_tasks.info.yml
@@ -7,7 +7,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_block_view.yml b/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_block_view.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cf6a8c1a753e3ca081a9270f4fdb6adc44cec9ff
--- /dev/null
+++ b/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_block_view.yml
@@ -0,0 +1,211 @@
+id: search_api_test_block_view
+label: 'Search API Test Block View'
+module: views
+tag: ''
+langcode: en
+dependencies:
+  config:
+    - search_api.index.database_search_index
+  module:
+    - search_api
+base_field: search_api_id
+base_table: search_api_index_database_search_index
+core: 8.x
+description: ''
+status: true
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: none
+        options: { }
+      cache:
+        type: none
+        options: { }
+      query:
+        type: search_api_query
+        options:
+          bypass_access: false
+          skip_access: false
+          preserve_facet_query_args: false
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: true
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: none
+        options: { }
+      style:
+        type: default
+      row:
+        type: fields
+      fields:
+        title:
+          id: title
+          table: search_api_index_database_search_index
+          field: title
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: string
+          settings:
+            link_to_entity: false
+          group_column: value
+          group_columns: { }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          field_rendering: false
+          fallback_handler: search_api
+          fallback_options:
+            link_to_item: false
+            use_highlighting: false
+            multi_type: separator
+            multi_separator: ', '
+          plugin_id: search_api_field
+      filters:
+        search_api_fulltext:
+          id: search_api_fulltext
+          table: search_api_index_database_search_index
+          field: search_api_fulltext
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: and
+          value: TEST
+          group: 1
+          exposed: true
+          expose:
+            operator_id: search_api_fulltext_op
+            label: 'Fulltext search'
+            description: ''
+            use_operator: false
+            operator: search_api_fulltext_op
+            operator_limit_selection: false
+            operator_list: { }
+            identifier: search_api_fulltext
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            placeholder: ''
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: { }
+            group_items: { }
+          parse_mode: terms
+          min_length: null
+          fields: { }
+          plugin_id: search_api_fulltext
+      sorts: { }
+      title: 'Search API Test Block View'
+      header:
+        result:
+          id: result
+          table: views
+          field: result
+          relationship: none
+          group_type: group
+          admin_label: ''
+          empty: true
+          content: 'Search API Test Block View: Found @total items'
+          plugin_id: result
+      footer: { }
+      empty: { }
+      relationships: { }
+      arguments: { }
+      display_extenders: { }
+      use_ajax: false
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - 'user.node_grants:view'
+      tags:
+        - 'config:search_api.index.database_search_index'
+  block_1:
+    display_plugin: block
+    id: block_1
+    display_title: Block
+    position: 1
+    display_options:
+      display_extenders: { }
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - 'user.node_grants:view'
+      tags:
+        - 'config:search_api.index.database_search_index'
diff --git a/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_cache.yml b/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_cache.yml
index 55f598e7e312a911fda5f906c20e8ebe69c83e76..0f271a8926fd03db7a03c347072ad90e2724f9eb 100644
--- a/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_cache.yml
+++ b/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_cache.yml
@@ -57,8 +57,8 @@ display:
             offset: false
             offset_label: Offset
           tags:
-            previous: ‹‹
-            next: ››
+            previous: '«'
+            next: '»'
       style:
         type: default
       row:
diff --git a/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_view.yml b/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_view.yml
index 66169b4813cff6a6931e67087b91f7ba990a56fb..ac7186c5c1cdcd4c636d24fb7734f2005e043a2e 100644
--- a/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_view.yml
+++ b/web/modules/search_api/tests/search_api_test_views/config/install/views.view.search_api_test_view.yml
@@ -78,6 +78,7 @@ display:
             description: ''
             use_operator: true
             operator: search_api_fulltext_op
+            expose_fields: false
             identifier: search_api_fulltext
             required: false
             remember: false
diff --git a/web/modules/search_api/tests/search_api_test_views/search_api_test_views.info.yml b/web/modules/search_api/tests/search_api_test_views/search_api_test_views.info.yml
index 8f20a4018975f1dd21f2020aca7ce3e29a961e2e..ec6a4aa682cf54cafeb790cebab771b82ad5554f 100644
--- a/web/modules/search_api/tests/search_api_test_views/search_api_test_views.info.yml
+++ b/web/modules/search_api/tests/search_api_test_views/search_api_test_views.info.yml
@@ -11,7 +11,7 @@ dependencies:
 core_version_requirement: ^8.8 || ^9
 hidden: true
 
-# Information added by Drupal.org packaging script on 2020-10-22
-version: '8.x-1.18'
+# Information added by Drupal.org packaging script on 2021-02-01
+version: '8.x-1.19'
 project: 'search_api'
-datestamp: 1603359377
+datestamp: 1612192043
diff --git a/web/modules/search_api/tests/src/Functional/ViewsTest.php b/web/modules/search_api/tests/src/Functional/ViewsTest.php
index f7f1bdb05a894ccceb8c62f88d1778e18b58aaca..ad1d7888fec4615fb9de977ee8131ec96d06478e 100644
--- a/web/modules/search_api/tests/src/Functional/ViewsTest.php
+++ b/web/modules/search_api/tests/src/Functional/ViewsTest.php
@@ -309,6 +309,9 @@ public function testSearchView() {
 
     $this->regressionTests();
 
+    // Check special functionality that requires editing the view.
+    $this->checkExposedSearchFields();
+
     // Make sure there was a display plugin created for this view.
     /** @var \Drupal\search_api\Display\DisplayInterface[] $displays */
     $displays = \Drupal::getContainer()
@@ -373,6 +376,7 @@ public function testViewWithOperations() {
   protected function regressionTests() {
     $this->regressionTest2869121();
     $this->regressionTest3031991();
+    $this->regressionTest3136277();
   }
 
   /**
@@ -458,6 +462,59 @@ protected function regressionTest3031991() {
     $this->checkResults($query, [4], 'Search with multiple fulltext filters');
   }
 
+  /**
+   * Tests that query preprocessing works correctly for block views.
+   *
+   * @see https://www.drupal.org/node/3136277
+   */
+  protected function regressionTest3136277() {
+    $block = $this->drupalPlaceBlock('views_block:search_api_test_block_view-block_1', [
+      'region' => 'content',
+    ]);
+    /** @var \Drupal\search_api\IndexInterface $index */
+    $index = Index::load($this->indexId);
+    $processor = \Drupal::getContainer()
+      ->get('search_api.plugin_helper')
+      ->createProcessorPlugin($index, 'ignorecase');
+    $index->addProcessor($processor)->save();
+
+    $this->drupalGet('<front>');
+    $this->assertSession()->pageTextContains('Search API Test Block View: Found 4 items');
+
+    $index->removeProcessor('ignorecase')->save();
+    $block->delete();
+  }
+
+  /**
+   * Verifies that exposed fulltext fields work correctly.
+   */
+  protected function checkExposedSearchFields() {
+    $key = 'display.default.display_options.filters.search_api_fulltext.expose.expose_fields';
+    $view = \Drupal::configFactory()
+      ->getEditable('views.view.search_api_test_view');
+    $view->set($key, TRUE);
+    $view->save();
+
+    $query = [
+      'search_api_fulltext' => 'foo',
+      'search_api_fulltext_searched_fields' => [
+        'name',
+      ],
+    ];
+    $this->checkResults($query, [1, 2, 4], 'Search for results in name field only');
+
+    $query = [
+      'search_api_fulltext' => 'foo',
+      'search_api_fulltext_searched_fields' => [
+        'body',
+      ],
+    ];
+    $this->checkResults($query, [5], 'Search for results in body field only');
+
+    $view->set($key, FALSE);
+    $view->save();
+  }
+
   /**
    * Checks the Views results for a certain set of parameters.
    *
diff --git a/web/modules/search_api/tests/src/Kernel/Datasource/ReferencedEntitiesReindexingTest.php b/web/modules/search_api/tests/src/Kernel/Datasource/ReferencedEntitiesReindexingTest.php
index ead2c474612b4ac794ba9e1042e675fa54bcd686..d41173d264fe053cf0dd196137eb4caa12b52a01 100644
--- a/web/modules/search_api/tests/src/Kernel/Datasource/ReferencedEntitiesReindexingTest.php
+++ b/web/modules/search_api/tests/src/Kernel/Datasource/ReferencedEntitiesReindexingTest.php
@@ -8,6 +8,7 @@
 use Drupal\node\Entity\Node;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
+use Drupal\search_api\Utility\TrackingHelper;
 use Drupal\search_api\Utility\Utility;
 
 /**
@@ -76,6 +77,7 @@ public function setUp() {
             'selected' => ['parent'],
           ],
         ],
+        'entity:user' => [],
       ],
       'server' => 'server',
       'field_settings' => [
@@ -292,4 +294,54 @@ protected function createEntitiesFromMap(array $entity_fields, array $references
     return $entities;
   }
 
+  /**
+   * Tests whether relationships are correctly separated between datasources.
+   *
+   * @see https://www.drupal.org/node/3178941
+   */
+  public function testUnrelatedDatasourceUnaffected() {
+    // First, check whether the tracking helper correctly includes "datasource"
+    // keys with all foreign relationship entries.
+    $tracking_helper = \Drupal::getContainer()
+      ->get('search_api.tracking_helper');
+    $method = new \ReflectionMethod(TrackingHelper::class, 'getForeignEntityRelationsMap');
+    $method->setAccessible(TRUE);
+    /** @see \Drupal\search_api\Utility\TrackingHelper::getForeignEntityRelationsMap() */
+    $map = $method->invoke($tracking_helper, $this->index);
+    $expected = [
+      [
+        'datasource' => 'entity:node',
+        'entity_type' => 'node',
+        // Note: It's unspecified that this array has string keys, only its
+        // values are important. Still, it's easier to just reflect the current
+        // implementation, when checking for equality.
+        'bundles' => ['child' => 'child'],
+        'property_path_to_foreign_entity' => 'entity_reference',
+        'field_name' => 'indexed',
+      ],
+    ];
+    $this->assertEquals($expected, $map);
+
+    // Then, check whether datasources correctly ignore relationships from other
+    // datasources, or that they at least don't lead to an exception/error.
+    $datasource = $this->index->getDatasource('entity:user');
+    $entities = $this->createEntitiesFromMap([
+      'child' => [
+        'title' => 'Child',
+        'indexed' => 'Indexed value',
+        'not_indexed' => 'Not indexed value.',
+      ],
+    ], [], 'child');
+    $child = reset($entities);
+    $original_child = clone $child;
+    $child->get('indexed')->setValue(['New value']);
+    $result = $datasource->getAffectedItemsForEntityChange($child, $map, $original_child);
+    $this->assertEquals([], $result);
+
+    // Change foreign relationships map slightly to trigger #3178941 on purpose.
+    $map[0]['property_path_to_foreign_entity'] = 'entity_reference:entity';
+    $result = $datasource->getAffectedItemsForEntityChange($child, $map, $original_child);
+    $this->assertEquals([], $result);
+  }
+
 }
diff --git a/web/modules/search_api/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php b/web/modules/search_api/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php
index ff684a5344673c63359c7796bb575039e91dd75f..c6e79270a340ced6ee891ff2f452a2c3668251f2 100644
--- a/web/modules/search_api/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php
+++ b/web/modules/search_api/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php
@@ -155,6 +155,7 @@ public function testViewExport() {
     // Activate the alter hook and resave the view so it will recalculate the
     // cacheability metadata.
     $this->state->set('search_api_test_views.alter_query_cacheability_metadata', TRUE);
+    $view = $this->getView();
     $view->save();
 
     // Check that the altered metadata is now present in the view and the
diff --git a/web/modules/search_api/tests/src/Kernel/Views/ViewsFieldTraitTest.php b/web/modules/search_api/tests/src/Kernel/Views/ViewsFieldTraitTest.php
index 9ee0bdd15fa5a61908e4fbbdef47706544f2ea9b..11eced6ad62f07c84f88ac582c295450e43d8d08 100644
--- a/web/modules/search_api/tests/src/Kernel/Views/ViewsFieldTraitTest.php
+++ b/web/modules/search_api/tests/src/Kernel/Views/ViewsFieldTraitTest.php
@@ -103,7 +103,7 @@ protected function setUp() {
         'aggregated_field' => [
           'label' => 'Aggregated field',
           'property_path' => 'aggregated_field',
-          'type' => 'text',
+          'type' => 'string',
           'configuration' => [
             'type' => 'union',
             'fields' => [