public static function get_versionnumber_by_stage($class, $stage, $id, $cache = true)
    {
        ReadingMode::validateStage($stage);
        $baseClass = DataObject::getSchema()->baseDataClass($class);
        $stageTable = DataObject::getSchema()->tableName($baseClass);
        if ($stage === static::LIVE) {
            $stageTable .= "_{$stage}";
        }

        // cached call
        if ($cache) {
            if (isset(self::$cache_versionnumber[$baseClass][$stage][$id])) {
                return self::$cache_versionnumber[$baseClass][$stage][$id] ?: null;
            } elseif (isset(self::$cache_versionnumber[$baseClass][$stage]['_complete'])) {
                // if the cache was marked as "complete" then we know the record is missing, just return null
                // this is used for treeview optimisation to avoid unnecessary re-requests for draft pages
                return null;
            }
        }

        // get version as performance-optimized SQL query (gets called for each record in the sitetree)
        $version = DB::prepared_query(
            "SELECT \"Version\" FROM \"$stageTable\" WHERE \"ID\" = ?",
            [$id]
        )->value();

        // cache value (if required)
        if ($cache) {
            if (!isset(self::$cache_versionnumber[$baseClass])) {
                self::$cache_versionnumber[$baseClass] = [];
            }

            if (!isset(self::$cache_versionnumber[$baseClass][$stage])) {
                self::$cache_versionnumber[$baseClass][$stage] = [];
            }

            // Internally store nulls as 0
            self::$cache_versionnumber[$baseClass][$stage][$id] = $version ?: 0;
        }

        return $version ?: null;
    }