def gin_jsonb_value(criteria, node_forward=True):
    """
    Convert the given criteria into a query that matches the `edges_gin`
    table using the JSONB @> operator.

    In the table, we replace the 'start', 'end', 'rel', and 'dataset' URIs
    with lists of their URI prefixes. We query those slots with a
    single-element list, which will be a sub-list of the prefix list if
    it's a match.

    As an example, a query for {'start': '/c/en'} will become the GIN
    query {'start': ['/c/en']}, which will match indexed edges such as
    {
        'start': ['/c/en', '/c/en/dog'],
        'end': ['/c/en', '/c/en/bark'],
        'rel': ['/r/CapableOf'],
        ...
    }

    Bi-directional queries such as {'node': '/c/en/dog'} have to become two
    separate query dictionaries, one where 'node' is 'start' and 'other' is
    'end', and one where 'node' is 'end' and 'other' is 'start'.

    For that case, we take the optional `node_forward` argument that
    determines the mapping, and call this function twice, once where
    `node_forward` is True and once where it is False.
    """
    criteria_map = {
        'start': 'start',
        'end': 'end',
        'rel': 'rel',
        'dataset': 'dataset',
        # edges have a 'sources' element, but the query key we've historically
        # accepted is 'source', so let's just accept both
        'source': 'sources',
        'sources': 'sources',
    }
    if node_forward:
        criteria_map['node'] = 'start'
        criteria_map['other'] = 'end'
    else:
        criteria_map['node'] = 'end'
        criteria_map['other'] = 'start'

    query = {}
    for criterion_in, criterion_out in criteria_map.items():
        if criterion_in in criteria:
            assert isinstance(criteria[criterion_in], str)
            query[criterion_out] = [criteria[criterion_in]]
    return query