<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def ffill_query_in_range(expr, lower, upper, checkpoints=None, odo_kwargs=None, ts_field=TS_FIELD_NAME):
 """Query a blaze expression in a given time range properly forward filling from values that fall before the lower date. Parameters expr : Expr Bound blaze expression. lower : datetime The lower date to query for. upper : datetime The upper date to query for. checkpoints : Expr, optional Bound blaze expression for a checkpoints table from which to get a computed lower bound. odo_kwargs : dict, optional The extra keyword arguments to pass to ``odo``. ts_field : str, optional The name of the timestamp field in the given blaze expression. Returns ------- raw : pd.DataFrame A strict dataframe for the data in the given date range. This may start before the requested start date if a value is needed to ffill. """

    odo_kwargs = odo_kwargs or {}
    computed_lower, materialized_checkpoints = get_materialized_checkpoints(
        checkpoints,
        expr.fields,
        lower,
        odo_kwargs,
    )

    pred = expr[ts_field] <= upper

    if computed_lower is not None:
        # only constrain the lower date if we computed a new lower date
        pred &= expr[ts_field] >= computed_lower

    raw = pd.concat(
        (
            materialized_checkpoints,
            odo(
                expr[pred],
                pd.DataFrame,
                **odo_kwargs
            ),
        ),
        ignore_index=True,
    )
    raw.loc[:, ts_field] = raw.loc[:, ts_field].astype('datetime64[ns]')
    return raw