#include <bits/stdc++.h>
using namespace std;
template <typename T>
inline bool chkmax(T &a, const T &b) {
  return a < b ? a = b, 1 : 0;
}
template <typename T>
inline bool chkmin(T &a, const T &b) {
  return a > b ? a = b, 1 : 0;
}
inline int read() {
  int sum = 0, fg = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar())
    if (c == '-') fg = -1;
  for (; isdigit(c); c = getchar()) sum = (sum << 3) + (sum << 1) + (c ^ 0x30);
  return fg * sum;
}
const int maxn = 1e5 + 10;
set<int> S[maxn];
map<pair<int, int>, int> M;
namespace DSU {
int fa[maxn];
inline void init(int N) {
  for (int i = 1; i <= N; i++) fa[i] = i;
}
inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
inline void merge(int x, int y) {
  x = find(x), y = find(y);
  if (x == y) return;
  if (S[x].size() < S[y].size()) swap(x, y);
  fa[y] = x;
  S[x].insert(S[y].begin(), S[y].end());
}
}  // namespace DSU
int n, m, c, q;
inline void Add(int x, int y, int z) {
  S[DSU::find(x)].insert(y);
  if (M[make_pair(x, z)])
    DSU::merge(M[make_pair(x, z)], y);
  else
    M[make_pair(x, z)] = y;
}
inline bool query(int x, int y) {
  x = DSU::find(x);
  set<int>::iterator res = S[x].lower_bound(y);
  if (res != S[x].end() && *res == y) return 1;
  return 0;
}
int main() {
  n = read(), m = read(), c = read(), q = read();
  DSU::init(n);
  for (int i = 1; i <= n; i++) S[i].insert(i);
  for (int i = 1; i <= m; i++) {
    int x = read(), y = read(), z = read();
    Add(x, y, z), Add(y, x, z);
  }
  for (int i = 1; i <= q; i++) {
    char ch = getchar();
    if (ch == '+') {
      int x = read(), y = read(), z = read();
      Add(x, y, z), Add(y, x, z);
    } else {
      int x = read(), y = read();
      printf(query(x, y) ? "Yes\n" : "No\n");
    }
  }
  return 0;
}
