Warn that the minimum SQLite version will change to 3.40.1 as of 2025.2 (#104298)

Co-authored-by: Robert Resch <robert@resch.dev>
pull/122947/head
J. Nick Koston 2024-07-31 08:13:04 -05:00 committed by GitHub
parent cddb3bb668
commit a35fa0e95a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 132 additions and 20 deletions

View File

@ -16,6 +16,10 @@
"backup_failed_out_of_resources": {
"title": "Database backup failed due to lack of resources",
"description": "The database backup stated at {start_time} failed due to lack of resources. The backup cannot be trusted and must be restarted. This can happen if the database is too large or if the system is under heavy load. Consider upgrading the system hardware or reducing the size of the database by decreasing the number of history days to keep or creating a filter."
},
"sqlite_too_old": {
"title": "Update SQLite to {min_version} or later to continue using the recorder",
"description": "Support for version {server_version} of SQLite is ending; the minimum supported version is {min_version}. Please upgrade your database software."
}
},
"services": {

View File

@ -96,6 +96,7 @@ MARIADB_WITH_FIXED_IN_QUERIES_108 = _simple_version("10.8.4")
MIN_VERSION_MYSQL = _simple_version("8.0.0")
MIN_VERSION_PGSQL = _simple_version("12.0")
MIN_VERSION_SQLITE = _simple_version("3.31.0")
UPCOMING_MIN_VERSION_SQLITE = _simple_version("3.40.1")
MIN_VERSION_SQLITE_MODERN_BIND_VARS = _simple_version("3.32.0")
@ -356,7 +357,7 @@ def _fail_unsupported_dialect(dialect_name: str) -> NoReturn:
raise UnsupportedDialect
def _fail_unsupported_version(
def _raise_if_version_unsupported(
server_version: str, dialect_name: str, minimum_version: str
) -> NoReturn:
"""Warn about unsupported database version."""
@ -373,16 +374,54 @@ def _fail_unsupported_version(
raise UnsupportedDialect
@callback
def _async_delete_issue_deprecated_version(
hass: HomeAssistant, dialect_name: str
) -> None:
"""Delete the issue about upcoming unsupported database version."""
ir.async_delete_issue(hass, DOMAIN, f"{dialect_name}_too_old")
@callback
def _async_create_issue_deprecated_version(
hass: HomeAssistant,
server_version: AwesomeVersion,
dialect_name: str,
min_version: AwesomeVersion,
) -> None:
"""Warn about upcoming unsupported database version."""
ir.async_create_issue(
hass,
DOMAIN,
f"{dialect_name}_too_old",
is_fixable=False,
severity=ir.IssueSeverity.CRITICAL,
translation_key=f"{dialect_name}_too_old",
translation_placeholders={
"server_version": str(server_version),
"min_version": str(min_version),
},
breaks_in_ha_version="2025.2.0",
)
def _extract_version_from_server_response_or_raise(
server_response: str,
) -> AwesomeVersion:
"""Extract version from server response."""
return AwesomeVersion(
server_response,
ensure_strategy=AwesomeVersionStrategy.SIMPLEVER,
find_first_match=True,
)
def _extract_version_from_server_response(
server_response: str,
) -> AwesomeVersion | None:
"""Attempt to extract version from server response."""
try:
return AwesomeVersion(
server_response,
ensure_strategy=AwesomeVersionStrategy.SIMPLEVER,
find_first_match=True,
)
return _extract_version_from_server_response_or_raise(server_response)
except AwesomeVersionException:
return None
@ -475,13 +514,27 @@ def setup_connection_for_dialect(
# as its persistent and isn't free to call every time.
result = query_on_connection(dbapi_connection, "SELECT sqlite_version()")
version_string = result[0][0]
version = _extract_version_from_server_response(version_string)
version = _extract_version_from_server_response_or_raise(version_string)
if not version or version < MIN_VERSION_SQLITE:
_fail_unsupported_version(
if version < MIN_VERSION_SQLITE:
_raise_if_version_unsupported(
version or version_string, "SQLite", MIN_VERSION_SQLITE
)
# No elif here since _raise_if_version_unsupported raises
if version < UPCOMING_MIN_VERSION_SQLITE:
instance.hass.add_job(
_async_create_issue_deprecated_version,
instance.hass,
version or version_string,
dialect_name,
UPCOMING_MIN_VERSION_SQLITE,
)
else:
instance.hass.add_job(
_async_delete_issue_deprecated_version, instance.hass, dialect_name
)
if version and version > MIN_VERSION_SQLITE_MODERN_BIND_VARS:
max_bind_vars = SQLITE_MODERN_MAX_BIND_VARS
@ -513,7 +566,7 @@ def setup_connection_for_dialect(
if is_maria_db:
if not version or version < MIN_VERSION_MARIA_DB:
_fail_unsupported_version(
_raise_if_version_unsupported(
version or version_string, "MariaDB", MIN_VERSION_MARIA_DB
)
if version and (
@ -529,7 +582,7 @@ def setup_connection_for_dialect(
)
elif not version or version < MIN_VERSION_MYSQL:
_fail_unsupported_version(
_raise_if_version_unsupported(
version or version_string, "MySQL", MIN_VERSION_MYSQL
)
@ -551,7 +604,7 @@ def setup_connection_for_dialect(
version_string = result[0][0]
version = _extract_version_from_server_response(version_string)
if not version or version < MIN_VERSION_PGSQL:
_fail_unsupported_version(
_raise_if_version_unsupported(
version or version_string, "PostgreSQL", MIN_VERSION_PGSQL
)

View File

@ -26,6 +26,8 @@ from homeassistant.components.recorder.models import (
process_timestamp,
)
from homeassistant.components.recorder.util import (
MIN_VERSION_SQLITE,
UPCOMING_MIN_VERSION_SQLITE,
end_incomplete_runs,
is_second_sunday,
resolve_period,
@ -223,9 +225,9 @@ def test_setup_connection_for_dialect_mysql(mysql_version) -> None:
@pytest.mark.parametrize(
"sqlite_version",
["3.31.0"],
[str(UPCOMING_MIN_VERSION_SQLITE)],
)
def test_setup_connection_for_dialect_sqlite(sqlite_version) -> None:
def test_setup_connection_for_dialect_sqlite(sqlite_version: str) -> None:
"""Test setting up the connection for a sqlite dialect."""
instance_mock = MagicMock()
execute_args = []
@ -276,10 +278,10 @@ def test_setup_connection_for_dialect_sqlite(sqlite_version) -> None:
@pytest.mark.parametrize(
"sqlite_version",
["3.31.0"],
[str(UPCOMING_MIN_VERSION_SQLITE)],
)
def test_setup_connection_for_dialect_sqlite_zero_commit_interval(
sqlite_version,
sqlite_version: str,
) -> None:
"""Test setting up the connection for a sqlite dialect with a zero commit interval."""
instance_mock = MagicMock(commit_interval=0)
@ -503,10 +505,6 @@ def test_supported_pgsql(caplog: pytest.LogCaptureFixture, pgsql_version) -> Non
"2.0.0",
"Version 2.0.0 of SQLite is not supported; minimum supported version is 3.31.0.",
),
(
"dogs",
"Version dogs of SQLite is not supported; minimum supported version is 3.31.0.",
),
],
)
def test_fail_outdated_sqlite(
@ -725,6 +723,63 @@ async def test_no_issue_for_mariadb_with_MDEV_25020(
assert database_engine.optimizer.slow_range_in_select is False
async def test_issue_for_old_sqlite(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
) -> None:
"""Test we create and delete an issue for old sqlite versions."""
instance_mock = MagicMock()
instance_mock.hass = hass
execute_args = []
close_mock = MagicMock()
min_version = str(MIN_VERSION_SQLITE)
def execute_mock(statement):
nonlocal execute_args
execute_args.append(statement)
def fetchall_mock():
nonlocal execute_args
if execute_args[-1] == "SELECT sqlite_version()":
return [[min_version]]
return None
def _make_cursor_mock(*_):
return MagicMock(execute=execute_mock, close=close_mock, fetchall=fetchall_mock)
dbapi_connection = MagicMock(cursor=_make_cursor_mock)
database_engine = await hass.async_add_executor_job(
util.setup_connection_for_dialect,
instance_mock,
"sqlite",
dbapi_connection,
True,
)
await hass.async_block_till_done()
issue = issue_registry.async_get_issue(DOMAIN, "sqlite_too_old")
assert issue is not None
assert issue.translation_placeholders == {
"min_version": str(UPCOMING_MIN_VERSION_SQLITE),
"server_version": min_version,
}
min_version = str(UPCOMING_MIN_VERSION_SQLITE)
database_engine = await hass.async_add_executor_job(
util.setup_connection_for_dialect,
instance_mock,
"sqlite",
dbapi_connection,
True,
)
await hass.async_block_till_done()
issue = issue_registry.async_get_issue(DOMAIN, "sqlite_too_old")
assert issue is None
assert database_engine is not None
@pytest.mark.skip_on_db_engine(["mysql", "postgresql"])
@pytest.mark.usefixtures("skip_by_db_engine")
async def test_basic_sanity_check(