We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
EL log files should not contain secrets
The full database URI is shown in debug logs:

```console
$ export MELTANO_CLI_LOG_LEVEL=debug
$ meltano invoke my-tap
2022-09-07T16:34:57.234152Z [info     ] Environment 'dev' is active
2022-09-07T16:34:57.338859Z [debug    ] Creating engine <meltano.core.project.Project object at 0x10e9702e0>@postgresql://***********
```

Where I redacted the username, password, etc. from the Postgres URI.

The full environment variables mapping log message may also contain secrets:

```console
2022-09-07T16:35:01.443284Z [debug    ] Env: {'USER': ...
```

_Raised by Tomas B in Office Hours._


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/meltano/core/db.py`
Content:
```
1 """Defines helpers related to the system database."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import time
7 
8 from sqlalchemy import create_engine
9 from sqlalchemy.engine import Connection, Engine
10 from sqlalchemy.exc import OperationalError
11 from sqlalchemy.orm import sessionmaker
12 from sqlalchemy.pool import NullPool
13 from sqlalchemy.sql import text
14 
15 from meltano.core.error import MeltanoError
16 from meltano.core.project import Project
17 
18 # Keep a Project → Engine mapping to serve
19 # the same engine for the same Project
20 _engines = {}
21 
22 
23 class MeltanoDatabaseCompatibilityError(MeltanoError):
24     """Raised when the database is not compatible with Meltano."""
25 
26     INSTRUCTION = (
27         "Upgrade your database to be compatible with Meltano or use a different "
28         "database"
29     )
30 
31     def __init__(self, reason: str):
32         """Initialize the error with a reason.
33 
34         Args:
35             reason: The reason why the database is not compatible.
36         """
37         super().__init__(reason, self.INSTRUCTION)
38 
39 
40 class NullConnectionStringError(MeltanoError):
41     """Raised when the database is not compatible with Meltano."""
42 
43     REASON = "The `database_uri` setting has a null value"
44     INSTRUCTION = (
45         "Verify that the `database_uri` setting points to a valid database connection "
46         "URI, or use `MELTANO_FF_STRICT_ENV_VAR_MODE=1 meltano config meltano list` "
47         "to check for missing environment variables"
48     )
49 
50     def __init__(self):
51         """Initialize the exception."""
52         super().__init__(self.REASON, self.INSTRUCTION)
53 
54 
55 def project_engine(
56     project: Project,
57     default: bool = False,
58 ) -> tuple[Engine, sessionmaker]:
59     """Create and register a SQLAlchemy engine for a Meltano project instance.
60 
61     Args:
62         project: The Meltano project that the engine will be connected to.
63         default: Whether the engine created should be stored as the default
64             engine for this project.
65 
66     Returns:
67         The engine, and a session maker bound to the engine.
68 
69     Raises:
70         NullConnectionStringError: The `database_uri` setting has a null value.
71     """
72     existing_engine = _engines.get(project)
73     if existing_engine:
74         return existing_engine
75 
76     engine_uri = project.settings.get("database_uri")
77     logging.debug(f"Creating engine '{project}@{engine_uri}'")
78 
79     if engine_uri is None:
80         raise NullConnectionStringError
81 
82     engine = create_engine(engine_uri, poolclass=NullPool)
83 
84     # Connect to the database to ensure it is available.
85     connect(
86         engine,
87         max_retries=project.settings.get("database_max_retries"),
88         retry_timeout=project.settings.get("database_retry_timeout"),
89     )
90 
91     check_database_compatibility(engine)
92     init_hook(engine)
93 
94     engine_session = (engine, sessionmaker(bind=engine))
95 
96     if default:
97         # register the default engine
98         _engines[project] = engine_session
99 
100     return engine_session
101 
102 
103 def connect(
104     engine: Engine,
105     max_retries: int,
106     retry_timeout: float,
107 ) -> Connection:
108     """Connect to the database.
109 
110     Args:
111         engine: The DB engine with which the check will be performed.
112         max_retries: The maximum number of retries that will be attempted.
113         retry_timeout: The number of seconds to wait between retries.
114 
115     Raises:
116         OperationalError: Error during DB connection - max retries exceeded.
117 
118     Returns:
119         A connection to the database.
120     """
121     attempt = 0
122     while True:
123         try:
124             return engine.connect()
125         except OperationalError:
126             if attempt >= max_retries:
127                 logging.error(
128                     f"Could not connect to the database after {attempt} "
129                     "attempts. Max retries exceeded.",
130                 )
131                 raise
132             attempt += 1
133             logging.info(
134                 f"DB connection failed. Will retry after {retry_timeout}s. "
135                 f"Attempt {attempt}/{max_retries}",
136             )
137             time.sleep(retry_timeout)
138 
139 
140 init_hooks = {
141     "sqlite": lambda x: x.execute("PRAGMA journal_mode=WAL"),
142 }
143 
144 
145 def init_hook(engine: Engine) -> None:
146     """Run the initialization hook for the provided DB engine.
147 
148     The initialization hooks are taken from the `meltano.core.db.init_hooks`
149     dictionary, which maps the dialect name of the engine to a unary function
150     which will be called with the provided DB engine.
151 
152     Args:
153         engine: The engine for which the init hook will be run.
154 
155     Raises:
156         Exception: The init hook raised an exception.
157     """
158     try:
159         hook = init_hooks[engine.dialect.name]
160     except KeyError:
161         return
162 
163     try:
164         hook(engine)
165     except Exception as ex:
166         raise Exception(f"Failed to initialize database: {ex!s}") from ex
167 
168 
169 def ensure_schema_exists(
170     engine: Engine,
171     schema_name: str,
172     grant_roles: tuple[str] = (),
173 ) -> None:
174     """Ensure the specified `schema_name` exists in the database.
175 
176     Args:
177         engine: The DB engine to be used.
178         schema_name: The name of the schema.
179         grant_roles: Roles to grant to the specified schema.
180     """
181     group_identifiers = ",".join(grant_roles)
182 
183     create_schema = text(f"CREATE SCHEMA IF NOT EXISTS {schema_name}")
184     grant_select_schema = text(
185         f"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema_name} GRANT SELECT ON "
186         f"TABLES TO {group_identifiers}",
187     )
188     grant_usage_schema = text(
189         f"GRANT USAGE ON SCHEMA {schema_name} TO {group_identifiers}",
190     )
191 
192     with engine.connect() as conn, conn.begin():
193         conn.execute(create_schema)
194         if grant_roles:
195             conn.execute(grant_select_schema)
196             conn.execute(grant_usage_schema)
197 
198     logging.info(f"Schema {schema_name} has been created successfully.")
199     for role in grant_roles:
200         logging.info(f"Usage has been granted for role: {role}.")
201 
202 
203 def check_database_compatibility(engine: Engine) -> None:
204     """Check that the database is compatible with Meltano.
205 
206     Args:
207         engine: The DB engine to be used. This should already be connected to
208             the database.
209 
210     Raises:
211         MeltanoDatabaseCompatibilityError: The database is not compatible with
212             Meltano.
213     """
214     dialect = engine.dialect.name
215     version = engine.dialect.server_version_info
216 
217     if dialect == "sqlite" and version < (3, 25, 1):
218         version_string = ".".join(map(str, version))
219         reason = (
220             f"Detected SQLite {version_string}, but Meltano requires at least 3.25.1"
221         )
222         raise MeltanoDatabaseCompatibilityError(reason)
223
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/meltano/core/db.py b/src/meltano/core/db.py
--- a/src/meltano/core/db.py
+++ b/src/meltano/core/db.py
@@ -4,6 +4,7 @@
 
 import logging
 import time
+from urllib.parse import urlparse
 
 from sqlalchemy import create_engine
 from sqlalchemy.engine import Connection, Engine
@@ -73,13 +74,27 @@
     if existing_engine:
         return existing_engine
 
-    engine_uri = project.settings.get("database_uri")
-    logging.debug(f"Creating engine '{project}@{engine_uri}'")
+    database_uri = project.settings.get("database_uri")
+    parsed_db_uri = urlparse(database_uri)
+    sanitized_db_uri = parsed_db_uri._replace(  # noqa: WPS437
+        netloc=(
+            f"{parsed_db_uri.username}:********@"  # user:pass auth case
+            if parsed_db_uri.password
+            else "********@"  # token auth case
+            if parsed_db_uri.username
+            else ""  # no auth case
+        )
+        + (parsed_db_uri.hostname or ""),
+    ).geturl()
+    logging.debug(
+        f"Creating DB engine for project at {str(project.root)!r} "
+        f"with DB URI {sanitized_db_uri!r}",
+    )
 
-    if engine_uri is None:
+    if database_uri is None:
         raise NullConnectionStringError
 
-    engine = create_engine(engine_uri, poolclass=NullPool)
+    engine = create_engine(database_uri, poolclass=NullPool)
 
     # Connect to the database to ensure it is available.
     connect(

{"golden_diff": "diff --git a/src/meltano/core/db.py b/src/meltano/core/db.py\n--- a/src/meltano/core/db.py\n+++ b/src/meltano/core/db.py\n@@ -4,6 +4,7 @@\n \n import logging\n import time\n+from urllib.parse import urlparse\n \n from sqlalchemy import create_engine\n from sqlalchemy.engine import Connection, Engine\n@@ -73,13 +74,27 @@\n     if existing_engine:\n         return existing_engine\n \n-    engine_uri = project.settings.get(\"database_uri\")\n-    logging.debug(f\"Creating engine '{project}@{engine_uri}'\")\n+    database_uri = project.settings.get(\"database_uri\")\n+    parsed_db_uri = urlparse(database_uri)\n+    sanitized_db_uri = parsed_db_uri._replace(  # noqa: WPS437\n+        netloc=(\n+            f\"{parsed_db_uri.username}:********@\"  # user:pass auth case\n+            if parsed_db_uri.password\n+            else \"********@\"  # token auth case\n+            if parsed_db_uri.username\n+            else \"\"  # no auth case\n+        )\n+        + (parsed_db_uri.hostname or \"\"),\n+    ).geturl()\n+    logging.debug(\n+        f\"Creating DB engine for project at {str(project.root)!r} \"\n+        f\"with DB URI {sanitized_db_uri!r}\",\n+    )\n \n-    if engine_uri is None:\n+    if database_uri is None:\n         raise NullConnectionStringError\n \n-    engine = create_engine(engine_uri, poolclass=NullPool)\n+    engine = create_engine(database_uri, poolclass=NullPool)\n \n     # Connect to the database to ensure it is available.\n     connect(\n", "issue": "EL log files should not contain secrets\nThe full database URI is shown in debug logs:\r\n\r\n```console\r\n$ export MELTANO_CLI_LOG_LEVEL=debug\r\n$ meltano invoke my-tap\r\n2022-09-07T16:34:57.234152Z [info     ] Environment 'dev' is active\r\n2022-09-07T16:34:57.338859Z [debug    ] Creating engine <meltano.core.project.Project object at 0x10e9702e0>@postgresql://***********\r\n```\r\n\r\nWhere I redacted the username, password, etc. from the Postgres URI.\r\n\r\nThe full environment variables mapping log message may also contain secrets:\r\n\r\n```console\r\n2022-09-07T16:35:01.443284Z [debug    ] Env: {'USER': ...\r\n```\r\n\r\n_Raised by Tomas B in Office Hours._\r\n\n", "before_files": [{"content": "\"\"\"Defines helpers related to the system database.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport time\n\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.engine import Connection, Engine\nfrom sqlalchemy.exc import OperationalError\nfrom sqlalchemy.orm import sessionmaker\nfrom sqlalchemy.pool import NullPool\nfrom sqlalchemy.sql import text\n\nfrom meltano.core.error import MeltanoError\nfrom meltano.core.project import Project\n\n# Keep a Project \u2192 Engine mapping to serve\n# the same engine for the same Project\n_engines = {}\n\n\nclass MeltanoDatabaseCompatibilityError(MeltanoError):\n    \"\"\"Raised when the database is not compatible with Meltano.\"\"\"\n\n    INSTRUCTION = (\n        \"Upgrade your database to be compatible with Meltano or use a different \"\n        \"database\"\n    )\n\n    def __init__(self, reason: str):\n        \"\"\"Initialize the error with a reason.\n\n        Args:\n            reason: The reason why the database is not compatible.\n        \"\"\"\n        super().__init__(reason, self.INSTRUCTION)\n\n\nclass NullConnectionStringError(MeltanoError):\n    \"\"\"Raised when the database is not compatible with Meltano.\"\"\"\n\n    REASON = \"The `database_uri` setting has a null value\"\n    INSTRUCTION = (\n        \"Verify that the `database_uri` setting points to a valid database connection \"\n        \"URI, or use `MELTANO_FF_STRICT_ENV_VAR_MODE=1 meltano config meltano list` \"\n        \"to check for missing environment variables\"\n    )\n\n    def __init__(self):\n        \"\"\"Initialize the exception.\"\"\"\n        super().__init__(self.REASON, self.INSTRUCTION)\n\n\ndef project_engine(\n    project: Project,\n    default: bool = False,\n) -> tuple[Engine, sessionmaker]:\n    \"\"\"Create and register a SQLAlchemy engine for a Meltano project instance.\n\n    Args:\n        project: The Meltano project that the engine will be connected to.\n        default: Whether the engine created should be stored as the default\n            engine for this project.\n\n    Returns:\n        The engine, and a session maker bound to the engine.\n\n    Raises:\n        NullConnectionStringError: The `database_uri` setting has a null value.\n    \"\"\"\n    existing_engine = _engines.get(project)\n    if existing_engine:\n        return existing_engine\n\n    engine_uri = project.settings.get(\"database_uri\")\n    logging.debug(f\"Creating engine '{project}@{engine_uri}'\")\n\n    if engine_uri is None:\n        raise NullConnectionStringError\n\n    engine = create_engine(engine_uri, poolclass=NullPool)\n\n    # Connect to the database to ensure it is available.\n    connect(\n        engine,\n        max_retries=project.settings.get(\"database_max_retries\"),\n        retry_timeout=project.settings.get(\"database_retry_timeout\"),\n    )\n\n    check_database_compatibility(engine)\n    init_hook(engine)\n\n    engine_session = (engine, sessionmaker(bind=engine))\n\n    if default:\n        # register the default engine\n        _engines[project] = engine_session\n\n    return engine_session\n\n\ndef connect(\n    engine: Engine,\n    max_retries: int,\n    retry_timeout: float,\n) -> Connection:\n    \"\"\"Connect to the database.\n\n    Args:\n        engine: The DB engine with which the check will be performed.\n        max_retries: The maximum number of retries that will be attempted.\n        retry_timeout: The number of seconds to wait between retries.\n\n    Raises:\n        OperationalError: Error during DB connection - max retries exceeded.\n\n    Returns:\n        A connection to the database.\n    \"\"\"\n    attempt = 0\n    while True:\n        try:\n            return engine.connect()\n        except OperationalError:\n            if attempt >= max_retries:\n                logging.error(\n                    f\"Could not connect to the database after {attempt} \"\n                    \"attempts. Max retries exceeded.\",\n                )\n                raise\n            attempt += 1\n            logging.info(\n                f\"DB connection failed. Will retry after {retry_timeout}s. \"\n                f\"Attempt {attempt}/{max_retries}\",\n            )\n            time.sleep(retry_timeout)\n\n\ninit_hooks = {\n    \"sqlite\": lambda x: x.execute(\"PRAGMA journal_mode=WAL\"),\n}\n\n\ndef init_hook(engine: Engine) -> None:\n    \"\"\"Run the initialization hook for the provided DB engine.\n\n    The initialization hooks are taken from the `meltano.core.db.init_hooks`\n    dictionary, which maps the dialect name of the engine to a unary function\n    which will be called with the provided DB engine.\n\n    Args:\n        engine: The engine for which the init hook will be run.\n\n    Raises:\n        Exception: The init hook raised an exception.\n    \"\"\"\n    try:\n        hook = init_hooks[engine.dialect.name]\n    except KeyError:\n        return\n\n    try:\n        hook(engine)\n    except Exception as ex:\n        raise Exception(f\"Failed to initialize database: {ex!s}\") from ex\n\n\ndef ensure_schema_exists(\n    engine: Engine,\n    schema_name: str,\n    grant_roles: tuple[str] = (),\n) -> None:\n    \"\"\"Ensure the specified `schema_name` exists in the database.\n\n    Args:\n        engine: The DB engine to be used.\n        schema_name: The name of the schema.\n        grant_roles: Roles to grant to the specified schema.\n    \"\"\"\n    group_identifiers = \",\".join(grant_roles)\n\n    create_schema = text(f\"CREATE SCHEMA IF NOT EXISTS {schema_name}\")\n    grant_select_schema = text(\n        f\"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema_name} GRANT SELECT ON \"\n        f\"TABLES TO {group_identifiers}\",\n    )\n    grant_usage_schema = text(\n        f\"GRANT USAGE ON SCHEMA {schema_name} TO {group_identifiers}\",\n    )\n\n    with engine.connect() as conn, conn.begin():\n        conn.execute(create_schema)\n        if grant_roles:\n            conn.execute(grant_select_schema)\n            conn.execute(grant_usage_schema)\n\n    logging.info(f\"Schema {schema_name} has been created successfully.\")\n    for role in grant_roles:\n        logging.info(f\"Usage has been granted for role: {role}.\")\n\n\ndef check_database_compatibility(engine: Engine) -> None:\n    \"\"\"Check that the database is compatible with Meltano.\n\n    Args:\n        engine: The DB engine to be used. This should already be connected to\n            the database.\n\n    Raises:\n        MeltanoDatabaseCompatibilityError: The database is not compatible with\n            Meltano.\n    \"\"\"\n    dialect = engine.dialect.name\n    version = engine.dialect.server_version_info\n\n    if dialect == \"sqlite\" and version < (3, 25, 1):\n        version_string = \".\".join(map(str, version))\n        reason = (\n            f\"Detected SQLite {version_string}, but Meltano requires at least 3.25.1\"\n        )\n        raise MeltanoDatabaseCompatibilityError(reason)\n", "path": "src/meltano/core/db.py"}], "after_files": [{"content": "\"\"\"Defines helpers related to the system database.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport time\nfrom urllib.parse import urlparse\n\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.engine import Connection, Engine\nfrom sqlalchemy.exc import OperationalError\nfrom sqlalchemy.orm import sessionmaker\nfrom sqlalchemy.pool import NullPool\nfrom sqlalchemy.sql import text\n\nfrom meltano.core.error import MeltanoError\nfrom meltano.core.project import Project\n\n# Keep a Project \u2192 Engine mapping to serve\n# the same engine for the same Project\n_engines = {}\n\n\nclass MeltanoDatabaseCompatibilityError(MeltanoError):\n    \"\"\"Raised when the database is not compatible with Meltano.\"\"\"\n\n    INSTRUCTION = (\n        \"Upgrade your database to be compatible with Meltano or use a different \"\n        \"database\"\n    )\n\n    def __init__(self, reason: str):\n        \"\"\"Initialize the error with a reason.\n\n        Args:\n            reason: The reason why the database is not compatible.\n        \"\"\"\n        super().__init__(reason, self.INSTRUCTION)\n\n\nclass NullConnectionStringError(MeltanoError):\n    \"\"\"Raised when the database is not compatible with Meltano.\"\"\"\n\n    REASON = \"The `database_uri` setting has a null value\"\n    INSTRUCTION = (\n        \"Verify that the `database_uri` setting points to a valid database connection \"\n        \"URI, or use `MELTANO_FF_STRICT_ENV_VAR_MODE=1 meltano config meltano list` \"\n        \"to check for missing environment variables\"\n    )\n\n    def __init__(self):\n        \"\"\"Initialize the exception.\"\"\"\n        super().__init__(self.REASON, self.INSTRUCTION)\n\n\ndef project_engine(\n    project: Project,\n    default: bool = False,\n) -> tuple[Engine, sessionmaker]:\n    \"\"\"Create and register a SQLAlchemy engine for a Meltano project instance.\n\n    Args:\n        project: The Meltano project that the engine will be connected to.\n        default: Whether the engine created should be stored as the default\n            engine for this project.\n\n    Returns:\n        The engine, and a session maker bound to the engine.\n\n    Raises:\n        NullConnectionStringError: The `database_uri` setting has a null value.\n    \"\"\"\n    existing_engine = _engines.get(project)\n    if existing_engine:\n        return existing_engine\n\n    database_uri = project.settings.get(\"database_uri\")\n    parsed_db_uri = urlparse(database_uri)\n    sanitized_db_uri = parsed_db_uri._replace(  # noqa: WPS437\n        netloc=(\n            f\"{parsed_db_uri.username}:********@\"  # user:pass auth case\n            if parsed_db_uri.password\n            else \"********@\"  # token auth case\n            if parsed_db_uri.username\n            else \"\"  # no auth case\n        )\n        + (parsed_db_uri.hostname or \"\"),\n    ).geturl()\n    logging.debug(\n        f\"Creating DB engine for project at {str(project.root)!r} \"\n        f\"with DB URI {sanitized_db_uri!r}\",\n    )\n\n    if database_uri is None:\n        raise NullConnectionStringError\n\n    engine = create_engine(database_uri, poolclass=NullPool)\n\n    # Connect to the database to ensure it is available.\n    connect(\n        engine,\n        max_retries=project.settings.get(\"database_max_retries\"),\n        retry_timeout=project.settings.get(\"database_retry_timeout\"),\n    )\n\n    check_database_compatibility(engine)\n    init_hook(engine)\n\n    engine_session = (engine, sessionmaker(bind=engine))\n\n    if default:\n        # register the default engine\n        _engines[project] = engine_session\n\n    return engine_session\n\n\ndef connect(\n    engine: Engine,\n    max_retries: int,\n    retry_timeout: float,\n) -> Connection:\n    \"\"\"Connect to the database.\n\n    Args:\n        engine: The DB engine with which the check will be performed.\n        max_retries: The maximum number of retries that will be attempted.\n        retry_timeout: The number of seconds to wait between retries.\n\n    Raises:\n        OperationalError: Error during DB connection - max retries exceeded.\n\n    Returns:\n        A connection to the database.\n    \"\"\"\n    attempt = 0\n    while True:\n        try:\n            return engine.connect()\n        except OperationalError:\n            if attempt >= max_retries:\n                logging.error(\n                    f\"Could not connect to the database after {attempt} \"\n                    \"attempts. Max retries exceeded.\",\n                )\n                raise\n            attempt += 1\n            logging.info(\n                f\"DB connection failed. Will retry after {retry_timeout}s. \"\n                f\"Attempt {attempt}/{max_retries}\",\n            )\n            time.sleep(retry_timeout)\n\n\ninit_hooks = {\n    \"sqlite\": lambda x: x.execute(\"PRAGMA journal_mode=WAL\"),\n}\n\n\ndef init_hook(engine: Engine) -> None:\n    \"\"\"Run the initialization hook for the provided DB engine.\n\n    The initialization hooks are taken from the `meltano.core.db.init_hooks`\n    dictionary, which maps the dialect name of the engine to a unary function\n    which will be called with the provided DB engine.\n\n    Args:\n        engine: The engine for which the init hook will be run.\n\n    Raises:\n        Exception: The init hook raised an exception.\n    \"\"\"\n    try:\n        hook = init_hooks[engine.dialect.name]\n    except KeyError:\n        return\n\n    try:\n        hook(engine)\n    except Exception as ex:\n        raise Exception(f\"Failed to initialize database: {ex!s}\") from ex\n\n\ndef ensure_schema_exists(\n    engine: Engine,\n    schema_name: str,\n    grant_roles: tuple[str] = (),\n) -> None:\n    \"\"\"Ensure the specified `schema_name` exists in the database.\n\n    Args:\n        engine: The DB engine to be used.\n        schema_name: The name of the schema.\n        grant_roles: Roles to grant to the specified schema.\n    \"\"\"\n    group_identifiers = \",\".join(grant_roles)\n\n    create_schema = text(f\"CREATE SCHEMA IF NOT EXISTS {schema_name}\")\n    grant_select_schema = text(\n        f\"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema_name} GRANT SELECT ON \"\n        f\"TABLES TO {group_identifiers}\",\n    )\n    grant_usage_schema = text(\n        f\"GRANT USAGE ON SCHEMA {schema_name} TO {group_identifiers}\",\n    )\n\n    with engine.connect() as conn, conn.begin():\n        conn.execute(create_schema)\n        if grant_roles:\n            conn.execute(grant_select_schema)\n            conn.execute(grant_usage_schema)\n\n    logging.info(f\"Schema {schema_name} has been created successfully.\")\n    for role in grant_roles:\n        logging.info(f\"Usage has been granted for role: {role}.\")\n\n\ndef check_database_compatibility(engine: Engine) -> None:\n    \"\"\"Check that the database is compatible with Meltano.\n\n    Args:\n        engine: The DB engine to be used. This should already be connected to\n            the database.\n\n    Raises:\n        MeltanoDatabaseCompatibilityError: The database is not compatible with\n            Meltano.\n    \"\"\"\n    dialect = engine.dialect.name\n    version = engine.dialect.server_version_info\n\n    if dialect == \"sqlite\" and version < (3, 25, 1):\n        version_string = \".\".join(map(str, version))\n        reason = (\n            f\"Detected SQLite {version_string}, but Meltano requires at least 3.25.1\"\n        )\n        raise MeltanoDatabaseCompatibilityError(reason)\n", "path": "src/meltano/core/db.py"}]}