-
Notifications
You must be signed in to change notification settings - Fork 48
fix(utc): move DB config override to _apply_driver_defaults #198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
f296420
d1a3332
9259692
326e042
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,11 +9,13 @@ | |
|
|
||
| """Shared database object for Invenio.""" | ||
|
|
||
| import re | ||
| import warnings | ||
| from datetime import datetime, timezone | ||
|
|
||
| from flask_sqlalchemy import SQLAlchemy as FlaskSQLAlchemy | ||
| from sqlalchemy import Column, MetaData, event, util | ||
| from sqlalchemy.engine import Engine | ||
| from sqlalchemy.engine import Engine, make_url | ||
| from sqlalchemy.sql import text | ||
| from sqlalchemy.types import DateTime, TypeDecorator | ||
| from werkzeug.local import LocalProxy | ||
|
|
@@ -129,10 +131,12 @@ def __getattr__(self, name): | |
|
|
||
| return super().__getattr__(name) | ||
|
|
||
| def apply_driver_hacks(self, app, sa_url, options): | ||
| def _apply_driver_defaults(self, options, app): | ||
| """Call before engine creation.""" | ||
| # Don't forget to apply hacks defined on parent object. | ||
| super(SQLAlchemy, self).apply_driver_hacks(app, sa_url, options) | ||
| # Don't forget to apply defaults defined on parent object. | ||
| super(SQLAlchemy, self)._apply_driver_defaults(options, app) | ||
|
|
||
| sa_url = make_url(options["url"]) | ||
|
|
||
| if sa_url.drivername == "sqlite": | ||
| connect_args = options.setdefault("connect_args", {}) | ||
|
|
@@ -158,6 +162,30 @@ def adapt_proxy(proxy): | |
| elif sa_url.drivername == "postgresql+psycopg2": # pragma: no cover | ||
| from psycopg2.extensions import adapt, register_adapter | ||
|
|
||
| connect_args = options.setdefault("connect_args", {}) | ||
| options_override = "-c timezone=UTC" | ||
| if "options" not in connect_args: | ||
| # Ensure the database is using the UTC timezone for interpreting timestamps (PostgreSQL only). | ||
| # This overrides any default setting (e.g. in postgresql.conf). Invenio expects the DB to receive | ||
| # and provide UTC timestamps in all cases, so it's important that this doesn't get changed. | ||
| connect_args["options"] = options_override | ||
| elif ( | ||
| # Check that the exact correct timezone override is not in the existing `options`. | ||
| # The regex simply checks that the override is either at the end of the string or has | ||
| # a space after it. Otherwise, a value like `-c timezone=UTC+3` would still match. | ||
| # If the app is in dev mode and is auto-reloading, the correct timezone will have been added | ||
| # already by the code above, so we want to avoid showing a warning. | ||
| not re.search( | ||
| rf"{re.escape(options_override)}( |$)", connect_args["options"] | ||
| ) | ||
| ): | ||
| warnings.warn( | ||
| "It looks like you are manually passing command line options to libpq (via `connect_args.options` in `SQLALCHEMY_ENGINE_OPTIONS`). " | ||
| "To avoid unexpected behaviour, InvenioDB won't add an override to these options to set the time zone to UTC. " | ||
| "Please note that PostgreSQL databases used with Invenio must be in UTC. If your database or connection is configured with a non-UTC " | ||
| "timezone, please change this before continuing to avoid unexpected behaviour." | ||
| ) | ||
|
|
||
| def adapt_proxy(proxy): | ||
| """Get current object and try to adapt it again.""" | ||
| return adapt(proxy._get_current_object()) | ||
|
|
@@ -178,8 +206,6 @@ def escape_local_proxy(val, mapping): | |
| converters.conversions[LocalProxy] = escape_local_proxy | ||
| converters.encoders[LocalProxy] = escape_local_proxy | ||
|
|
||
| return sa_url, options | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yikes, I thought for a minute, we're not actually applying anything, but it looks like
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as i understand Pal, we are not applying anything, since the name has been changed and the method is not called!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed we don't need to return anything anymore, it uses |
||
|
|
||
|
|
||
| def do_sqlite_connect(dbapi_connection, connection_record): | ||
| """Ensure SQLite checks foreign key constraints. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: should we be a bit "defensive" and log a warning in case this is already set with potentially other values? I'm not sure what the lifecycle of
connect_argsis, and if our timezone patch is not applied, one would have a hard time figuring out where/why this is not happening...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is a warning enough in such a case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I agree it would be a very difficult issue to debug if an instance was overriding this and didn't notice the change. A warning should probably be enough since we still want to allow instances to override if needed, so we shouldn't raise an exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see two approaches (in case
optionsis already set):optionsvalue and append the-c timezone=UTCstring. on one hand one might argue that since the way we've built Invenio now, UTC timezone in Postgres is a "hard requirement" and thus without it you're basically running a "broken instance"I'm not decided yet on if in either of the above approaches, we should also provide a way out for people to supress the behavior...
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forgot to mention on Zenodo e.g. we set
application_nameto the hostname of the client usingSQLALCHEMY_ENGINE_OPTIONS, which I haven't tested if it fails... we're doing this because we're using Pgbouncer though, so that we can know better whcih clients connect to it: https://github.com/zenodo/zenodo-rdm/blob/72906255c1970970984af3cf2b4bc6f93bb87687/invenio.cfg#L149-L151There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Zenodo config should be fine since we're specifically checking for the
"options"key ofconnect_argshere (corresponding to the libpqoptionskey, not to be confused with theoptionsparameter passed into the_apply_driver_defaultsmethod) and only overriding that. Soapplication_namewould stay inconnect_argswithout any changes.In terms of the approaches, I think (A) might potentially be risky since it relies on us checking that the timezone isn't already in
options. There are multiple ways to include it and a potentially infinite set of valid values that all mean UTC. It is indeed a hard requirement so trying to set any other timezone is wrong, but I think maybe it's more reliable to just give a warning message if we see any timezone being set.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid that a warning will be difficult for anyone running this on production systems to see (since we're not usually paying attention at logs)... On the other hand, since this is something that will be introduced in InvenioRDM v14, maybe we just add it to the migration guide, so that folks make sure to check in their environment, e.g., by just running any
invenio ...command that accesses the DB.I would say, let's go with warning and @utnapischtim we should add a note in the v14 upgrade notes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added the warning