Status Check()

in src/search/ir_sema_checker.h [42:181]


  Status Check(Node *node) {
    if (auto v = dynamic_cast<SearchExpr *>(node)) {
      auto index_name = v->index->name;
      if (auto iter = index_map.Find(index_name, ns); iter != index_map.end()) {
        current_index = iter->second.get();
        v->index->info = current_index;

        GET_OR_RET(Check(v->select.get()));
        GET_OR_RET(Check(v->query_expr.get()));
        if (v->limit) GET_OR_RET(Check(v->limit.get()));
        if (v->sort_by) GET_OR_RET(Check(v->sort_by.get()));
        if (v->sort_by && v->sort_by->IsVectorField()) {
          if (!v->limit) {
            return {Status::NotOK, "expect a LIMIT clause for vector field to construct a KNN search"};
          }
          // TODO: allow hybrid query
          if (auto b = dynamic_cast<BoolLiteral *>(v->query_expr.get()); b == nullptr) {
            return {Status::NotOK, "KNN search cannot be combined with other query expressions"};
          }
        }
      } else {
        return {Status::NotOK, fmt::format("index `{}` not found", index_name)};
      }
    } else if (auto v [[maybe_unused]] = dynamic_cast<LimitClause *>(node)) {
      return Status::OK();
    } else if (auto v = dynamic_cast<SortByClause *>(node)) {
      if (auto iter = current_index->fields.find(v->field->name); iter == current_index->fields.end()) {
        return {Status::NotOK, fmt::format("field `{}` not found in index `{}`", v->field->name, current_index->name)};
      } else if (!iter->second.IsSortable()) {
        return {Status::NotOK, fmt::format("field `{}` is not sortable", v->field->name)};
      } else if (auto is_vector = iter->second.MetadataAs<redis::HnswVectorFieldMetadata>() != nullptr;
                 is_vector != v->IsVectorField()) {
        std::string not_str = is_vector ? "" : "not ";
        return {Status::NotOK,
                fmt::format("field `{}` is {}a vector field according to metadata and does {}expect a vector parameter",
                            v->field->name, not_str, not_str)};
      } else {
        v->field->info = &iter->second;
        if (v->IsVectorField()) {
          auto meta = v->field->info->MetadataAs<redis::HnswVectorFieldMetadata>();
          if (!v->field->info->HasIndex()) {
            return {Status::NotOK,
                    fmt::format("field `{}` is marked as NOINDEX and cannot be used for KNN search", v->field->name)};
          }
          if (v->vector->values.size() != meta->dim) {
            return {Status::NotOK,
                    fmt::format("vector should be of size `{}` for field `{}`", meta->dim, v->field->name)};
          }
        }
      }
    } else if (auto v = dynamic_cast<AndExpr *>(node)) {
      for (const auto &n : v->inners) {
        GET_OR_RET(Check(n.get()));
      }
    } else if (auto v = dynamic_cast<OrExpr *>(node)) {
      for (const auto &n : v->inners) {
        GET_OR_RET(Check(n.get()));
      }
    } else if (auto v = dynamic_cast<NotExpr *>(node)) {
      GET_OR_RET(Check(v->inner.get()));
    } else if (auto v = dynamic_cast<TagContainExpr *>(node)) {
      if (auto iter = current_index->fields.find(v->field->name); iter == current_index->fields.end()) {
        return {Status::NotOK, fmt::format("field `{}` not found in index `{}`", v->field->name)};
      } else if (auto meta = iter->second.MetadataAs<redis::TagFieldMetadata>(); !meta) {
        return {Status::NotOK, fmt::format("field `{}` is not a tag field", v->field->name)};
      } else {
        v->field->info = &iter->second;

        if (v->tag->val.empty()) {
          return {Status::NotOK, "tag cannot be an empty string"};
        }

        if (v->tag->val.find(meta->separator) != std::string::npos) {
          return {Status::NotOK, fmt::format("tag cannot contain the separator `{}`", meta->separator)};
        }
      }
    } else if (auto v = dynamic_cast<NumericCompareExpr *>(node)) {
      if (auto iter = current_index->fields.find(v->field->name); iter == current_index->fields.end()) {
        return {Status::NotOK, fmt::format("field `{}` not found in index `{}`", v->field->name, current_index->name)};
      } else if (!iter->second.MetadataAs<redis::NumericFieldMetadata>()) {
        return {Status::NotOK, fmt::format("field `{}` is not a numeric field", v->field->name)};
      } else {
        v->field->info = &iter->second;
      }
    } else if (auto v = dynamic_cast<VectorKnnExpr *>(node)) {
      if (auto iter = current_index->fields.find(v->field->name); iter == current_index->fields.end()) {
        return {Status::NotOK, fmt::format("field `{}` not found in index `{}`", v->field->name, current_index->name)};
      } else if (!iter->second.MetadataAs<redis::HnswVectorFieldMetadata>()) {
        return {Status::NotOK, fmt::format("field `{}` is not a vector field", v->field->name)};
      } else {
        v->field->info = &iter->second;

        if (!v->field->info->HasIndex()) {
          return {Status::NotOK,
                  fmt::format("field `{}` is marked as NOINDEX and cannot be used for KNN search", v->field->name)};
        }
        auto meta = v->field->info->MetadataAs<redis::HnswVectorFieldMetadata>();
        if (v->vector->values.size() != meta->dim) {
          return {Status::NotOK,
                  fmt::format("vector should be of size `{}` for field `{}`", meta->dim, v->field->name)};
        }
      }
    } else if (auto v = dynamic_cast<VectorRangeExpr *>(node)) {
      if (auto iter = current_index->fields.find(v->field->name); iter == current_index->fields.end()) {
        return {Status::NotOK, fmt::format("field `{}` not found in index `{}`", v->field->name, current_index->name)};
      } else if (!iter->second.MetadataAs<redis::HnswVectorFieldMetadata>()) {
        return {Status::NotOK, fmt::format("field `{}` is not a vector field", v->field->name)};
      } else {
        v->field->info = &iter->second;

        auto meta = v->field->info->MetadataAs<redis::HnswVectorFieldMetadata>();
        if (meta->distance_metric == redis::DistanceMetric::L2 && v->range->val < 0) {
          return {Status::NotOK, "range cannot be a negative number for l2 distance metric"};
        }

        if (meta->distance_metric == redis::DistanceMetric::COSINE && (v->range->val < 0 || v->range->val > 2)) {
          return {Status::NotOK, "range has to be between 0 and 2 for cosine distance metric"};
        }

        if (v->vector->values.size() != meta->dim) {
          return {Status::NotOK,
                  fmt::format("vector should be of size `{}` for field `{}`", meta->dim, v->field->name)};
        }
      }
    } else if (auto v = dynamic_cast<SelectClause *>(node)) {
      for (const auto &n : v->fields) {
        if (auto iter = current_index->fields.find(n->name); iter == current_index->fields.end()) {
          return {Status::NotOK, fmt::format("field `{}` not found in index `{}`", n->name, current_index->name)};
        } else {
          n->info = &iter->second;
        }
      }
    } else if (auto v [[maybe_unused]] = dynamic_cast<BoolLiteral *>(node)) {
      return Status::OK();
    } else {
      return {Status::NotOK, fmt::format("unexpected IR node type: {}", node->Name())};
    }

    return Status::OK();
  }