#include <bits/stdc++.h>
using namespace std;
const long long maxn = 2e5 + 1, mod = 1e9 + 7, inf = 1e18, siz = 500;
struct ds {
  int t, x, y, z;
};
vector<pair<int, int> > G[maxn];
ds a[maxn];
int n, m, deg[maxn], root[maxn];
vector<int> group[maxn], near_group[maxn], Gn[maxn];
void inquery(int i) {
  int x, y, c;
  cin >> x >> y >> c;
  a[i] = {1, x, y, c};
  deg[x]++;
  deg[y]++;
  G[x].push_back(make_pair(c, 0));
  G[y].push_back(make_pair(c, 0));
}
void read() {
  int c, q, x, y;
  cin >> n >> m >> c >> q;
  for (int i = 1; i <= m; i++) inquery(i);
  char ch;
  for (int i = 1; i <= q; i++) {
    cin >> ch;
    if (ch == '+')
      inquery(i + m);
    else
      cin >> x >> y, a[i + m] = {2, x, y, 0};
  }
  m += q;
}
void build() {
  for (int i = 1; i <= n; i++) {
    sort(G[i].begin(), G[i].end());
    G[i].push_back(make_pair(0, 0));
    vector<pair<int, int> > tmp;
    for (int j = 0; j < G[i].size() - 1; j++)
      if (G[i][j].first != G[i][j + 1].first)
        tmp.push_back(make_pair(G[i][j].first, 0));
    G[i] = tmp;
  }
  for (int i = 1; i <= n; i++) root[i] = i, group[i].push_back(i);
}
void merg(int x, int y) {
  if (x == y) return;
  if (group[x].size() < group[y].size()) swap(x, y);
  for (int i = 0; i < group[y].size(); i++)
    root[group[y][i]] = x, group[x].push_back(group[y][i]);
  group[y].clear();
  vector<int> tmp(near_group[x].size() + near_group[y].size());
  merge(near_group[x].begin(), near_group[x].end(), near_group[y].begin(),
        near_group[y].end(), tmp.begin());
  tmp.push_back(0);
  near_group[x].clear();
  for (int i = 0; i < tmp.size() - 1; i++)
    if (tmp[i] != tmp[i + 1]) near_group[x].push_back(tmp[i]);
  near_group[y].clear();
}
void addto(int x, int y) {
  int tmp = lower_bound(near_group[x].begin(), near_group[x].end(), y) -
            near_group[x].begin();
  if (tmp == near_group[x].size() || near_group[x][tmp] != y) {
    vector<int> tt;
    for (int i = 0; i < tmp; i++) tt.push_back(near_group[x][i]);
    tt.push_back(y);
    for (int i = tmp; i < near_group[x].size(); i++)
      tt.push_back(near_group[x][i]);
    near_group[x] = tt;
  }
}
void update(int x, int y, int z) {
  int tmp =
      (lower_bound(G[x].begin(), G[x].end(), make_pair(z, 0)) - G[x].begin());
  if (G[x][tmp].second)
    merg(root[G[x][tmp].second], root[y]);
  else
    G[x][tmp].second = y;
  if (deg[y] >= siz) addto(root[x], y);
  Gn[x].push_back(y);
}
bool query(int x, int y) {
  if (root[x] == root[y]) return true;
  if (deg[y] >= siz) {
    int r = root[x];
    int tmp = lower_bound(near_group[r].begin(), near_group[r].end(), y) -
              near_group[r].begin();
    return (tmp < near_group[r].size()) && (near_group[r][tmp] == y);
  } else {
    for (int i = 0; i < Gn[y].size(); i++)
      if (root[Gn[y][i]] == root[x]) return true;
    return false;
  }
}
void process() {
  for (int i = 1; i <= m; i++)
    if (a[i].t == 1) {
      update(a[i].x, a[i].y, a[i].z);
      update(a[i].y, a[i].x, a[i].z);
    } else {
      if (query(a[i].x, a[i].y))
        printf("Yes\n");
      else
        printf("No\n");
    }
}
int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  read();
  build();
  process();
  return 0;
}
