-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Add interface to abstract query result persistence #4147
Changes from all commits
3fa19c3
fda74a2
068b9ee
32de5dd
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 |
---|---|---|
|
@@ -232,17 +232,39 @@ class DataSourceGroup(db.Model): | |
__tablename__ = "data_source_groups" | ||
|
||
|
||
DESERIALIZED_DATA_ATTR = '_deserialized_data' | ||
|
||
class DBPersistence(object): | ||
@property | ||
def data(self): | ||
if self._data is None: | ||
return None | ||
|
||
if not hasattr(self, DESERIALIZED_DATA_ATTR): | ||
setattr(self, DESERIALIZED_DATA_ATTR, json_loads(self._data)) | ||
|
||
return self._deserialized_data | ||
|
||
@data.setter | ||
def data(self, data): | ||
if hasattr(self, DESERIALIZED_DATA_ATTR): | ||
delattr(self, DESERIALIZED_DATA_ATTR) | ||
self._data = data | ||
|
||
|
||
QueryResultPersistence = settings.dynamic_settings.QueryResultPersistence or DBPersistence | ||
|
||
@python_2_unicode_compatible | ||
@generic_repr('id', 'org_id', 'data_source_id', 'query_hash', 'runtime', 'retrieved_at') | ||
class QueryResult(db.Model, BelongsToOrgMixin): | ||
class QueryResult(db.Model, QueryResultPersistence, BelongsToOrgMixin): | ||
id = Column(db.Integer, primary_key=True) | ||
org_id = Column(db.Integer, db.ForeignKey('organizations.id')) | ||
org = db.relationship(Organization) | ||
data_source_id = Column(db.Integer, db.ForeignKey("data_sources.id")) | ||
data_source = db.relationship(DataSource, backref=backref('query_results')) | ||
query_hash = Column(db.String(32), index=True) | ||
query_text = Column('query', db.Text) | ||
data = Column(db.Text) | ||
_data = Column('data', db.Text) | ||
runtime = Column(postgresql.DOUBLE_PRECISION) | ||
retrieved_at = Column(db.DateTime(True)) | ||
|
||
|
@@ -256,7 +278,7 @@ def to_dict(self): | |
'id': self.id, | ||
'query_hash': self.query_hash, | ||
'query': self.query_text, | ||
'data': json_loads(self.data), | ||
'data': self.data, | ||
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. We now deserialize it in a single location instead of having every user call |
||
'data_source_id': self.data_source_id, | ||
'runtime': self.runtime, | ||
'retrieved_at': self.retrieved_at | ||
|
@@ -304,22 +326,12 @@ def store_result(cls, org, data_source, query_hash, query, data, run_time, retri | |
data_source=data_source, | ||
retrieved_at=retrieved_at, | ||
data=data) | ||
|
||
|
||
db.session.add(query_result) | ||
logging.info("Inserted query (%s) data; id=%s", query_hash, query_result.id) | ||
# TODO: Investigate how big an impact this select-before-update makes. | ||
queries = Query.query.filter( | ||
Query.query_hash == query_hash, | ||
Query.data_source == data_source | ||
) | ||
for q in queries: | ||
q.latest_query_data = query_result | ||
# don't auto-update the updated_at timestamp | ||
q.skip_updated_at = True | ||
db.session.add(q) | ||
query_ids = [q.id for q in queries] | ||
logging.info("Updated %s queries with result (%s).", len(query_ids), query_hash) | ||
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. We moved this to a separate class method on the |
||
|
||
return query_result, query_ids | ||
return query_result | ||
|
||
@property | ||
def groups(self): | ||
|
@@ -640,6 +652,26 @@ def all_groups_for_query_ids(cls, query_ids): | |
|
||
return db.session.execute(query, {'ids': tuple(query_ids)}).fetchall() | ||
|
||
@classmethod | ||
def update_latest_result(cls, query_result): | ||
# TODO: Investigate how big an impact this select-before-update makes. | ||
queries = Query.query.filter( | ||
Query.query_hash == query_result.query_hash, | ||
Query.data_source == query_result.data_source | ||
) | ||
jezdez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
for q in queries: | ||
q.latest_query_data = query_result | ||
# don't auto-update the updated_at timestamp | ||
q.skip_updated_at = True | ||
db.session.add(q) | ||
|
||
query_ids = [q.id for q in queries] | ||
logging.info("Updated %s queries with result (%s).", len(query_ids), query_result.query_hash) | ||
|
||
return query_ids | ||
|
||
|
||
def fork(self, user): | ||
forked_list = ['org', 'data_source', 'latest_query_data', 'description', | ||
'query_text', 'query_hash', 'options'] | ||
|
@@ -788,7 +820,7 @@ def get_by_id_and_org(cls, object_id, org): | |
return super(Alert, cls).get_by_id_and_org(object_id, org, Query) | ||
|
||
def evaluate(self): | ||
data = json_loads(self.query_rel.latest_query_data.data) | ||
data = self.query_rel.latest_query_data.data | ||
|
||
if data['rows'] and self.options['column'] in data['rows'][0]: | ||
operators = { | ||
|
@@ -825,7 +857,7 @@ def render_template(self, template): | |
if template is None: | ||
return '' | ||
|
||
data = json_loads(self.query_rel.latest_query_data.data) | ||
data = self.query_rel.latest_query_data.data | ||
host = base_url(self.query_rel.org) | ||
|
||
col_name = self.options['column'] | ||
|
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.
Shouldn't this also check if
self._deserialized_data
has already been set and return it if yes (and also unset it in the property setter below)?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.
In what case
_data
will beNone
and_deserialized_data
has some value?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.
This is basically a race condition when
_data
is set toNone
via the data property setter below when it was previously notNone
. Ifdata
should just be used as a read-only property I would suggest to remove the property setter.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 don't know. It feels like this complicates the code for no good reason, as this class has a very defined use 🤔
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.
Read your comment again and realized that actually making
_deserialized_data
in sync with the setter isn't that complex. Implemented in 16fbc5e + added tests.