#include <bits/stdc++.h>
const int MAX_CITIES = 1e5;
int n, m, c, q;
int dsu[MAX_CITIES + 1] = {0};
std::set<int> adj[MAX_CITIES + 1];
std::map<int, int>* city_rainbows;
int getDsuRoot(int v) {
  int root = v;
  while (dsu[root] != 0) {
    root = dsu[root];
  }
  while (v != root) {
    int p = dsu[v];
    dsu[v] = root;
    v = p;
  }
  return root;
}
int mergeDsu(int x, int y) {
  int rootx = getDsuRoot(x);
  int rooty = getDsuRoot(y);
  if (adj[rootx].size() < adj[rooty].size()) std::swap(rootx, rooty);
  if (rootx != rooty) {
    dsu[rooty] = rootx;
    adj[rootx].insert(adj[rooty].begin(), adj[rooty].end());
    adj[rooty].clear();
  }
  return rootx;
}
bool isReachable(int x, int y) {
  bool ans = false;
  int rootx = getDsuRoot(x), rooty = getDsuRoot(y);
  if (rootx == rooty || adj[rootx].find(y) != adj[rootx].end()) ans = true;
  return ans;
}
void addEdge(int x, int y, int z) {
  std::map<int, int>&r1 = city_rainbows[x], &r2 = city_rainbows[y];
  if (r1.find(z) == r1.end()) {
    r1.emplace(std::make_pair(z, y));
  } else {
    mergeDsu(r1[z], y);
  }
  if (r2.find(z) == r2.end()) {
    r2.emplace(std::make_pair(z, x));
  } else {
    mergeDsu(r2[z], x);
  }
  int rootx = getDsuRoot(x);
  int rooty = getDsuRoot(y);
  if (rootx != rooty) {
    adj[rootx].insert(y);
    adj[rooty].insert(x);
  }
}
int main() {
  scanf("%d%d%d%d", &n, &m, &c, &q);
  city_rainbows = new std::map<int, int>[n + 1];
  for (int i = 0; i < m; i++) {
    int x, y, z;
    scanf("%d%d%d", &x, &y, &z);
    addEdge(x, y, z);
  }
  for (int i = 0; i < q; i++) {
    char type;
    int x, y, z;
    std::cin >> type;
    if (type == '+') {
      scanf("%d%d%d", &x, &y, &z);
      addEdge(x, y, z);
    } else if (type == '?') {
      scanf("%d%d", &x, &y);
      if (isReachable(x, y)) {
        printf("Yes\n");
      } else {
        printf("No\n");
      }
    }
  }
  delete[] city_rainbows;
  return 0;
}
