#include <bits/stdc++.h>
using namespace std;
int nodes, initialRoads, totalColours, totalQueries;
const int NMAX = 100505;
struct query {
  int from, to, colour;
  bool addEdge;
};
vector<query> queries;
set<pair<int, int>> edges[NMAX];
set<int> reachable[NMAX];
vector<int> components[NMAX];
int whichComponent[NMAX];
void read() {
  cin >> nodes >> initialRoads >> totalColours >> totalQueries;
  queries.reserve(initialRoads + totalQueries);
  int from, to, colour;
  for (int roadIdx = 0; roadIdx < initialRoads; roadIdx++) {
    cin >> from >> to >> colour;
    queries.push_back({from, to, colour, true});
  }
  string type;
  for (int queryIdx = 0; queryIdx < totalQueries; queryIdx++) {
    cin >> type;
    if (type == "?") {
      cin >> from >> to;
      queries.push_back({from, to, -1, false});
    } else {
      cin >> from >> to >> colour;
      queries.push_back({from, to, colour, true});
    }
  }
}
void prepareStructs() {
  for (int node = 1; node <= nodes; node++) {
    components[node].push_back(node);
    whichComponent[node] = node;
    reachable[node].insert(whichComponent[node]);
  }
}
void updateReachable(int from, int oldComponent, int newComponent) {
  for (auto neighbour : edges[from]) {
    reachable[neighbour.second].erase(oldComponent);
    reachable[neighbour.second].insert(newComponent);
  }
  reachable[from].erase(oldComponent);
  reachable[from].insert(newComponent);
}
void addEdgeInRainbowGraph(int from, int to) {
  if (whichComponent[from] == whichComponent[to]) {
    return;
  }
  int component1 = whichComponent[from], component2 = whichComponent[to];
  if (components[component1].size() < components[component2].size()) {
    for (auto node : components[component1]) {
      updateReachable(node, component1, component2);
      whichComponent[node] = component2;
    }
    components[component2].insert(components[component2].end(),
                                  components[component1].begin(),
                                  components[component1].end());
    components[component1].clear();
  } else {
    for (auto node : components[component2]) {
      updateReachable(node, component2, component1);
      whichComponent[node] = component1;
    }
    components[component1].insert(components[component1].end(),
                                  components[component2].begin(),
                                  components[component2].end());
    components[component2].clear();
  }
}
int extractCandidateEdge(int from, int excludingNode, int colour) {
  auto edge = edges[from].lower_bound({colour, -1});
  if (edge != edges[from].end() && edge->second == excludingNode) {
    edge++;
  }
  if (edge != edges[from].end() && edge->first != colour) {
    return -1;
  }
  return edge != edges[from].end() ? edge->second : -1;
}
void addEdgesInRainbowGraph(int from, int to, int colour) {
  int neighbourIntoFrom = extractCandidateEdge(from, to, colour);
  if (neighbourIntoFrom != -1) {
    addEdgeInRainbowGraph(neighbourIntoFrom, to);
  }
  int neighbourIntoTo = extractCandidateEdge(to, from, colour);
  if (neighbourIntoTo != -1) {
    addEdgeInRainbowGraph(neighbourIntoTo, from);
  }
}
void addEdgeAndUpdateReachability(int from, int to, int colour) {
  edges[from].insert({colour, to});
  reachable[to].insert(whichComponent[from]);
}
void solve() {
  for (query &currQuery : queries) {
    if (currQuery.addEdge) {
      addEdgeAndUpdateReachability(currQuery.from, currQuery.to,
                                   currQuery.colour);
      addEdgeAndUpdateReachability(currQuery.to, currQuery.from,
                                   currQuery.colour);
      addEdgesInRainbowGraph(currQuery.from, currQuery.to, currQuery.colour);
    } else {
      if (reachable[currQuery.to].find(whichComponent[currQuery.from]) ==
          reachable[currQuery.to].end()) {
        cout << "No\n";
      } else {
        cout << "Yes\n";
      }
    }
  }
}
int main() {
  read();
  prepareStructs();
  solve();
  return 0;
}
