Replace fixed DSL fields with parameterized metric expressions
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""One-time rewrite of saved queries to parameterized metric expressions."""
|
||||
|
||||
import re
|
||||
|
||||
import click
|
||||
|
||||
from .extensions import db
|
||||
from .models import SavedAnalysis, User
|
||||
from .stats import parse_query, query_text
|
||||
|
||||
|
||||
def rewrite(query):
|
||||
threshold = '10'
|
||||
lines = []
|
||||
for line in query.splitlines():
|
||||
match = re.fullmatch(r'\s*long run distance >= (\d+(?:\.\d+)?) km\s*', line)
|
||||
if match:
|
||||
threshold = match[1]
|
||||
else:
|
||||
lines.append(line)
|
||||
daily = any(line.strip() in {'days', 'health'} for line in lines)
|
||||
replacements = {
|
||||
'days_since_run': 'days_since_run()',
|
||||
'days_since_long_run': f'days_since_run(distance >= {threshold} km)',
|
||||
**{f'best_{km}km': f'best_time({km} km)' for km in (1, 2, 5, 10)},
|
||||
**{f'rolling_{days}d_distance': f'rolling_sum(distance, {days} days)' for days in (7, 28)},
|
||||
**{f'rolling_{days}d_count': (f'rolling_sum(sessions, {days} days)' if daily
|
||||
else f'rolling_count(distance, {days} days)') for days in (7, 28)},
|
||||
}
|
||||
pattern = re.compile(r'\b(' + '|'.join(replacements) + r')\b(?!\s*\()')
|
||||
result = []
|
||||
for line in lines:
|
||||
if not line.lstrip().startswith('#'):
|
||||
parts = re.split(r'("(?:[^"\\]|\\.)*")', line)
|
||||
for index in range(0, len(parts), 2):
|
||||
parts[index] = pattern.sub(lambda m: replacements[m[0]], parts[index])
|
||||
line = ''.join(parts)
|
||||
result.append(line)
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
def migrate(user):
|
||||
changes = []
|
||||
for row in db.session.scalars(db.select(SavedAnalysis).where(SavedAnalysis.owner_id == user.id)):
|
||||
updated = rewrite(row.query)
|
||||
if updated != row.query:
|
||||
# Validate every replacement before modifying any persisted query.
|
||||
canonical = query_text(parse_query({'query': updated}))
|
||||
parse_query({'query': canonical})
|
||||
changes.append((row, updated))
|
||||
for row, updated in changes:
|
||||
row.query = updated
|
||||
db.session.commit()
|
||||
return len(changes)
|
||||
|
||||
|
||||
def register_cli(app):
|
||||
@app.cli.command('migrate-analysis-expressions')
|
||||
@click.option('--email', required=True)
|
||||
def migrate_queries(email):
|
||||
user = db.session.scalar(db.select(User).filter_by(email=email.strip().lower()))
|
||||
if not user:
|
||||
raise click.ClickException('Account not found.')
|
||||
try:
|
||||
count = migrate(user)
|
||||
except ValueError as error:
|
||||
db.session.rollback()
|
||||
raise click.ClickException(str(error)) from None
|
||||
click.echo(f'Updated {count} saved analyses.')
|
||||
Reference in New Issue
Block a user