LCOV - code coverage report
Current view: top level - src/IR - Operator.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 21 362 5.8 %
Date: 2025-03-25 01:19:55 Functions: 7 68 10.3 %
Branches: 17 788 2.2 %

           Branch data     Line data    Source code
       1                 :            : #include <mutable/IR/Operator.hpp>
       2                 :            : 
       3                 :            : #include <mutable/catalog/Catalog.hpp>
       4                 :            : 
       5                 :            : 
       6                 :            : using namespace m;
       7                 :            : 
       8                 :            : 
       9                 :         81 : OperatorData::~OperatorData() { }
      10                 :            : 
      11                 :            : M_LCOV_EXCL_START
      12                 :            : std::ostream & m::operator<<(std::ostream &out, const Operator &op) {
      13                 :            :     std::vector<unsigned> depth({0}); // stack of indentation depths
      14                 :            :     struct indent {
      15                 :            :         std::ostream &out;
      16                 :            :         const Operator &op;
      17                 :            :         std::vector<unsigned> &depth;
      18                 :            : 
      19                 :            :         indent(std::ostream &out, const Operator &op, std::vector<unsigned> &depth) : out(out), op(op), depth(depth) {
      20                 :            :             M_insist(not depth.empty());
      21                 :            :             const unsigned n = depth.back();
      22                 :            :             depth.pop_back();
      23                 :            :             if (auto c = cast<const Consumer>(&op))
      24                 :            :                 depth.insert(depth.end(), c->children().size(), n+1);
      25                 :            :             if (n) out << '\n' << std::string(2 * (n-1), ' ') << "` ";
      26                 :            :         }
      27                 :            : 
      28                 :            :         ~indent() {
      29                 :            :             out << ' ' << op.schema();
      30                 :            :             if (op.has_info()) {
      31                 :            :                 auto &info = op.info();
      32                 :            :                 out << " <" << info.estimated_cardinality << '>';
      33                 :            :             }
      34                 :            :         }
      35                 :            :     };
      36                 :            :     visit(overloaded {
      37                 :            :         [&out, &depth](const CallbackOperator &op) { indent(out, op, depth).out << "CallbackOperator"; },
      38                 :            :         [&out, &depth](const PrintOperator &op) {
      39                 :            :             indent i(out, op, depth);
      40                 :            :             out << "PrintOperator";
      41                 :            :             if (&op.out == &std::cout) out << " to stdout";
      42                 :            :             else if (&op.out == &std::cerr) out << " to stderr";
      43                 :            :         },
      44                 :            :         [&out, &depth](const NoOpOperator &op) { indent(out, op, depth).out << "NoOpOperator"; },
      45                 :            :         [&out, &depth](const ScanOperator &op) {
      46                 :            :             indent(out, op, depth).out
      47                 :            :                 << "ScanOperator (" << op.store().table().name() << " AS " << op.alias() << ')';
      48                 :            :         },
      49                 :            :         [&out, &depth](const FilterOperator &op) { indent(out, op, depth).out << "FilterOperator " << op.filter(); },
      50                 :            :         [&out, &depth](const DisjunctiveFilterOperator &op) {
      51                 :            :             indent(out, op, depth).out << "DisjunctiveFilterOperator " << op.filter();
      52                 :            :         },
      53                 :            :         [&out, &depth](const JoinOperator &op) {
      54                 :            :             indent(out, op, depth).out << "JoinOperator " << op.predicate();
      55                 :            :         },
      56                 :            :         [&out, &depth](const ProjectionOperator &op) { indent(out, op, depth).out << "ProjectionOperator"; },
      57                 :            :         [&out, &depth](const LimitOperator &op) {
      58                 :            :             indent(out, op, depth).out << "LimitOperator " << op.limit() << ", " << op.offset();
      59                 :            :         },
      60                 :            :         [&out, &depth](const GroupingOperator &op) {
      61                 :            :             indent i(out, op, depth);
      62                 :            :             out << "GroupingOperator [";
      63                 :            :             for (auto begin = op.group_by().begin(), it = begin, end = op.group_by().end(); it != end; ++it) {
      64                 :            :                 if (it != begin) out << ", ";
      65                 :            :                 out << it->first.get();
      66                 :            :                 if (it->second.has_value())
      67                 :            :                     out << " AS " << it->second;
      68                 :            :             }
      69                 :            :             out << "] [";
      70                 :            :             for (auto begin = op.aggregates().begin(), it = begin, end = op.aggregates().end(); it != end; ++it) {
      71                 :            :                 if (it != begin) out << ", ";
      72                 :            :                 out << it->get();
      73                 :            :             }
      74                 :            :             out << ']';
      75                 :            :         },
      76                 :            :         [&out, &depth](const AggregationOperator &op) {
      77                 :            :             indent i(out, op, depth);
      78                 :            :             out << "AggregationOperator [";
      79                 :            :             for (auto begin = op.aggregates().begin(), it = begin, end = op.aggregates().end(); it != end; ++it) {
      80                 :            :                 if (it != begin) out << ", ";
      81                 :            :                 out << it->get();
      82                 :            :             }
      83                 :            :             out << ']';
      84                 :            :         },
      85                 :            :         [&out, &depth](const SortingOperator &op) {
      86                 :            :             indent i(out, op, depth);
      87                 :            :             out << "SortingOperator [";
      88                 :            :             for (auto begin = op.order_by().begin(), it = begin, end = op.order_by().end(); it != end; ++it) {
      89                 :            :                 if (it != begin) out << ", ";
      90                 :            :                 out << it->first.get() << ' ' << (it->second ? "ASC" : "DESC");
      91                 :            :             }
      92                 :            :             out << ']';
      93                 :            :         },
      94                 :            :     }, op, tag<ConstPreOrderOperatorVisitor>());
      95                 :            : 
      96                 :            :     return out;
      97                 :            : }
      98                 :            : M_LCOV_EXCL_STOP
      99                 :            : 
     100                 :          0 : void Operator::dot(std::ostream &out) const
     101                 :            : {
     102                 :          0 :     constexpr const char * const GRAPH_TYPE = "digraph";
     103                 :          0 :     constexpr const char * const EDGE = " -> ";
     104                 :          0 :     out << GRAPH_TYPE << " plan\n{\n"
     105                 :          0 :         << "    forcelabels=true;\n"
     106                 :          0 :         << "    overlap=false;\n"
     107                 :          0 :         << "    rankdir=BT;\n"
     108                 :          0 :         << "    graph [compound=true];\n"
     109                 :          0 :         << "    graph [fontname = \"DejaVu Sans\"];\n"
     110                 :          0 :         << "    node [fontname = \"DejaVu Sans\"];\n"
     111                 :          0 :         << "    edge [fontname = \"DejaVu Sans\"];\n";
     112                 :            : 
     113                 :            : #define q(X) '"' << X << '"' // quote
     114                 :            : #define id(X) q(std::hex << &X << std::dec) // convert virtual address to identifier
     115                 :          0 :     visit(overloaded {
     116                 :          0 :         [&out](const ScanOperator &op) {
     117   [ #  #  #  #  :          0 :             out << "    " << id(op) << " [label=<<B>" << html_escape(*op.alias()) << "</B>>];\n";
             #  #  #  # ]
     118                 :          0 :         },
     119                 :          0 :         [&out](const FilterOperator &op) {
     120                 :          0 :             out << "    " << id(op) << " [label=<<B>σ</B><SUB><FONT COLOR=\"0.0 0.0 0.25\" POINT-SIZE=\"10\">"
     121   [ #  #  #  # ]:          0 :                 << html_escape(to_string(op.filter()))
     122         [ #  # ]:          0 :                 << "</FONT></SUB>>];\n"
     123   [ #  #  #  #  :          0 :                 << "    " << id(*op.child(0)) << EDGE << id(op) << ";\n";
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
             #  #  #  # ]
     124                 :          0 :         },
     125                 :          0 :         [&out](const DisjunctiveFilterOperator &op) {
     126                 :          0 :             out << "    " << id(op) << " [label=<<B>σ</B><SUB><FONT COLOR=\"0.0 0.0 0.25\" POINT-SIZE=\"10\">";
     127                 :          0 :             const auto &clause = op.filter()[0];
     128         [ #  # ]:          0 :             for (auto it = clause.cbegin(); it != clause.cend(); ++it) {
     129         [ #  # ]:          0 :                 if (it != clause.cbegin()) out << " → ";
     130   [ #  #  #  # ]:          0 :                 out << html_escape(to_string(*it));
     131                 :          0 :             }
     132                 :          0 :             out << "</FONT></SUB>>];\n"
     133                 :          0 :                 << "    " << id(*op.child(0)) << EDGE << id(op) << ";\n";
     134                 :          0 :         },
     135                 :          0 :         [&out](const JoinOperator &op) {
     136                 :          0 :             out << "    " << id(op) << " [label=<<B>⋈</B><SUB><FONT COLOR=\"0.0 0.0 0.25\" POINT-SIZE=\"10\">"
     137   [ #  #  #  # ]:          0 :                 << html_escape(to_string(op.predicate()))
     138         [ #  # ]:          0 :                 << "</FONT></SUB>>];\n";
     139                 :            : 
     140         [ #  # ]:          0 :             for (auto c : op.children())
     141                 :          0 :                 out << "    " << id(*c) << EDGE << id(op) << ";\n";
     142                 :          0 :         },
     143                 :          0 :         [&out](const ProjectionOperator &op) {
     144                 :          0 :             out << id(op) << " [label=<<B>π</B><SUB><FONT COLOR=\"0.0 0.0 0.25\" POINT-SIZE=\"10\">";
     145                 :          0 :             const auto &P = op.projections();
     146         [ #  # ]:          0 :             for (auto it = P.begin(); it != P.end(); ++it) {
     147         [ #  # ]:          0 :                 if (it != P.begin()) out << ", ";
     148                 :          0 :                 out << it->first;
     149         [ #  # ]:          0 :                 if (it->second.has_value())
     150                 :          0 :                     out << " AS " << it->second;
     151                 :          0 :             }
     152                 :          0 :             out << "</FONT></SUB>>];\n";
     153                 :            : 
     154         [ #  # ]:          0 :             if (not op.children().empty())
     155                 :          0 :                 out << id(*op.child(0)) << EDGE << id(op) << ";\n";
     156                 :          0 :         },
     157                 :          0 :         [&out](const LimitOperator &op) {
     158                 :          0 :             out << "    " << id(op) << " [label=<<B>λ</B><SUB><FONT COLOR=\"0.0 0.0 0.25\" POINT-SIZE=\"10\">"
     159                 :          0 :                 << op.limit();
     160         [ #  # ]:          0 :             if (op.offset()) out << ", " << op.offset();
     161                 :          0 :             out << "</FONT></SUB>>];\n"
     162                 :          0 :                 << "    " << id(*op.child(0)) << EDGE << id(op) << ";\n";
     163                 :          0 :         },
     164                 :          0 :         [&out](const GroupingOperator &op) {
     165                 :          0 :             out << "    " << id(op) << " [label=<<B>γ</B><SUB><FONT COLOR=\"0.0 0.0 0.25\" POINT-SIZE=\"10\">";
     166                 :            : 
     167                 :          0 :             const auto &G = op.group_by();
     168                 :          0 :             const auto &A = op.aggregates();
     169                 :            : 
     170         [ #  # ]:          0 :             for (auto it = G.begin(); it != G.end(); ++it) {
     171         [ #  # ]:          0 :                 if (it != G.begin()) out << ", ";
     172                 :          0 :                 std::ostringstream oss;
     173         [ #  # ]:          0 :                 oss << it->first.get();
     174   [ #  #  #  #  :          0 :                 out << html_escape(oss.str());
                   #  # ]
     175   [ #  #  #  # ]:          0 :                 if (it->second.has_value())
     176   [ #  #  #  #  :          0 :                     out << html_escape(*it->second);
             #  #  #  # ]
     177                 :          0 :             }
     178                 :            : 
     179   [ #  #  #  # ]:          0 :             if (G.size() and A.size()) out << ", ";
     180                 :            : 
     181         [ #  # ]:          0 :             for (auto it = A.begin(); it != A.end(); ++it) {
     182         [ #  # ]:          0 :                 if (it != A.begin()) out << ", ";
     183                 :          0 :                 out << it->get();
     184                 :          0 :             }
     185                 :            : 
     186                 :          0 :             out << "</FONT></SUB>>];\n"
     187                 :          0 :                 << "    " << id(*op.child(0)) << EDGE << id(op) << ";\n";
     188                 :          0 :         },
     189                 :          0 :         [&out](const AggregationOperator &op) {
     190                 :          0 :             out << "    " << id(op) << " [label=<<B>Γ</B><SUB><FONT COLOR=\"0.0 0.0 0.25\" POINT-SIZE=\"10\">";
     191                 :          0 :             const auto &A = op.aggregates();
     192         [ #  # ]:          0 :             for (auto it = A.begin(); it != A.end(); ++it) {
     193         [ #  # ]:          0 :                 if (it != A.begin()) out << ", ";
     194                 :          0 :                 out << it->get();
     195                 :          0 :             }
     196                 :            : 
     197                 :          0 :             out << "</FONT></SUB>>];\n"
     198                 :          0 :                 << "    " << id(*op.child(0)) << EDGE << id(op) << ";\n";
     199                 :          0 :         },
     200                 :          0 :         [&out](const SortingOperator &op) {
     201                 :          0 :             out << "    " << id(op) << " [label=<<B>ω</B><SUB><FONT COLOR=\"0.0 0.0 0.25\" POINT-SIZE=\"10\">";
     202                 :            : 
     203                 :          0 :             const auto &O = op.order_by();
     204         [ #  # ]:          0 :             for (auto it = O.begin(); it != O.end(); ++it) {
     205         [ #  # ]:          0 :                 if (it != O.begin()) out << ", ";
     206                 :          0 :                 out << it->first.get() << ' ' << (it->second ? "ASC" : "DESC");
     207                 :          0 :             }
     208                 :            : 
     209                 :          0 :             out << "</FONT></SUB>>];\n"
     210                 :          0 :                 << "    " << id(*op.child(0)) << EDGE << id(op) << ";\n";
     211                 :          0 :         },
     212                 :          0 :         [](auto&&) { /* nothing to be done */ }
     213                 :            :     }, *this, tag<ConstPostOrderOperatorVisitor>());
     214                 :            : #undef id
     215                 :            : #undef q
     216                 :          0 :     out << "}\n";
     217                 :          0 : }
     218                 :            : 
     219                 :            : M_LCOV_EXCL_START
     220                 :            : void Operator::dump(std::ostream &out) const { out << *this << std::endl; }
     221                 :            : void Operator::dump() const { dump(std::cerr); }
     222                 :            : M_LCOV_EXCL_STOP
     223                 :            : 
     224                 :        243 : ProjectionOperator::ProjectionOperator(std::vector<projection_type> projections)
     225                 :         81 :     : projections_(std::move(projections))
     226                 :        243 : {
     227                 :            :     /* Compute the schema of the operator. */
     228   [ #  #  +  - ]:         81 :     auto &S = schema();
     229   [ #  #  +  + ]:       1167 :     for (auto &[proj, alias] : projections_) {
     230   [ #  #  +  - ]:        362 :         auto ty = proj.get().type();
     231                 :        362 :         Schema::entry_type::constraints_t constraints{0};
     232   [ #  #  #  #  :        362 :         if (not proj.get().can_be_null())
             +  -  -  + ]
     233   [ #  #  #  # ]:          0 :             constraints |= Schema::entry_type::NOT_NULLABLE;
     234   [ #  #  #  #  :        362 :         if (alias.has_value()) { // alias was given
             +  -  -  + ]
     235   [ #  #  #  #  :          0 :             Schema::Identifier id(alias.assert_not_none());
             #  #  #  # ]
     236   [ #  #  #  #  :          0 :             S.add(std::move(id), ty, constraints);
             #  #  #  # ]
     237   [ #  #  #  #  :        362 :         } else if (auto D = cast<const ast::Designator>(proj)) { // no alias, but designator -> keep name
             +  -  +  - ]
     238   [ #  #  #  #  :        362 :             Schema::Identifier id(D->table_name.text, D->attr_name.text.assert_not_none());
          #  #  +  -  +  
                -  +  - ]
     239   [ #  #  #  #  :        362 :             S.add(std::move(id), ty, constraints);
             +  -  +  - ]
     240                 :        362 :         } else { // no designator, no alias -> derive name
     241   [ #  #  #  #  :          0 :             if (is<const ast::Constant>(proj)) {
             #  #  #  # ]
     242                 :            :                 // TODO: use `Expr::is_constant()` once interpretation of constant expressions is supported
     243   [ #  #  #  #  :          0 :                 S.add(Schema::Identifier::GetConstant(), ty, constraints);
             #  #  #  # ]
     244                 :          0 :             } else {
     245   [ #  #  #  # ]:          0 :                 std::ostringstream oss;
     246   [ #  #  #  # ]:          0 :                 oss << proj.get();
     247   [ #  #  #  #  :          0 :                 Schema::Identifier id(Catalog::Get().pool(oss.str().c_str()));
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     248   [ #  #  #  #  :          0 :                 S.add(std::move(id), ty, constraints);
             #  #  #  # ]
     249                 :          0 :             }
     250                 :            :         }
     251                 :            :     }
     252                 :         81 : }
     253                 :            : 
     254                 :          0 : GroupingOperator::GroupingOperator(std::vector<group_type> group_by,
     255                 :            :                                    std::vector<std::reference_wrapper<const ast::FnApplicationExpr>> aggregates)
     256                 :          0 :     : group_by_(std::move(group_by))
     257                 :          0 :     , aggregates_(std::move(aggregates))
     258                 :          0 : {
     259   [ #  #  #  # ]:          0 :     auto &C = Catalog::Get();
     260   [ #  #  #  # ]:          0 :     auto &S = schema();
     261   [ #  #  #  # ]:          0 :     std::ostringstream oss;
     262                 :            : 
     263                 :            :     {
     264   [ #  #  #  # ]:          0 :         for (auto &[grp, alias] : group_by_) {
     265   [ #  #  #  #  :          0 :             auto pt = as<const PrimitiveType>(grp.get().type());
             #  #  #  # ]
     266                 :          0 :             Schema::entry_type::constraints_t constraints{0};
     267   [ #  #  #  # ]:          0 :             if (group_by.size() == 1)
     268   [ #  #  #  # ]:          0 :                 constraints |= Schema::entry_type::UNIQUE;
     269   [ #  #  #  #  :          0 :             if (not grp.get().can_be_null())
             #  #  #  # ]
     270   [ #  #  #  # ]:          0 :                 constraints |= Schema::entry_type::NOT_NULLABLE;
     271   [ #  #  #  #  :          0 :             if (alias.has_value()) {
             #  #  #  # ]
     272   [ #  #  #  #  :          0 :                 S.add(alias.assert_not_none(), pt->as_scalar(), constraints);
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
                      # ]
     273   [ #  #  #  #  :          0 :             } else if (auto D = cast<const ast::Designator>(grp)) { // designator -> keep name
             #  #  #  # ]
     274   [ #  #  #  #  :          0 :                 Schema::Identifier id(D->attr_name.text.assert_not_none()); // w/o table name
             #  #  #  # ]
     275   [ #  #  #  #  :          0 :                 S.add(std::move(id), pt->as_scalar(), constraints);
          #  #  #  #  #  
                #  #  # ]
     276                 :          0 :             } else {
     277   [ #  #  #  #  :          0 :                 oss.str("");
             #  #  #  # ]
     278   [ #  #  #  # ]:          0 :                 oss << grp.get();
     279   [ #  #  #  #  :          0 :                 auto alias = C.pool(oss.str().c_str());
             #  #  #  # ]
     280   [ #  #  #  #  :          0 :                 S.add(std::move(alias), pt->as_scalar(), constraints);
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     281                 :          0 :             }
     282                 :            :         }
     283                 :            :     }
     284                 :            : 
     285   [ #  #  #  # ]:          0 :     for (auto &e : aggregates_) {
     286   [ #  #  #  # ]:          0 :         auto ty = e.get().type();
     287   [ #  #  #  #  :          0 :         oss.str("");
             #  #  #  # ]
     288   [ #  #  #  # ]:          0 :         oss << e.get();
     289   [ #  #  #  #  :          0 :         auto alias = C.pool(oss.str().c_str());
             #  #  #  # ]
     290                 :          0 :         Schema::entry_type::constraints_t constraints{0};
     291   [ #  #  #  #  :          0 :         if (not e.get().can_be_null()) // group cannot be empty, thus no default NULL will occur
             #  #  #  # ]
     292   [ #  #  #  # ]:          0 :             constraints |= Schema::entry_type::NOT_NULLABLE;
     293   [ #  #  #  #  :          0 :         S.add(std::move(alias), ty, constraints);
          #  #  #  #  #  
                #  #  # ]
     294                 :          0 :     }
     295                 :          0 : }
     296                 :            : 
     297                 :          0 : AggregationOperator::AggregationOperator(std::vector<std::reference_wrapper<const ast::FnApplicationExpr>> aggregates)
     298                 :          0 :     : aggregates_(std::move(aggregates))
     299                 :          0 : {
     300   [ #  #  #  # ]:          0 :     auto &C = Catalog::Get();
     301   [ #  #  #  # ]:          0 :     auto &S = schema();
     302   [ #  #  #  # ]:          0 :     std::ostringstream oss;
     303   [ #  #  #  # ]:          0 :     for (auto &e : aggregates_) {
     304   [ #  #  #  # ]:          0 :         auto ty = e.get().type();
     305   [ #  #  #  #  :          0 :         oss.str("");
             #  #  #  # ]
     306   [ #  #  #  # ]:          0 :         oss << e.get();
     307   [ #  #  #  #  :          0 :         auto alias = C.pool(oss.str().c_str());
             #  #  #  # ]
     308                 :          0 :         Schema::entry_type::constraints_t constraints{Schema::entry_type::UNIQUE}; // since a single tuple is produced
     309   [ #  #  #  #  :          0 :         if (e.get().get_function().fnid == Function::FN_COUNT) // COUNT cannot be NULL (even for empty input)
             #  #  #  # ]
     310   [ #  #  #  # ]:          0 :             constraints |= Schema::entry_type::NOT_NULLABLE;
     311   [ #  #  #  #  :          0 :         S.add(std::move(alias), ty, constraints);
          #  #  #  #  #  
                #  #  # ]
     312                 :          0 :     }
     313                 :          0 : }
     314                 :            : 
     315                 :            : 
     316                 :            : /*======================================================================================================================
     317                 :            :  * accept()
     318                 :            :  *====================================================================================================================*/
     319                 :            : 
     320                 :            : #define ACCEPT(CLASS) \
     321                 :            :     void CLASS::accept(OperatorVisitor &V) { V(*this); } \
     322                 :            :     void CLASS::accept(ConstOperatorVisitor &V) const { V(*this); }
     323                 :        787 : M_OPERATOR_LIST(ACCEPT)
     324                 :            : #undef ACCEPT
     325                 :            : 
     326                 :            : 
     327                 :            : /*======================================================================================================================
     328                 :            :  * minimize_schema()
     329                 :            :  *====================================================================================================================*/
     330                 :            : 
     331                 :          0 : struct SchemaMinimizer : OperatorVisitor
     332                 :            : {
     333                 :            :     private:
     334                 :            :     Schema required;
     335                 :          0 :     bool is_top_of_plan_ = true;
     336                 :            : 
     337                 :            :     public:
     338                 :            :     using OperatorVisitor::operator();
     339                 :            : 
     340                 :            : #define DECLARE(CLASS) void operator()(Const<CLASS> &op) override;
     341                 :            :     M_OPERATOR_LIST(DECLARE)
     342                 :            : #undef DECLARE
     343                 :            : 
     344                 :            :     private:
     345                 :            :     /** Add the constraints of entries from \p constraints to matching entries in \p schema except the
     346                 :            :      * \p excluded_constraints.  Adapts only the first \p n entries of \p schema. */
     347                 :          0 :     void add_constraints(Schema &schema, const Schema &constraints, std::size_t n,
     348                 :            :                          Schema::entry_type::constraints_t excluded_constraints = Schema::entry_type::constraints_t{0})
     349                 :            :     {
     350                 :          0 :         M_insist(n <= schema.num_entries(), "invalid length");
     351         [ #  # ]:          0 :         for (std::size_t idx = 0; idx < n; ++idx) {
     352                 :          0 :             auto &e = schema[idx];
     353                 :          0 :             auto it = constraints.find(e.id);
     354         [ #  # ]:          0 :             if (it != constraints.end())
     355                 :          0 :                 e.constraints |= it->constraints & ~excluded_constraints; // merge constraints except excluded ones
     356                 :          0 :         }
     357                 :          0 :     }
     358                 :          0 :     void add_constraints(Schema &schema, const Schema &constraints,
     359                 :            :                          Schema::entry_type::constraints_t excluded_constraints = Schema::entry_type::constraints_t{0})
     360                 :            :     {
     361                 :          0 :         add_constraints(schema, constraints, schema.num_entries(), excluded_constraints);
     362                 :          0 :     }
     363                 :            : };
     364                 :            : 
     365                 :          0 : void SchemaMinimizer::operator()(ScanOperator &op)
     366                 :            : {
     367         [ #  # ]:          0 :     if (is_top_of_plan_) // scan is top of plan and leaf at the same time
     368                 :          0 :         return; // still provide all entries of the table
     369                 :            : 
     370                 :          0 :     required = required & op.schema(); // intersect with scan operator schema to add constraints to the required schema
     371                 :          0 :     op.schema() = required; // the scan operator produces exactly those attributes required by the ancestors
     372                 :          0 : }
     373                 :            : 
     374                 :          0 : void SchemaMinimizer::operator()(CallbackOperator &op)
     375                 :            : {
     376                 :          0 :     (*this)(*op.child(0)); // this operator does not affect what is required; nothing to be done
     377                 :          0 :     op.schema() = op.child(0)->schema();
     378                 :          0 : }
     379                 :            : 
     380                 :          0 : void SchemaMinimizer::operator()(PrintOperator &op)
     381                 :            : {
     382                 :          0 :     (*this)(*op.child(0)); // this operator does not affect what is required; nothing to be done
     383                 :          0 :     op.schema() = op.child(0)->schema();
     384                 :          0 : }
     385                 :            : 
     386                 :          0 : void SchemaMinimizer::operator()(NoOpOperator &op)
     387                 :            : {
     388                 :          0 :     (*this)(*op.child(0)); // this operator does not affect what is required; nothing to be done
     389                 :          0 :     op.schema() = op.child(0)->schema();
     390                 :          0 : }
     391                 :            : 
     392                 :          0 : void SchemaMinimizer::operator()(FilterOperator &op)
     393                 :            : {
     394         [ #  # ]:          0 :     if (is_top_of_plan_) {
     395                 :          0 :         required = op.schema(); // require everything
     396                 :          0 :         is_top_of_plan_ = false;
     397                 :          0 :     } else {
     398                 :          0 :         auto required_by_op = op.filter().get_required(); // add what's required to evaluate the filter predicate
     399   [ #  #  #  #  :          0 :         M_insist(required == (required & op.schema()), "required must be subset of operator schema");
             #  #  #  # ]
     400   [ #  #  #  # ]:          0 :         op.schema() = required; // set schema to what is required above
     401         [ #  # ]:          0 :         required |= required_by_op; // add what's required to evaluate the filter predicate
     402                 :          0 :     }
     403                 :          0 :     (*this)(*op.child(0));
     404                 :          0 :     add_constraints(op.schema(), op.child(0)->schema()); // add constraints from child
     405                 :          0 : }
     406                 :            : 
     407                 :          0 : void SchemaMinimizer::operator()(DisjunctiveFilterOperator &op)
     408                 :            : {
     409                 :          0 :     (*this)(as<FilterOperator>(op)); // delegate to FilterOperator
     410                 :          0 : }
     411                 :            : 
     412                 :          0 : void SchemaMinimizer::operator()(JoinOperator &op)
     413                 :            : {
     414                 :          0 :     Schema required_from_below;
     415         [ #  # ]:          0 :     if (is_top_of_plan_) {
     416   [ #  #  #  # ]:          0 :         required_from_below = op.schema(); // require everything
     417                 :          0 :         is_top_of_plan_ = false;
     418                 :          0 :     } else {
     419   [ #  #  #  #  :          0 :         required_from_below = required | op.predicate().get_required(); // what we need and all operators above us
                   #  # ]
     420   [ #  #  #  #  :          0 :         M_insist(required == (required & op.schema()), "required must be subset of operator schema");
             #  #  #  # ]
     421   [ #  #  #  # ]:          0 :         op.schema() = required;
     422                 :            :     }
     423   [ #  #  #  # ]:          0 :     for (auto c : const_cast<const JoinOperator&>(op).children()) {
     424   [ #  #  #  # ]:          0 :         required = required_from_below & c->schema(); // what we need from this child
     425         [ #  # ]:          0 :         (*this)(*c);
     426   [ #  #  #  #  :          0 :         add_constraints(op.schema(), c->schema(), JoinOperator::REMOVED_CONSTRAINTS); // add constraints from child except removed ones
                   #  # ]
     427                 :            :     }
     428                 :          0 : }
     429                 :            : 
     430                 :          0 : void SchemaMinimizer::operator()(ProjectionOperator &op)
     431                 :            : {
     432                 :          0 :     Schema required_by_op;
     433                 :          0 :     Schema ours;
     434                 :            : 
     435                 :          0 :     std::size_t pos_out = 0;
     436   [ #  #  #  # ]:          0 :     for (std::size_t pos_in = 0; pos_in != op.projections().size(); ++pos_in) {
     437         [ #  # ]:          0 :         M_insist(pos_out <= pos_in);
     438         [ #  # ]:          0 :         auto &proj = op.projections()[pos_in];
     439   [ #  #  #  # ]:          0 :         auto &e = op.schema()[pos_in];
     440                 :            : 
     441   [ #  #  #  #  :          0 :         if (is_top_of_plan_ or required.has(e.id)) {
                   #  # ]
     442   [ #  #  #  # ]:          0 :             required_by_op |= proj.first.get().get_required();
     443   [ #  #  #  #  :          0 :             op.projections()[pos_out++] = std::move(op.projections()[pos_in]);
                   #  # ]
     444   [ #  #  #  # ]:          0 :             ours.add(e);
     445                 :          0 :         }
     446                 :          0 :     }
     447   [ #  #  #  # ]:          0 :     M_insist(pos_out <= op.projections().size());
     448         [ #  # ]:          0 :     const auto &dummy = op.projections()[0];
     449   [ #  #  #  # ]:          0 :     op.projections().resize(pos_out, dummy);
     450                 :            : 
     451         [ #  # ]:          0 :     op.schema() = std::move(ours);
     452                 :          0 :     required = std::move(required_by_op);
     453                 :          0 :     is_top_of_plan_ = false;
     454   [ #  #  #  # ]:          0 :     if (not const_cast<const ProjectionOperator&>(op).children().empty()) {
     455   [ #  #  #  # ]:          0 :         (*this)(*op.child(0));
     456   [ #  #  #  #  :          0 :         add_constraints(op.schema(), op.child(0)->schema()); // add constraints from child
             #  #  #  # ]
     457                 :          0 :     }
     458                 :          0 : }
     459                 :            : 
     460                 :          0 : void SchemaMinimizer::operator()(LimitOperator &op)
     461                 :            : {
     462                 :          0 :     (*this)(*op.child(0));
     463                 :          0 :     op.schema() = op.child(0)->schema();
     464                 :          0 : }
     465                 :            : 
     466                 :          0 : void SchemaMinimizer::operator()(GroupingOperator &op)
     467                 :            : {
     468                 :          0 :     Catalog &C = Catalog::Get();
     469                 :            : 
     470                 :          0 :     Schema ours;
     471                 :          0 :     Schema required_by_us;
     472                 :            : 
     473   [ #  #  #  # ]:          0 :     auto it = op.schema().cbegin();
     474   [ #  #  #  # ]:          0 :     for (auto &[grp, _] : op.group_by()) {
     475   [ #  #  #  # ]:          0 :         required_by_us |= grp.get().get_required();
     476   [ #  #  #  # ]:          0 :         ours.add(*it++); // copy grouping keys
     477                 :            :     }
     478                 :            : 
     479   [ #  #  #  # ]:          0 :     if (not op.aggregates().empty()) {
     480         [ #  # ]:          0 :         std::ostringstream oss;
     481                 :          0 :         std::size_t pos_out = 0;
     482   [ #  #  #  # ]:          0 :         for (std::size_t pos_in = 0; pos_in != op.aggregates().size(); ++pos_in) {
     483         [ #  # ]:          0 :             M_insist(pos_out <= pos_in);
     484         [ #  # ]:          0 :             auto &agg = op.aggregates()[pos_in];
     485                 :            : 
     486   [ #  #  #  # ]:          0 :             oss.str("");
     487         [ #  # ]:          0 :             oss << agg.get();
     488   [ #  #  #  #  :          0 :             Schema::Identifier agg_id(C.pool(oss.str()));
                   #  # ]
     489                 :            : 
     490   [ #  #  #  #  :          0 :             if (is_top_of_plan_ or required.has(agg_id)) { // if first, require everything
                   #  # ]
     491         [ #  # ]:          0 :                 for (auto &arg : agg.get().args)
     492   [ #  #  #  # ]:          0 :                     required_by_us |= arg->get_required();
     493   [ #  #  #  # ]:          0 :                 op.aggregates()[pos_out++] = std::move(op.aggregates()[pos_in]); // keep aggregate
     494   [ #  #  #  #  :          0 :                 ours.add(op.schema()[agg_id].second);
             #  #  #  # ]
     495                 :          0 :             }
     496                 :          0 :         }
     497   [ #  #  #  # ]:          0 :         M_insist(pos_out <= op.aggregates().size());
     498         [ #  # ]:          0 :         const ast::FnApplicationExpr &dummy = op.aggregates()[0].get();
     499   [ #  #  #  # ]:          0 :         op.aggregates().resize(pos_out, dummy); // discard unrequired aggregates
     500                 :          0 :     }
     501                 :            : 
     502         [ #  # ]:          0 :     op.schema() = std::move(ours);
     503                 :          0 :     required = std::move(required_by_us);
     504                 :          0 :     is_top_of_plan_ = false;
     505   [ #  #  #  # ]:          0 :     (*this)(*op.child(0));
     506   [ #  #  #  #  :          0 :     add_constraints(op.schema(), op.child(0)->schema(), op.group_by().size()); // add constraints to grouping keys from child
          #  #  #  #  #  
                      # ]
     507                 :          0 : }
     508                 :            : 
     509                 :          0 : void SchemaMinimizer::operator()(AggregationOperator &op)
     510                 :            : {
     511                 :          0 :     M_insist(not op.aggregates().empty());
     512                 :          0 :     Catalog &C = Catalog::Get();
     513                 :          0 :     std::ostringstream oss;
     514                 :            : 
     515                 :          0 :     Schema required_by_op; // the AggregationOperator doesn't care what later operators require
     516                 :          0 :     Schema ours;
     517                 :            : 
     518                 :          0 :     std::size_t pos_out = 0;
     519   [ #  #  #  # ]:          0 :     for (std::size_t pos_in = 0; pos_in != op.aggregates().size(); ++pos_in) {
     520         [ #  # ]:          0 :         M_insist(pos_out <= pos_in);
     521         [ #  # ]:          0 :         auto &agg = op.aggregates()[pos_in];
     522                 :            : 
     523   [ #  #  #  # ]:          0 :         oss.str("");
     524         [ #  # ]:          0 :         oss << agg.get();
     525   [ #  #  #  #  :          0 :         Schema::Identifier agg_id(C.pool(oss.str()));
                   #  # ]
     526                 :            : 
     527   [ #  #  #  #  :          0 :         if (is_top_of_plan_ or required.has(agg_id)) { // if first, require everything
                   #  # ]
     528         [ #  # ]:          0 :             for (auto &arg : agg.get().args)
     529   [ #  #  #  # ]:          0 :                 required_by_op |= arg->get_required();
     530   [ #  #  #  # ]:          0 :             op.aggregates()[pos_out++] = std::move(op.aggregates()[pos_in]); // keep aggregate
     531   [ #  #  #  #  :          0 :             ours.add(op.schema()[agg_id].second);
             #  #  #  # ]
     532                 :          0 :         }
     533                 :          0 :     }
     534   [ #  #  #  # ]:          0 :     M_insist(pos_out <= op.aggregates().size());
     535         [ #  # ]:          0 :     const ast::FnApplicationExpr &dummy = op.aggregates()[0].get();
     536   [ #  #  #  # ]:          0 :     op.aggregates().resize(pos_out, dummy); // discard unrequired aggregates
     537                 :            : 
     538         [ #  # ]:          0 :     op.schema() = std::move(ours);
     539                 :          0 :     required = std::move(required_by_op);
     540                 :          0 :     is_top_of_plan_ = false;
     541   [ #  #  #  # ]:          0 :     (*this)(*op.child(0));
     542                 :            :     /* do not add constraints from child since aggregates are newly computed */
     543                 :          0 : }
     544                 :            : 
     545                 :          0 : void SchemaMinimizer::operator()(SortingOperator &op)
     546                 :            : {
     547         [ #  # ]:          0 :     if (is_top_of_plan_) {
     548                 :          0 :         required = op.schema(); // require everything
     549                 :          0 :         is_top_of_plan_ = false;
     550                 :          0 :     } else {
     551                 :          0 :         Schema required_by_op;
     552   [ #  #  #  # ]:          0 :         for (auto &ord : op.order_by())
     553   [ #  #  #  # ]:          0 :             required_by_op |= ord.first.get().get_required(); // we require *all* expressions to order by
     554   [ #  #  #  #  :          0 :         M_insist(required == (required & op.schema()), "required must be subset of operator schema");
             #  #  #  # ]
     555   [ #  #  #  # ]:          0 :         op.schema() = required; // set schema to what is required above
     556         [ #  # ]:          0 :         required |= required_by_op; // add what we require to compute the order
     557                 :          0 :     }
     558                 :          0 :     (*this)(*op.child(0));
     559                 :          0 :     add_constraints(op.schema(), op.child(0)->schema()); // add constraints from child
     560                 :          0 : }
     561                 :            : 
     562                 :          0 : void Operator::minimize_schema()
     563                 :            : {
     564                 :          0 :     SchemaMinimizer M;
     565         [ #  # ]:          0 :     M(*this);
     566                 :          0 : }
     567                 :            : 
     568                 :            : __attribute__((constructor(202)))
     569                 :          1 : void register_post_optimization()
     570                 :            : {
     571                 :          1 :     Catalog &C = Catalog::Get();
     572                 :            : 
     573         [ +  - ]:          1 :     C.register_logical_post_optimization(C.pool("minimize schema"), [](std::unique_ptr<Producer> plan) {
     574                 :          0 :         plan->minimize_schema();
     575                 :          0 :         return plan;
     576                 :            :     }, "minimizes the schema of an operator tree");
     577                 :            : 
     578         [ -  + ]:          1 :     C.register_physical_post_optimization("minimize schema", [](std::unique_ptr<MatchBase> plan) {
     579                 :          0 :         const_cast<Operator*>(&plan->get_matched_root())->minimize_schema();
     580                 :          0 :         return plan;
     581                 :            :     }, "minimizes the schema of an operator tree");
     582                 :          1 : }

Generated by: LCOV version 1.16