diff --git a/core/core.services.yml b/core/core.services.yml
index 21a3e590ba..1defdfa146 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -417,14 +417,15 @@ services:
     arguments: ['form']
   logger.log_message_parser:
     class: Drupal\Core\Logger\LogMessageParser
-
+  schema.data:
+    class: Drupal\Core\Schema\SchemaData
+    arguments: ['@module_handler', '@cache.default', '@keyvalue', '@database', '@cache_tags.invalidator']
   serialization.json:
     class: Drupal\Component\Serialization\Json
   serialization.phpserialize:
     class: Drupal\Component\Serialization\PhpSerialize
   serialization.yaml:
     class: Drupal\Component\Serialization\Yaml
-
   settings:
     class: Drupal\Core\Site\Settings
     factory: Drupal\Core\Site\Settings::getInstance
@@ -502,7 +503,7 @@ services:
     class: Drupal\Core\Extension\ModuleInstaller
     tags:
       - { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator }
-    arguments: ['@app.root', '@module_handler', '@kernel', '@router.builder']
+    arguments: ['@app.root', '@module_handler', '@kernel', '@router.builder', '@schema.data']
     lazy: true
   content_uninstall_validator:
     class: Drupal\Core\Entity\ContentUninstallValidator
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 5c0abcab8e..d9ef53db9d 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -77,7 +77,7 @@
  * Loads .install files for installed modules to initialize the update system.
  */
 function drupal_load_updates() {
-  foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
+  foreach (\Drupal::service('schema.data')->getInstalledVersion(NULL, FALSE, TRUE) as $module => $schema_version) {
     if ($schema_version > -1) {
       module_load_install($module);
     }
diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index 443cbf1934..05fb124399 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -5,6 +5,8 @@
  * Schema API handling functions.
  */
 
+use Drupal\Core\Schema\SchemaDataInterface;
+
 /**
  * @addtogroup schemaapi
  * @{
@@ -12,8 +14,11 @@
 
 /**
  * Indicates that a module has not been installed yet.
+ *
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.
+ *   Use \Drupal\Core\Schema\SchemaDataInterface::UNINSTALLED.
  */
-const SCHEMA_UNINSTALLED = -1;
+const SCHEMA_UNINSTALLED = SchemaDataInterface::UNINSTALLED;
 
 /**
  * Returns an array of available schema versions for a module.
@@ -24,38 +29,18 @@
  * @return array|bool
  *   If the module has updates, an array of available updates sorted by
  *   version. Otherwise, FALSE.
+ *
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.
+ *   Use \Drupal::service('schema.data')->getVersions().
  */
 function drupal_get_schema_versions($module) {
-  $updates = &drupal_static(__FUNCTION__, NULL);
-  if (!isset($updates[$module])) {
-    $updates = [];
-    foreach (\Drupal::moduleHandler()->getModuleList() as $loaded_module => $filename) {
-      $updates[$loaded_module] = [];
-    }
+  $versions = \Drupal::service('schema.data')->getVersions($module);
 
-    // Prepare regular expression to match all possible defined hook_update_N().
-    $regexp = '/^(?<module>.+)_update_(?<version>\d+)$/';
-    $functions = get_defined_functions();
-    // Narrow this down to functions ending with an integer, since all
-    // hook_update_N() functions end this way, and there are other
-    // possible functions which match '_update_'. We use preg_grep() here
-    // instead of foreaching through all defined functions, since the loop
-    // through all PHP functions can take significant page execution time
-    // and this function is called on every administrative page via
-    // system_requirements().
-    foreach (preg_grep('/_\d+$/', $functions['user']) as $function) {
-      // If this function is a module update function, add it to the list of
-      // module updates.
-      if (preg_match($regexp, $function, $matches)) {
-        $updates[$matches['module']][] = $matches['version'];
-      }
-    }
-    // Ensure that updates are applied in numerical order.
-    foreach ($updates as &$module_updates) {
-      sort($module_updates, SORT_NUMERIC);
-    }
+  // The new method returns an empty array instead of FALSE as before.
+  if ($versions === []) {
+    return FALSE;
   }
-  return empty($updates[$module]) ? FALSE : $updates[$module];
+  return $versions;
 }
 
 /**
@@ -72,26 +57,12 @@ function drupal_get_schema_versions($module) {
  * @return string|int
  *   The currently installed schema version, or SCHEMA_UNINSTALLED if the
  *   module is not installed.
+ *
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.
+ *   Use \Drupal::service('schema.data')->getInstalledVersion().
  */
 function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
-  $versions = &drupal_static(__FUNCTION__, []);
-
-  if ($reset) {
-    $versions = [];
-  }
-
-  if (!$versions) {
-    if (!$versions = \Drupal::keyValue('system.schema')->getAll()) {
-      $versions = [];
-    }
-  }
-
-  if ($array) {
-    return $versions;
-  }
-  else {
-    return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED;
-  }
+  return \Drupal::service('schema.data')->getInstalledVersion($module, $reset, $array);
 }
 
 /**
@@ -101,11 +72,12 @@ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = F
  *   A module name.
  * @param string $version
  *   The new schema version.
+ *
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.
+ *   Use \Drupal::service('schema.data')->setInstalledVersion().
  */
 function drupal_set_installed_schema_version($module, $version) {
-  \Drupal::keyValue('system.schema')->set($module, $version);
-  // Reset the static cache of module schema versions.
-  drupal_get_installed_schema_version(NULL, TRUE);
+  \Drupal::service('schema.data')->setInstalledVersion($module, $version);
 }
 
 /**
@@ -113,14 +85,12 @@ function drupal_set_installed_schema_version($module, $version) {
  *
  * @param string $module
  *   The module for which the tables will be created.
+ *
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.
+ *   Use \Drupal::service('schema.data')->install().
  */
 function drupal_install_schema($module) {
-  $schema = drupal_get_module_schema($module);
-  _drupal_schema_initialize($schema, $module, FALSE);
-
-  foreach ($schema as $name => $table) {
-    \Drupal::database()->schema()->createTable($name, $table);
-  }
+  \Drupal::service('schema.data')->install($module);
 }
 
 /**
@@ -128,16 +98,12 @@ function drupal_install_schema($module) {
  *
  * @param string $module
  *   The module for which the tables will be removed.
+ *
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.
+ *   Use \Drupal::service('schema.data')->uninstall().
  */
 function drupal_uninstall_schema($module) {
-  $schema = drupal_get_module_schema($module);
-  _drupal_schema_initialize($schema, $module, FALSE);
-
-  foreach ($schema as $table) {
-    if (db_table_exists($table['name'])) {
-      db_drop_table($table['name']);
-    }
-  }
+  \Drupal::service('schema.data')->uninstall($module);
 }
 
 /**
@@ -171,37 +137,6 @@ function drupal_get_module_schema($module, $table = NULL) {
 }
 
 /**
- * Fills in required default values for table definitions from hook_schema().
- *
- * @param array $schema
- *   The schema definition array as it was returned by the module's
- *   hook_schema().
- * @param string $module
- *   The module for which hook_schema() was invoked.
- * @param bool $remove_descriptions
- *   (optional) Whether to additionally remove 'description' keys of all tables
- *   and fields to improve performance of serialize() and unserialize().
- *   Defaults to TRUE.
- */
-function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) {
-  // Set the name and module key for all tables.
-  foreach ($schema as $name => &$table) {
-    if (empty($table['module'])) {
-      $table['module'] = $module;
-    }
-    if (!isset($table['name'])) {
-      $table['name'] = $name;
-    }
-    if ($remove_descriptions) {
-      unset($table['description']);
-      foreach ($table['fields'] as &$field) {
-        unset($field['description']);
-      }
-    }
-  }
-}
-
-/**
  * Typecasts values to proper datatypes.
  *
  * MySQL PDO silently casts, e.g. FALSE and '' to 0, when inserting the value
@@ -215,21 +150,12 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU
  *
  * @return mixed
  *   The converted value.
+ *
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.
+ *   Use \Drupal::service('schema.data')->getFieldValue().
  */
 function drupal_schema_get_field_value(array $info, $value) {
-  // Preserve legal NULL values.
-  if (isset($value) || !empty($info['not null'])) {
-    if ($info['type'] == 'int' || $info['type'] == 'serial') {
-      $value = (int) $value;
-    }
-    elseif ($info['type'] == 'float') {
-      $value = (float) $value;
-    }
-    elseif (!is_array($value)) {
-      $value = (string) $value;
-    }
-  }
-  return $value;
+  return \Drupal::service('schema.data')->getFieldValue($info, $value);
 }
 
 /**
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 2caa6ca3f9..ea063152a5 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -70,7 +70,7 @@ function update_check_incompatibility($name, $type = 'module') {
 function update_system_schema_requirements() {
   $requirements = [];
 
-  $system_schema = drupal_get_installed_schema_version('system');
+  $system_schema = \Drupal::service('schema.data')->getInstalledVersion('system');
 
   $requirements['minimum schema']['title'] = 'Minimum schema version';
   if ($system_schema >= \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
@@ -211,7 +211,7 @@ function update_do_one($module, $number, $dependency_map, &$context) {
 
   // Record the schema update if it was completed successfully.
   if ($context['finished'] == 1 && empty($ret['#abort'])) {
-    drupal_set_installed_schema_version($module, $number);
+    \Drupal::service('schema.data')->setInstalledVersion($module, $number);
   }
 
   $context['message'] = t('Updating @module', ['@module' => $module]);
@@ -300,7 +300,7 @@ function update_get_update_list() {
   // Make sure that the system module is first in the list of updates.
   $ret = ['system' => []];
 
-  $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
+  $modules = \Drupal::service('schema.data')->getInstalledVersion(NULL, FALSE, TRUE);
   foreach ($modules as $module => $schema_version) {
     // Skip uninstalled and incompatible modules.
     if ($schema_version == SCHEMA_UNINSTALLED || update_check_incompatibility($module)) {
@@ -313,7 +313,7 @@ function update_get_update_list() {
       continue;
     }
     // Otherwise, get the list of updates defined by this module.
-    $updates = drupal_get_schema_versions($module);
+    $updates = \Drupal::service('schema.data')->getVersions($module);
     if ($updates !== FALSE) {
       // \Drupal::moduleHandler()->invoke() returns NULL for non-existing hooks,
       // so if no updates are removed, it will == 0.
@@ -451,7 +451,7 @@ function update_get_update_function_list($starting_updates) {
   $update_functions = [];
   foreach ($starting_updates as $module => $version) {
     $update_functions[$module] = [];
-    $updates = drupal_get_schema_versions($module);
+    $updates = \Drupal::service('schema.data')->getVersions($module);
     if ($updates !== FALSE) {
       $max_version = max($updates);
       if ($version <= $max_version) {
@@ -581,7 +581,7 @@ function update_is_missing($module, $number, $update_functions) {
  *   performed; FALSE otherwise.
  */
 function update_already_performed($module, $number) {
-  return $number <= drupal_get_installed_schema_version($module);
+  return $number <= \Drupal::service('schema.data')->getInstalledVersion($module);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index d615e0c843..eba3564a48 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -997,7 +997,7 @@ protected function mapToStorageRecord(ContentEntityInterface $entity, $table_nam
         // Do not set serial fields if we do not have a value. This supports all
         // SQL database drivers.
         // @see https://www.drupal.org/node/2279395
-        $value = drupal_schema_get_field_value($definition->getSchema()['columns'][$column_name], $value);
+        $value = \Drupal::service('schema.data')->getFieldValue($definition->getSchema()['columns'][$column_name], $value);
         if (!(empty($value) && $this->isColumnSerial($table_name, $schema_name))) {
           $record->$schema_name = $value;
         }
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 10fabd412a..c257b4d0a3 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -8,6 +8,8 @@
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Serialization\Yaml;
+use Drupal\Core\Routing\RouteBuilderInterface;
+use Drupal\Core\Schema\SchemaDataInterface;
 
 /**
  * Default implementation of the module installer.
@@ -39,6 +41,18 @@ class ModuleInstaller implements ModuleInstallerInterface {
   protected $root;
 
   /**
+   * @var \Drupal\Core\Routing\RouteBuilderInterface
+   */
+  protected $routeBuilder;
+
+  /**
+   * The schema service.
+   *
+   * @var \Drupal\Core\Schema\SchemaDataInterface
+   */
+  protected $schema;
+
+  /**
    * The uninstall validators.
    *
    * @var \Drupal\Core\Extension\ModuleUninstallValidatorInterface[]
@@ -54,14 +68,21 @@ class ModuleInstaller implements ModuleInstallerInterface {
    *   The module handler.
    * @param \Drupal\Core\DrupalKernelInterface $kernel
    *   The drupal kernel.
+   * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
+   *   (optional) The route builder service to rebuild the routes if a theme is
+   *   installed.
+   * @param \Drupal\Core\Schema\SchemaDataInterface $schema
+   *   The schema service.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel) {
+  public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel, RouteBuilderInterface $route_builder, SchemaDataInterface $schema) {
     $this->root = $root;
     $this->moduleHandler = $module_handler;
     $this->kernel = $kernel;
+    $this->routeBuilder = $route_builder;
+    $this->schema = $schema;
   }
 
   /**
@@ -198,7 +219,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         $this->moduleHandler->invokeAll('module_preinstall', [$module]);
 
         // Now install the module's schema if necessary.
-        drupal_install_schema($module);
+        $this->schema->install($module);
 
         // Clear plugin manager caches.
         \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
@@ -206,7 +227,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         // Set the schema version to the number of the last update provided by
         // the module, or the minimum core schema version.
         $version = \Drupal::CORE_MINIMUM_SCHEMA_VERSION;
-        $versions = drupal_get_schema_versions($module);
+        $versions = $this->schema->getVersions($module);
         if ($versions) {
           $version = max(max($versions), $version);
         }
@@ -257,7 +278,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         if ($last_removed = $this->moduleHandler->invoke($module, 'update_last_removed')) {
           $version = max($version, $last_removed);
         }
-        drupal_set_installed_schema_version($module, $version);
+        $this->schema->setInstalledVersion($module, $version);
 
         // Ensure that all post_update functions are registered already.
         /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
@@ -425,7 +446,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
       }
 
       // Remove the schema.
-      drupal_uninstall_schema($module);
+      $this->schema->uninstall($module);
 
       // Remove the module's entry from the config. Don't check schema when
       // uninstalling a module since we are only clearing a key.
@@ -475,7 +496,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
     // fastCGI which executes ::destruct() after the Module uninstallation page
     // was sent already.
     \Drupal::service('router.builder')->rebuild();
-    drupal_get_installed_schema_version(NULL, TRUE);
+    $this->schema->getInstalledVersion(NULL, TRUE);
 
     // Let other modules react.
     $this->moduleHandler->invokeAll('modules_uninstalled', [$module_list]);
diff --git a/core/lib/Drupal/Core/Schema/SchemaData.php b/core/lib/Drupal/Core/Schema/SchemaData.php
new file mode 100644
index 0000000000..7b22c93cc6
--- /dev/null
+++ b/core/lib/Drupal/Core/Schema/SchemaData.php
@@ -0,0 +1,267 @@
+<?php
+
+namespace Drupal\Core\Schema;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+
+/**
+ * Provides database schema handling.
+ */
+class SchemaData implements SchemaDataInterface {
+
+  /**
+   * Statically cached schema data.
+   *
+   * @var array
+   */
+  protected $schema;
+
+  /**
+   * Statically cached complete schema data.
+   *
+   * @var array
+   */
+  protected $completeSchema;
+
+  /**
+   * A static cache of schema currentVersions per module.
+   *
+   * @var array
+   */
+  protected $allVersions = [];
+
+  /**
+   * A static cache of current module schema currentVersions.
+   *
+   * @var array
+   */
+  protected $currentVersions;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * The key value factory.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactory
+   */
+  protected $keyValue;
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
+   */
+  protected $cacheTagsInvalidator;
+
+  /**
+   * Constructs a Schema object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
+   *   The key value factory.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection.
+   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
+   *   The cache tags invalidator.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, KeyValueFactoryInterface $key_value_factory, Connection $connection, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
+    $this->moduleHandler = $module_handler;
+    $this->cacheBackend = $cache_backend;
+    $this->keyValue = $key_value_factory;
+    $this->connection = $connection;
+    $this->cacheTagsInvalidator = $cache_tags_invalidator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVersions($module) {
+    if (!isset($this->allVersions[$module])) {
+      $this->allVersions[$module] = [];
+
+      foreach ($this->moduleHandler->getModuleList() as $loaded_module => $filename) {
+        $this->allVersions[$loaded_module] = [];
+      }
+
+      // Prepare regular expression to match all possible defined hook_update_N().
+      $regexp = '/^(?<module>.+)_update_(?<version>\d+)$/';
+      $functions = get_defined_functions();
+      // Narrow this down to functions ending with an integer, since all
+      // hook_update_N() functions end this way, and there are other
+      // possible functions which match '_update_'. We use preg_grep() here
+      // instead of foreaching through all defined functions, since the loop
+      // through all PHP functions can take significant page execution time
+      // and this function is called on every administrative page via
+      // system_requirements().
+      foreach (preg_grep('/_\d+$/', $functions['user']) as $function) {
+        // If this function is a module update function, add it to the list of
+        // module updates.
+        if (preg_match($regexp, $function, $matches)) {
+          $this->allVersions[$matches['module']][] = $matches['version'];
+        }
+      }
+      // Ensure that updates are applied in numerical order.
+      foreach ($this->allVersions as &$module_updates) {
+        sort($module_updates, SORT_NUMERIC);
+      }
+      unset($module_updates);
+    }
+
+    return empty($this->allVersions[$module]) ? [] : $this->allVersions[$module];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInstalledVersion($module, $reset = FALSE, $array = FALSE) {
+    if ($reset) {
+      $this->currentVersions = [];
+    }
+
+    if (!$this->currentVersions = $this->keyValue->get('system.schema')->getAll()) {
+      $this->currentVersions = [];
+    }
+
+    if ($array) {
+      return $this->currentVersions;
+    }
+    else {
+      return isset($this->currentVersions[$module]) ? $this->currentVersions[$module] : static::UNINSTALLED;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setInstalledVersion($module, $version) {
+    $this->keyValue->get('system.schema')->set($module, $version);
+    // Reset the static cache of module schema currentVersions.
+    $this->getInstalledVersion(NULL, TRUE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function install($module) {
+    $schema = $this->get($module);
+    $this->initialize($schema, $module, FALSE);
+
+    foreach ($schema as $name => $table) {
+      $this->connection->schema()->createTable($name, $table);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uninstall($module) {
+    $schema = $this->get($module);
+    $this->initialize($schema, $module, FALSE);
+    $connection_schema = $this->connection->schema();
+
+    foreach ($schema as $table) {
+      if ($connection_schema->tableExists($table['name'])) {
+        $connection_schema->dropTable($table['name']);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($module, $table = NULL) {
+    // Load the .install file to get hook_schema.
+    $this->moduleHandler->loadInclude($module, 'install');
+    $schema = $this->moduleHandler->invoke($module, 'schema');
+
+    if (isset($table)) {
+      if (isset($schema[$table])) {
+        return $schema[$table];
+      }
+      return [];
+    }
+    elseif (!empty($schema)) {
+      return $schema;
+    }
+
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldValue(array $info, $value) {
+    // Preserve legal NULL values.
+    if (isset($value) || !empty($info['not null'])) {
+      if ($info['type'] == 'int' || $info['type'] == 'serial') {
+        $value = (int) $value;
+      }
+      elseif ($info['type'] == 'float') {
+        $value = (float) $value;
+      }
+      else {
+        $value = (string) $value;
+      }
+    }
+
+    return $value;
+  }
+
+  /**
+   * Fills in required default values for table definitions from hook_schema().
+   *
+   * @param array $schema
+   *   The schema definition array as it was returned by the module's
+   *   hook_schema().
+   * @param string $module
+   *   The module for which hook_schema() was invoked.
+   * @param bool $remove_descriptions
+   *   (optional) Whether to additionally remove 'description' keys of all
+   *   tables and fields to improve performance of serialize() and
+   *   unserialize(). Defaults to TRUE.
+   */
+  protected function initialize(&$schema, $module, $remove_descriptions = TRUE) {
+    // Set the name and module key for all tables.
+    foreach ($schema as $name => &$table) {
+      if (empty($table['module'])) {
+        $table['module'] = $module;
+      }
+      if (!isset($table['name'])) {
+        $table['name'] = $name;
+      }
+      if ($remove_descriptions) {
+        unset($table['description']);
+        foreach ($table['fields'] as &$field) {
+          unset($field['description']);
+        }
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Schema/SchemaDataInterface.php b/core/lib/Drupal/Core/Schema/SchemaDataInterface.php
new file mode 100644
index 0000000000..fc0e83e243
--- /dev/null
+++ b/core/lib/Drupal/Core/Schema/SchemaDataInterface.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Drupal\Core\Schema;
+
+/**
+ * Provides an interface for database schema handling.
+ */
+interface SchemaDataInterface {
+
+  /**
+   * Indicates that a module schema has not been installed yet.
+   */
+  const UNINSTALLED = -1;
+
+  /**
+   * Returns an array of available schema versions for a module.
+   *
+   * @param string $module
+   *   A module name.
+   *
+   * @return array
+   *   If the module has updates, an array of available updates sorted by
+   *   version. Otherwise, an empty array.
+   */
+  public function getVersions($module);
+
+  /**
+   * Returns the currently installed schema version for a module.
+   *
+   * @param string $module
+   *   A module name.
+   * @param bool $reset
+   *   (optional) Set to TRUE after installing or uninstalling an extension.
+   *   Defaults to FALSE.
+   * @param bool $array
+   *   (optional) Set to TRUE if you want to get information about all modules
+   *   in the system. Defaults to FALSE.
+   *
+   * @return string|int
+   *   The currently installed schema version, or static::UNINSTALLED if the
+   *   module is not installed.
+   */
+  public function getInstalledVersion($module, $reset = FALSE, $array = FALSE);
+
+  /**
+   * Updates the installed version information for a module.
+   *
+   * @param string $module
+   *   A module name.
+   * @param string $version
+   *   The new schema version.
+   */
+  public function setInstalledVersion($module, $version);
+
+  /**
+   * Creates all tables defined in a module's hook_schema().
+   *
+   * Note: This function does not pass the module's schema through
+   * hook_schema_alter(). The module's tables will be created exactly as the
+   * module defines them.
+   *
+   * @param string $module
+   *   The module for which the tables will be created.
+   */
+  public function install($module);
+
+  /**
+   * Removes all tables defined in a module's hook_schema().
+   *
+   * Note: This function does not pass the module's schema through
+   * hook_schema_alter(). The module's tables will be deleted exactly as the
+   * module defines them.
+   *
+   * @param string $module
+   *   The module for which the tables will be removed.
+   */
+  public function uninstall($module);
+
+  /**
+   * Returns the module's schema.
+   *
+   * This function can be used to retrieve a schema specification in
+   * hook_schema(), so it allows you to derive your tables from existing
+   * specifications.
+   *
+   * It is also used by ::install() and ::uninstall() to ensure that a module's
+   * tables are created exactly as specified.
+   *
+   * @param string $module
+   *   The module to which the table belongs.
+   * @param string $table
+   *   (optional) The name of the table. Defaults to NULL, which means that the
+   *   module's complete schema is returned.
+   */
+  public function get($module, $table = NULL);
+
+  /**
+   * Typecasts values to proper datatypes.
+   *
+   * MySQL PDO silently casts, e.g. FALSE and '' to 0, when inserting the value
+   * into an integer column, but PostgreSQL PDO does not. Look up the schema
+   * information and use that to correctly typecast the value.
+   *
+   * @param array $info
+   *   An array describing the schema field info.
+   * @param mixed $value
+   *   The value to be converted.
+   *
+   * @return mixed
+   *   The converted value.
+   */
+  public function getFieldValue(array $info, $value);
+
+}
diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php
index 0a244b46bc..447a984017 100644
--- a/core/lib/Drupal/Core/Updater/Module.php
+++ b/core/lib/Drupal/Core/Updater/Module.php
@@ -88,7 +88,7 @@ public function getSchemaUpdates() {
     }
     module_load_include('install', $this->name);
 
-    if (!$updates = drupal_get_schema_versions($this->name)) {
+    if (!$updates = \Drupal::service('schema.data')->getVersions($this->name)) {
       return [];
     }
     $modules_with_updates = update_get_update_list();
diff --git a/core/modules/block/block.post_update.php b/core/modules/block/block.post_update.php
index d5f0852a3c..01da5c5f53 100644
--- a/core/modules/block/block.post_update.php
+++ b/core/modules/block/block.post_update.php
@@ -13,7 +13,7 @@ function block_post_update_disable_blocks_with_missing_contexts() {
   // which used to do the same. Note: Its okay to check here, because
   // update_do_one() does not update the installed schema version until the
   // batch is finished.
-  $module_schema = drupal_get_installed_schema_version('block');
+  $module_schema = \Drupal::service('schema.data')->getInstalledVersion('block');
 
   // The state entry 'block_update_8002_placeholder' is used in order to
   // indicate that the placeholder block_update_8002() function has been
diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
index 1e9e75da0b..9cc96afe60 100644
--- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
+++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
@@ -149,7 +149,7 @@ public function testEnableModulesInstallContainer() {
   }
 
   /**
-   * Tests expected behavior of installSchema().
+   * Tests expected behavior of install().
    */
   public function testInstallSchema() {
     $module = 'entity_test';
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index 9e0a374a4a..9f9f670496 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
 use Drupal\Core\Render\BareHtmlPageRendererInterface;
+use Drupal\Core\Schema\SchemaDataInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\State\StateInterface;
@@ -78,6 +79,13 @@ class DbUpdateController extends ControllerBase {
   protected $postUpdateRegistry;
 
   /**
+   * The schema service.
+   *
+   * @var \Drupal\Core\Schema\SchemaDataInterface
+   */
+  protected $schema;
+
+  /**
    * Constructs a new UpdateController.
    *
    * @param string $root
@@ -96,8 +104,10 @@ class DbUpdateController extends ControllerBase {
    *   The bare HTML page renderer.
    * @param \Drupal\Core\Update\UpdateRegistry $post_update_registry
    *   The post update registry.
+   * @param \Drupal\Core\Schema\SchemaDataInterface $schema
+   *   The schema service.
    */
-  public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, UpdateRegistry $post_update_registry) {
+  public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, UpdateRegistry $post_update_registry, SchemaDataInterface $schema) {
     $this->root = $root;
     $this->keyValueExpirableFactory = $key_value_expirable_factory;
     $this->cache = $cache;
@@ -106,6 +116,7 @@ public function __construct($root, KeyValueExpirableFactoryInterface $key_value_
     $this->account = $account;
     $this->bareHtmlPageRenderer = $bare_html_page_renderer;
     $this->postUpdateRegistry = $post_update_registry;
+    $this->schema = $schema;
   }
 
   /**
@@ -120,7 +131,8 @@ public static function create(ContainerInterface $container) {
       $container->get('module_handler'),
       $container->get('current_user'),
       $container->get('bare_html_page_renderer'),
-      $container->get('update.post_update_registry')
+      $container->get('update.post_update_registry'),
+      $container->get('schema.data')
     );
   }
 
@@ -599,7 +611,7 @@ protected function triggerBatch(Request $request) {
         // correct place. (The updates are already sorted, so we can simply base
         // this on the first one we come across in the above foreach loop.)
         if (isset($start[$update['module']])) {
-          drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
+          $this->schema->setInstalledVersion($update['module'], $update['number'] - 1);
           unset($start[$update['module']]);
         }
         $operations[] = ['update_do_one', [$update['module'], $update['number'], $dependency_map[$function]]];
diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php
index 80348c59f1..784c3ae614 100644
--- a/core/modules/system/src/Form/ModulesUninstallForm.php
+++ b/core/modules/system/src/Form/ModulesUninstallForm.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
+use Drupal\Core\Schema\SchemaDataInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -36,13 +37,21 @@ class ModulesUninstallForm extends FormBase {
   protected $keyValueExpirable;
 
   /**
+   * The schema service.
+   *
+   * @var \Drupal\Core\Schema\SchemaDataInterface
+   */
+  protected $schema;
+
+  /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('module_handler'),
       $container->get('module_installer'),
-      $container->get('keyvalue.expirable')->get('modules_uninstall')
+      $container->get('keyvalue.expirable')->get('modules_uninstall'),
+      $container->get('schema.data')
     );
   }
 
@@ -55,11 +64,14 @@ public static function create(ContainerInterface $container) {
    *   The module installer.
    * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value_expirable
    *   The key value expirable factory.
+   * @param \Drupal\Core\Schema\SchemaDataInterface $schema
+   *   The schema service.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable) {
+  public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, SchemaDataInterface $schema) {
     $this->moduleHandler = $module_handler;
     $this->moduleInstaller = $module_installer;
     $this->keyValueExpirable = $key_value_expirable;
+    $this->schema = $schema;
   }
 
   /**
@@ -143,7 +155,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       // we can allow this module to be uninstalled. (The installation profile
       // is excluded from this list.)
       foreach (array_keys($module->required_by) as $dependent) {
-        if ($dependent != $profile && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) {
+        if ($dependent != $profile && $this->schema->getInstalledVersion($dependent) != SCHEMA_UNINSTALLED) {
           $name = isset($modules[$dependent]->info['name']) ? $modules[$dependent]->info['name'] : $dependent;
           $form['modules'][$module->getName()]['#required_by'][] = $name;
           $form['uninstall'][$module->getName()]['#disabled'] = TRUE;
diff --git a/core/modules/system/tests/src/Functional/Module/InstallTest.php b/core/modules/system/tests/src/Functional/Module/InstallTest.php
index ccbca6d312..7f26024d79 100644
--- a/core/modules/system/tests/src/Functional/Module/InstallTest.php
+++ b/core/modules/system/tests/src/Functional/Module/InstallTest.php
@@ -44,9 +44,9 @@ public function testEnableUserTwice() {
    * Tests recorded schema versions of early installed modules in the installer.
    */
   public function testRequiredModuleSchemaVersions() {
-    $version = drupal_get_installed_schema_version('system', TRUE);
+    $version = \Drupal::service('schema.data')->getInstalledVersion('system', TRUE);
     $this->assertTrue($version > 0, 'System module version is > 0.');
-    $version = drupal_get_installed_schema_version('user', TRUE);
+    $version = \Drupal::service('schema.data')->getInstalledVersion('user', TRUE);
     $this->assertTrue($version > 0, 'User module version is > 0.');
 
     $post_update_key_value = \Drupal::keyValue('post_update');
diff --git a/core/modules/system/tests/src/Functional/System/StatusTest.php b/core/modules/system/tests/src/Functional/System/StatusTest.php
index 1ec4fb5aab..b858100759 100644
--- a/core/modules/system/tests/src/Functional/System/StatusTest.php
+++ b/core/modules/system/tests/src/Functional/System/StatusTest.php
@@ -76,12 +76,12 @@ public function testStatusPage() {
 
     // Set the schema version of update_test_postupdate to a lower version, so
     // update_test_postupdate_update_8001() needs to be executed.
-    drupal_set_installed_schema_version('update_test_postupdate', 8000);
+    \Drupal::service('schema.data')->setInstalledVersion('update_test_postupdate', 8000);
     $this->drupalGet('admin/reports/status');
     $this->assertText(t('Out of date'));
 
     // Now cleanup the executed post update functions.
-    drupal_set_installed_schema_version('update_test_postupdate', 8001);
+    \Drupal::service('schema.data')->setInstalledVersion('update_test_postupdate', 8001);
     /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
     $post_update_registry = \Drupal::service('update.post_update_registry');
     $post_update_registry->filterOutInvokedUpdatesByModule('update_test_postupdate');
diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseTest.php
index 4077a07d2b..1b3636b870 100644
--- a/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseTest.php
+++ b/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseTest.php
@@ -38,7 +38,7 @@ public function testDatabaseLoaded() {
       'system' => '8013',
     ];
     foreach ($hook_updates as $module => $schema) {
-      $this->assertEqual(drupal_get_installed_schema_version($module), $schema, new FormattableMarkup('Module @module schema is @schema', ['@module' => $module, '@schema' => $schema]));
+      $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion($module), $schema, new FormattableMarkup('Module @module schema is @schema', ['@module' => $module, '@schema' => $schema]));
     }
 
     // Test post_update key value stores contains a list of the update functions
diff --git a/core/modules/system/tests/src/Functional/Update/UpdateSchemaTest.php b/core/modules/system/tests/src/Functional/Update/UpdateSchemaTest.php
index a42cbcb3c2..8fc804235e 100644
--- a/core/modules/system/tests/src/Functional/Update/UpdateSchemaTest.php
+++ b/core/modules/system/tests/src/Functional/Update/UpdateSchemaTest.php
@@ -45,7 +45,7 @@ protected function setUp() {
    */
   public function testUpdateHooks() {
     // Verify that the 8000 schema is in place.
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_schema'), 8000);
+    $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion('update_test_schema'), 8000);
     $this->assertFalse(db_index_exists('update_test_schema_table', 'test'), 'Version 8000 of the update_test_schema module is installed.');
 
     // Increment the schema version.
@@ -60,7 +60,7 @@ public function testUpdateHooks() {
     $this->checkForMetaRefresh();
 
     // Ensure schema has changed.
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_schema', TRUE), 8001);
+    $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion('update_test_schema', TRUE), 8001);
     // Ensure the index was added for column a.
     $this->assertTrue(db_index_exists('update_test_schema_table', 'test'), 'Version 8001 of the update_test_schema module is installed.');
   }
diff --git a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
index 29d4d08e23..c7e27cdab7 100644
--- a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
+++ b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
@@ -112,7 +112,7 @@ public function testRequirements() {
     // First, run this test with pending updates to make sure they can be run
     // successfully.
     $update_script_test_config->set('requirement_type', REQUIREMENT_WARNING)->save();
-    drupal_set_installed_schema_version('update_script_test', drupal_get_installed_schema_version('update_script_test') - 1);
+    \Drupal::service('schema.data')->setInstalledVersion('update_script_test', \Drupal::service('schema.data')->getInstalledVersion('update_script_test') - 1);
     $this->drupalGet($this->updateUrl, ['external' => TRUE]);
     $this->assertText('This is a requirements warning provided by the update_script_test module.');
     $this->clickLink('try again');
@@ -195,12 +195,12 @@ public function testSuccessfulUpdateFunctionality() {
     $this->assertEqual($final_maintenance_mode, $initial_maintenance_mode, 'Maintenance mode should not have changed after database updates.');
 
     // Reset the static cache to ensure we have the most current setting.
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    $schema_version = \Drupal::service('schema.data')->getInstalledVersion('update_script_test', TRUE);
     $this->assertEqual($schema_version, 8001, 'update_script_test schema version is 8001 after updating.');
 
     // Set the installed schema version to one less than the current update.
-    drupal_set_installed_schema_version('update_script_test', $schema_version - 1);
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    \Drupal::service('schema.data')->setInstalledVersion('update_script_test', $schema_version - 1);
+    $schema_version = \Drupal::service('schema.data')->getInstalledVersion('update_script_test', TRUE);
     $this->assertEqual($schema_version, 8000, 'update_script_test schema version overridden to 8000.');
 
     // Click through update.php with 'access administration pages' and
@@ -254,12 +254,12 @@ public function testSuccessfulMultilingualUpdateFunctionality() {
     $config->save();
 
     // Reset the static cache to ensure we have the most current setting.
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    $schema_version = \Drupal::service('schema.data')->getInstalledVersion('update_script_test', TRUE);
     $this->assertEqual($schema_version, 8001, 'update_script_test schema version is 8001 after updating.');
 
     // Set the installed schema version to one less than the current update.
-    drupal_set_installed_schema_version('update_script_test', $schema_version - 1);
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    \Drupal::service('schema.data')->setInstalledVersion('update_script_test', $schema_version - 1);
+    $schema_version = \Drupal::service('schema.data')->getInstalledVersion('update_script_test', TRUE);
     $this->assertEqual($schema_version, 8000, 'update_script_test schema version overridden to 8000.');
 
     // Create admin user.
@@ -290,12 +290,13 @@ public function testSuccessfulMultilingualUpdateFunctionality() {
    * Helper function to run updates via the browser.
    */
   protected function runUpdates($maintenance_mode) {
-    $schema_version = drupal_get_installed_schema_version('update_script_test');
+    $schema_version = \Drupal::service('schema.data')->getInstalledVersion('update_script_test');
+
     $this->assertEqual($schema_version, 8001, 'update_script_test is initially installed with schema version 8001.');
 
     // Set the installed schema version to one less than the current update.
-    drupal_set_installed_schema_version('update_script_test', $schema_version - 1);
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    \Drupal::service('schema.data')->setInstalledVersion('update_script_test', $schema_version - 1);
+    $schema_version = \Drupal::service('schema.data')->getInstalledVersion('update_script_test', TRUE);
     $this->assertEqual($schema_version, 8000, 'update_script_test schema version overridden to 8000.');
 
     // Click through update.php with 'administer software updates' permission.
diff --git a/core/modules/system/tests/src/Functional/Update/UpdatesWith7xTest.php b/core/modules/system/tests/src/Functional/Update/UpdatesWith7xTest.php
index 2452264e37..ce770cfcc5 100644
--- a/core/modules/system/tests/src/Functional/Update/UpdatesWith7xTest.php
+++ b/core/modules/system/tests/src/Functional/Update/UpdatesWith7xTest.php
@@ -39,11 +39,11 @@ protected function setUp() {
   public function testWith7x() {
     // Ensure that the minimum schema version is 8000, despite 7200 update
     // hooks and a 7XXX hook_update_last_removed().
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_with_7x'), 8000);
+    $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion('update_test_with_7x'), 8000);
 
     // Try to manually set the schema version to 7110 and ensure that no
     // updates are allowed.
-    drupal_set_installed_schema_version('update_test_with_7x', 7110);
+    \Drupal::service('schema.data')->setInstalledVersion('update_test_with_7x', 7110);
 
     // Click through update.php with 'administer software updates' permission.
     $this->drupalLogin($this->updateUser);
diff --git a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
index d7c2650353..f69ddf0d30 100644
--- a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
+++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
@@ -142,7 +142,7 @@ public function testDependencyResolution() {
     $this->assertTrue($result, 'ModuleInstaller::uninstall() returned TRUE.');
 
     foreach (['color', 'config', 'help'] as $module) {
-      $this->assertEqual(drupal_get_installed_schema_version($module), SCHEMA_UNINSTALLED, "$module module was uninstalled.");
+      $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion($module), SCHEMA_UNINSTALLED, "$module module was uninstalled.");
     }
     $uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order') ?: [];
     $this->assertEqual($uninstalled_modules, ['color', 'config', 'help'], 'Modules were uninstalled in the correct order.');
@@ -193,7 +193,7 @@ public function testUninstallProfileDependency() {
     $result = $this->moduleInstaller()->uninstall([$dependency]);
     $this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.');
     $this->assertFalse($this->moduleHandler()->moduleExists($dependency));
-    $this->assertEqual(drupal_get_installed_schema_version($dependency), SCHEMA_UNINSTALLED, "$dependency module was uninstalled.");
+    $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion($dependency), SCHEMA_UNINSTALLED, "$dependency module was uninstalled.");
 
     // Verify that the installation profile itself was not uninstalled.
     $uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order') ?: [];
@@ -251,8 +251,8 @@ public function testUninstallContentDependency() {
     $entity->delete();
 
     $result = $this->moduleInstaller()->uninstall(['help']);
-    $this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.');
-    $this->assertEqual(drupal_get_installed_schema_version('entity_test'), SCHEMA_UNINSTALLED, "entity_test module was uninstalled.");
+    $this->assertTrue($result, 'ModuleHandler::uninstall() returns TRUE.');
+    $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion('entity_test'), SCHEMA_UNINSTALLED, "entity_test module was uninstalled.");
   }
 
   /**
diff --git a/core/phpcs.local.xml b/core/phpcs.local.xml
new file mode 100644
index 0000000000..8ab79cd835
--- /dev/null
+++ b/core/phpcs.local.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ruleset name="drupal_core">
+  <description>Default PHP CodeSniffer configuration for Drupal core.</description>
+  <file>.</file>
+  <!-- All Drupal code files must be UTF-8 encoded and we treat them as such. -->
+  <arg name="encoding" value="utf-8"/>
+
+  <arg name="extensions" value="inc,install,module,php,profile,test,theme"/>
+
+  <!--Exclude third party code.-->
+  <exclude-pattern>./assets/vendor/*</exclude-pattern>
+
+  <!--Exclude test files that are intentionally empty, or intentionally violate coding standards.-->
+  <exclude-pattern>./modules/system/tests/fixtures/HtaccessTest</exclude-pattern>
+
+  <rule ref="Squiz.Arrays.ArrayDeclaration"/>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.NoKeySpecified">
+    <exclude-pattern>lib/Drupal/Component/Transliteration/data/*</exclude-pattern>
+  </rule>
+  <!-- Disable some error messages that we do not want. -->
+  <rule ref="Squiz.Arrays.ArrayDeclaration.CloseBraceNotAligned">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.DoubleArrowNotAligned">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.FirstValueNoNewline">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.KeyNotAligned">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.MultiLineNotAllowed">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.NoComma">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.NoCommaAfterLast">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.NotLowerCase">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.SingleLineNotAllowed">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.ValueNotAligned">
+    <severity>0</severity>
+  </rule>
+  <rule ref="Squiz.Arrays.ArrayDeclaration.ValueNoNewline">
+    <severity>0</severity>
+  </rule>
+</ruleset>
diff --git a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php
index 911d523a0e..0766d48962 100644
--- a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php
+++ b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php
@@ -32,7 +32,7 @@ protected function setDatabaseDumpFiles() {
    */
   public function testDatabaseLoaded() {
     foreach (['user', 'node', 'system', 'update_test_schema'] as $module) {
-      $this->assertEqual(drupal_get_installed_schema_version($module), 8000, SafeMarkup::format('Module @module schema is 8000', ['@module' => $module]));
+      $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion($module), 8000, SafeMarkup::format('Module @module schema is 8000', ['@module' => $module]));
     }
 
     // Ensure that all {router} entries can be unserialized. If they cannot be
@@ -89,7 +89,7 @@ public function testUpdateHookN() {
     $this->assertEqual([], $container_cannot_be_saved_messages);
 
     // Ensure schema has changed.
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_schema', TRUE), 8001);
+    $this->assertEqual(\Drupal::service('schema.data')->getInstalledVersion('update_test_schema', TRUE), 8001);
     // Ensure the index was added for column a.
     $this->assertTrue(db_index_exists('update_test_schema_table', 'test'), 'Version 8001 of the update_test_schema module is installed.');
   }
