njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
    njs_object_t *object, const njs_value_t *key)
{
    double              num;
    njs_int_t           ret;
    njs_bool_t          own;
    njs_array_t         *array;
    njs_object_t        *proto;
    njs_object_prop_t   *prop;
    njs_typed_array_t   *tarray;
    njs_object_value_t  *ov;

    pq->lhq.proto = &njs_object_hash_proto;

    own = pq->own;
    pq->own = 1;

    proto = object;

    do {
        pq->prototype = proto;

        if (!njs_is_null_or_undefined_or_boolean(key)) {
            switch (proto->type) {
            case NJS_ARRAY:
                array = (njs_array_t *) proto;
                num = njs_key_to_index(key);

                if (njs_fast_path(njs_key_is_integer_index(num, key))) {
                    ret = njs_array_property_query(vm, pq, array, num);
                    if (njs_fast_path(ret != NJS_DECLINED)) {
                        return (ret == NJS_DONE) ? NJS_DECLINED : ret;
                    }
                }

                break;

            case NJS_TYPED_ARRAY:
                num = njs_key_to_index(key);
                if (njs_fast_path(njs_key_is_integer_index(num, key))) {
                    tarray = (njs_typed_array_t *) proto;
                    return njs_typed_array_property_query(vm, pq, tarray, num);
                }

                if (!isnan(num)) {
                    return NJS_DECLINED;
                }

                break;

            case NJS_OBJECT_VALUE:
                ov = (njs_object_value_t *) proto;
                if (!njs_is_string(&ov->value)) {
                    break;
                }

                num = njs_key_to_index(key);
                if (njs_fast_path(njs_key_is_integer_index(num, key))) {
                    ov = (njs_object_value_t *) proto;
                    ret = njs_string_property_query(vm, pq, &ov->value, num);
                    if (njs_fast_path(ret != NJS_DECLINED)) {
                        return ret;
                    }
                }

                break;

            default:
                break;
            }
        }

        ret = njs_lvlhsh_find(&proto->hash, &pq->lhq);

        if (ret == NJS_OK) {
            prop = pq->lhq.value;

            if (prop->type != NJS_WHITEOUT) {
                return ret;
            }

            if (pq->own) {
                pq->own_whiteout = prop;
            }

        } else {
            ret = njs_lvlhsh_find(&proto->shared_hash, &pq->lhq);

            if (ret == NJS_OK) {
                return njs_prop_private_copy(vm, pq);
            }
        }

        if (own) {
            return NJS_DECLINED;
        }

        pq->own = 0;
        proto = proto->__proto__;

    } while (proto != NULL);

    return NJS_DECLINED;
}
njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
    njs_object_t *object, const njs_value_t *key)
{
    double              num;
    njs_int_t           ret;
    njs_bool_t          own;
    njs_array_t         *array;
    njs_object_t        *proto;
    njs_object_prop_t   *prop;
    njs_typed_array_t   *tarray;
    njs_object_value_t  *ov;

    pq->lhq.proto = &njs_object_hash_proto;

    own = pq->own;
    pq->own = 1;

    proto = object;

    do {
        pq->prototype = proto;

        if (!njs_is_null_or_undefined_or_boolean(key)) {
            switch (proto->type) {
            case NJS_ARRAY:
                array = (njs_array_t *) proto;
                num = njs_key_to_index(key);

                if (njs_fast_path(njs_key_is_integer_index(num, key))) {
                    ret = njs_array_property_query(vm, pq, array, num);
                    if (njs_fast_path(ret != NJS_DECLINED)) {
                        return (ret == NJS_DONE) ? NJS_DECLINED : ret;
                    }
                }

                break;

            case NJS_TYPED_ARRAY:
                num = njs_key_to_index(key);
                if (njs_fast_path(njs_key_is_integer_index(num, key))) {
                    tarray = (njs_typed_array_t *) proto;
                    return njs_typed_array_property_query(vm, pq, tarray, num);
                }

                if (!isnan(num)) {
                    return NJS_DECLINED;
                }

                break;

            case NJS_OBJECT_VALUE:
                ov = (njs_object_value_t *) proto;
                if (!njs_is_string(&ov->value)) {
                    break;
                }

                num = njs_key_to_index(key);
                if (njs_fast_path(njs_key_is_integer_index(num, key))) {
                    ov = (njs_object_value_t *) proto;
                    ret = njs_string_property_query(vm, pq, &ov->value, num);
                    if (njs_fast_path(ret != NJS_DECLINED)) {
                        return ret;
                    }
                }

                break;

            default:
                break;
            }
        }

        ret = njs_lvlhsh_find(&proto->hash, &pq->lhq);

        if (ret == NJS_OK) {
            prop = pq->lhq.value;

            if (prop->type != NJS_WHITEOUT) {
                return ret;
            }

            if (pq->own) {
                pq->own_whiteout = prop;
            }

        } else {
            ret = njs_lvlhsh_find(&proto->shared_hash, &pq->lhq);

            if (ret == NJS_OK) {
                return njs_prop_private_copy(vm, pq);
            }
        }

        if (own) {
            return NJS_DECLINED;
        }

        pq->own = 0;
        proto = proto->__proto__;

    } while (proto != NULL);

    return NJS_DECLINED;
}