Repository: apache/superset
File: superset/migrations/versions/2025-06-06_00-39_363a9b1e8992_convert_metric_currencies_from_str_to_json.py (Python)

Code before:
    currency = Column(JSON)


def upgrade():
    bind = op.get_bind()
    session = db.Session(bind=bind)
    currency_configs = session.query(SqlMetric).filter(SqlMetric.currency.isnot(None))
    for metric in paginated_update(
        currency_configs,
        lambda current, total: logger.info((f"Upgrading {current}/{total} metrics")),
    ):
        while True:
            if isinstance(metric.currency, str):
                try:
                    metric.currency = json.loads(metric.currency)
                except Exception as e:
                    logger.error(
                        f"Error loading metric {metric.metric_name} as json: {e}"
                    )
                    metric.currency = {}
                    break
            else:
                break


def downgrade():
    """
    The downgrade just dumps the metric as a str. Might not retrieve the old value,
    but the syntax was a bug and shouldn't be preserved either way.
    """
    bind = op.get_bind()
    session = db.Session(bind=bind)
    currency_configs = session.query(SqlMetric).filter(SqlMetric.currency.isnot(None))
    for metric in paginated_update(
        currency_configs,
        lambda current, total: logger.info((f"Downgrading {current}/{total} metrics")),
    ):
        try:
            metric.currency = json.dumps(metric.currency)
        except Exception as e:
            logger.error(f"Error dumping metric {metric.metric_name} to string: {e}")
            metric.currency = {}


Review comment (suggestion):
Since this migration is done after mine, I think we want the downgrade to be a no-op, instead of storing as string. The reasoning is that after my migration we expect all currency to be JSON, the only reason they're not is because the frontend keeps stringifying. So the way I see it, the `upgrade` fixes the problem of values being strings instead of objects, and the `downgrade` shouldn't do anything.

Code after:
    currency = Column(JSON)


def upgrade():
    bind = op.get_bind()
    session = db.Session(bind=bind)
    currency_configs = session.query(SqlMetric).filter(SqlMetric.currency.isnot(None))
    for metric in paginated_update(
        currency_configs,
        lambda current, total: logger.info((f"Upgrading {current}/{total} metrics")),
    ):
        while True:
            if isinstance(metric.currency, str):
                try:
                    metric.currency = json.loads(metric.currency)
                except Exception as e:
                    logger.error(
                        f"Error loading metric {metric.metric_name} as json: {e}"
                    )
                    metric.currency = {}
                    break
            else:
                break


def downgrade():
    """
    No op downgrade.

    The downgrade could just do `metric.currency = json.dumps(metric.currency)`. However
    this is happening after `f1edd4a4d4f2` which already converted the currency column
    to JSON at the DB level, so we shouldn't have currencies in str anymore. It was the
    case because the client was still stringifying it.
    """
    pass