int pdo_parse_params()

in hphp/runtime/ext/pdo/ext_pdo.cpp [2336:2635]


int pdo_parse_params(sp_PDOStatement stmt, const String& in, String &out) {
  Scanner s;
  const char *ptr;
  char *newbuffer;
  int t;
  int bindno = 0;
  int ret = 0;
  int newbuffer_len;
  Array params;
  req::ptr<PDOBoundParam> param;
  int query_type = PDO_PLACEHOLDER_NONE;
  struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;

  s.cur = (char*)in.data();
  s.lim = (char*)in.data() + in.size() + 1;

  /* phase 1: look for args */
  while ((t = scan(&s)) != PDO_PARSER_EOI) {
    if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
      if (t == PDO_PARSER_BIND) {
        int len = s.cur - s.tok;
        if ((in.data() < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
          continue;
        }
        query_type |= PDO_PLACEHOLDER_NAMED;
      } else {
        query_type |= PDO_PLACEHOLDER_POSITIONAL;
      }

      plc = req::make_raw<placeholder>();
      memset(plc, 0, sizeof(*plc));
      plc->next = NULL;
      plc->pos = s.tok;
      plc->len = s.cur - s.tok;
      plc->bindno = bindno++;

      if (placetail) {
        placetail->next = plc;
      } else {
        placeholders = plc;
      }
      placetail = plc;
    }
  }

  if (bindno == 0) {
    /* nothing to do; good! */
    return 0;
  }

  /* did the query make sense to me? */
  if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
    /* they mixed both types; punt */
    pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
                         "mixed named and positional parameters");
    ret = -1;
    goto clean_up;
  }

  if ((int)stmt->supports_placeholders == query_type &&
      !stmt->named_rewrite_template) {
    /* query matches native syntax */
    ret = 0;
    goto clean_up;
  }

  if (stmt->named_rewrite_template) {
    /* magic/hack.
     * We we pretend that the query was positional even if
     * it was named so that we fall into the
     * named rewrite case below.  Not too pretty,
     * but it works. */
    query_type = PDO_PLACEHOLDER_POSITIONAL;
  }

  params = stmt->bound_params;

  /* Do we have placeholders but no bound params */
  if (bindno && params.empty() &&
      stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
    pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound");
    ret = -1;
    goto clean_up;
  }

  if (!params.empty() && bindno != params.size() &&
      stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
    /* extra bit of validation for instances when same params are bound
       more then once */
    if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > params.size()) {
      int ok = 1;
      for (plc = placeholders; plc; plc = plc->next) {
        if (!params.exists(String(plc->pos, plc->len, CopyString))) {
          ok = 0;
          break;
        }
      }
      if (ok) {
        goto safe;
      }
    }
    pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
                         "number of bound variables does not match number "
                         "of tokens");
    ret = -1;
    goto clean_up;
  }
safe:
  /* what are we going to do ? */
  if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
    /* query generation */

    newbuffer_len = in.size();

    /* let's quote all the values */
    for (plc = placeholders; plc; plc = plc->next) {
      Variant vparam;
      if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
        vparam = params[plc->bindno];
      } else {
        String str(plc->pos, plc->len, CopyString);
        auto const arrkey = params.convertKey<IntishCast::Cast>(str);
        vparam = params[arrkey];
      }
      if (vparam.isNull()) {
        /* parameter was not defined */
        ret = -1;
        pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
                             "parameter was not defined");
        goto clean_up;
      }
      param = cast<PDOBoundParam>(vparam);
      if (stmt->dbh->conn()->support(PDOConnection::MethodQuoter)) {
        if (param->param_type == PDO_PARAM_LOB &&
            param->parameter.isResource()) {
          Variant buf = HHVM_FN(stream_get_contents)(
                        param->parameter.toResource());
          if (!same(buf, false)) {
            if (!stmt->dbh->conn()->quoter(buf.toString(), plc->quoted,
                                   param->param_type)) {
              /* bork */
              ret = -1;
              setPDOError(stmt->error_code, stmt->dbh->conn()->error_code);
              goto clean_up;
            }
          } else {
            pdo_raise_impl_error(stmt->dbh, stmt, "HY105",
                                 "Expected a stream resource");
            ret = -1;
            goto clean_up;
          }
        } else {
          do {
            switch (param->parameter.getType()) {
              case KindOfUninit:
              case KindOfNull:
                plc->quoted = "NULL";
                continue;

              case KindOfInt64:
              case KindOfDouble:
                plc->quoted = param->parameter.toString();
                continue;

              case KindOfBoolean:
                param->parameter = param->parameter.toInt64();
                // fallthru
              case KindOfPersistentString:
              case KindOfString:
              case KindOfPersistentVec:
              case KindOfVec:
              case KindOfPersistentDict:
              case KindOfDict:
              case KindOfPersistentKeyset:
              case KindOfKeyset:
              case KindOfObject:
              case KindOfResource:
              case KindOfRFunc:
              case KindOfFunc:
              case KindOfClass:
              case KindOfLazyClass:
              case KindOfClsMeth:
              case KindOfRClsMeth:
                if (!stmt->dbh->conn()->quoter(
                      param->parameter.toString(),
                      plc->quoted,
                      param->param_type)) {
                  /* bork */
                  ret = -1;
                  setPDOError(stmt->error_code, stmt->dbh->conn()->error_code);
                  goto clean_up;
                }
                continue;
            }
            not_reached();
          } while (0);
        }
      } else {
        plc->quoted = param->parameter.toString();
      }
      newbuffer_len += plc->quoted.size();
    }

rewrite:
    /* allocate output buffer */
    out = String(newbuffer_len, ReserveString);
    newbuffer = out.mutableData();

    /* and build the query */
    plc = placeholders;
    ptr = in.data();

    do {
      t = plc->pos - ptr;
      if (t) {
        memcpy(newbuffer, ptr, t);
        newbuffer += t;
      }
      memcpy(newbuffer, plc->quoted.data(), plc->quoted.size());
      newbuffer += plc->quoted.size();
      ptr = plc->pos + plc->len;

      plc = plc->next;
    } while (plc);

    t = (in.data() + in.size()) - ptr;
    if (t) {
      memcpy(newbuffer, ptr, t);
      newbuffer += t;
    }
    out.setSize(newbuffer - out.data());

    ret = 1;
    goto clean_up;

  } else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
    /* rewrite ? to :pdoX */
    StringBuffer idxbuf;
    const char *tmpl = stmt->named_rewrite_template ?
      stmt->named_rewrite_template : ":pdo%d";
    int bind_no = 1;

    newbuffer_len = in.size();

    for (plc = placeholders; plc; plc = plc->next) {
      int skip_map = 0;
      String name(plc->pos, plc->len, CopyString);
      auto const name_key =
        stmt->bound_param_map.convertKey<IntishCast::Cast>(name);

      /* check if bound parameter is already available */
      if (!strcmp(name.c_str(), "?") ||
          !stmt->bound_param_map.exists(name_key)) {
        idxbuf.printf(tmpl, bind_no++);
      } else {
        idxbuf.clear();
        idxbuf.append(stmt->bound_param_map[name_key].toString());
        skip_map = 1;
      }

      plc->quoted = idxbuf.detach();
      newbuffer_len += plc->quoted.size();

      if (!skip_map && stmt->named_rewrite_template) {
        /* create a mapping */
        stmt->bound_param_map.set(name_key,
                                  make_tv<KindOfString>(plc->quoted.get()));
      }

      /* map number to name */
      stmt->bound_param_map.set(plc->bindno, plc->quoted);
    }

    goto rewrite;

  } else {
    /* rewrite :name to ? */

    newbuffer_len = in.size();

    for (plc = placeholders; plc; plc = plc->next) {
      String name(plc->pos, plc->len, CopyString);
      stmt->bound_param_map.set(plc->bindno, name);
      plc->quoted = "?";
    }

    goto rewrite;
  }

clean_up:

  while (placeholders) {
    plc = placeholders;
    placeholders = plc->next;
    plc->quoted.reset();
    req::free(plc);
  }

  return ret;
}