diff --git a/expr.cc b/expr.cc index 3638e2c..b5df176 100644 --- a/expr.cc +++ b/expr.cc @@ -48,12 +48,14 @@ case_expr::case_expr(prod *p, sqltype *type_constraint) true_expr = value_expr::factory(this, type_constraint, false); false_expr = value_expr::factory(this, true_expr->type, false); - if(false_expr->type != true_expr->type) { + retry_limit = 20; + while(false_expr->type != true_expr->type) { + retry(); /* Types are consistent but not identical. Try to find a more concrete one for a better match. */ if (true_expr->type->consistent(false_expr->type)) true_expr = value_expr::factory(this, false_expr->type, false); - else + else false_expr = value_expr::factory(this, true_expr->type, false); } type = true_expr->type; @@ -63,7 +65,7 @@ void case_expr::out(std::ostream &out) { out << "case when " << *condition; out << " then " << *true_expr; - out << " else " << *true_expr; + out << " else " << *false_expr; out << " end"; indent(out); } @@ -109,9 +111,11 @@ shared_ptr bool_expr::factory(prod *p) else if (d6() < 4 && g_joins > 0) { g_joins--; return make_shared(p); - } else + } + else if (d6() < 3) + return make_shared(p); + else return make_shared(p); -// return make_shared(q); } catch (runtime_error &e) { } p->retry(); @@ -195,6 +199,55 @@ void coalesce::out(std::ostream &out) out << " as " << type->name << ")"; } +/* Interesting string literals: empty/whitespace, LIKE metacharacters, + quoting/escaping hazards, unicode of various widths, casts-in-disguise + and a long string. All pre-escaped for use in single quotes. */ +static const char *interesting_strings[] = { + "", + "a", + "A A", + " ", + " padded ", + "0", + "-1", + "null", + "%", + "_", + "%_%", + "\\", + "''", + "a''b\\c\"d", + "ß", + "áéíóú", + "日本語", + "🌍🌍", + "\U0001D11E", + "{\"a\": 1}", + "{1,2,3}", + "2023-01-01", + "1 day", + "inf", + "NaN", + "0123456789012345678901234567890123456789", +}; + +static std::string random_string_literal() +{ + if (d6() < 6) { + size_t idx = d100() % (sizeof(interesting_strings)/sizeof(*interesting_strings)); + return std::string("'") + interesting_strings[idx] + "'"; + } + /* random short string over a small alphabet including LIKE + metacharacters and multi-byte characters */ + static const char *alphabet[] = {"a", "b", "Z", "0", " ", "%", "_", "ß", "''"}; + std::string result = "'"; + int len = d6(); + for (int i = 0; i < len; i++) + result += alphabet[d9() - 1]; + result += "'"; + return result; +} + const_expr::const_expr(prod *p, sqltype *type_constraint) : value_expr(p), expr("") { @@ -212,6 +265,12 @@ const_expr::const_expr(prod *p, sqltype *type_constraint) expr = "null"; else if (type->name == "anycompatible") expr = "null"; + /* an untyped literal lets the function/operator overload resolution + pick a concrete type, an explicit cast to the pseudo type would + always error */ + else if (type->name == "anynonarray" || type->name == "anyelement" + || type->name == "anycompatiblenonarray") + expr = (d6() < 4) ? random_string_literal() : "null"; else if (type->name == "anyarray") expr = "array[null, null]"; else if (type->name == "anycompatiblearray") @@ -254,6 +313,14 @@ const_expr::const_expr(prod *p, sqltype *type_constraint) expr = "cast('\\xFFFFFF' as bytea)"; else expr = "cast('\\xDEADBEEF' as bytea)"; + /* cast so the literal is not of type unknown, which polymorphic + functions reject. bpchar stays uncast, a bare bpchar cast means + char(1) and would truncate. */ + else if (type->name == "text" || type->name == "varchar" + || type->name == "name") + expr = random_string_literal() + "::" + type->name; + else if (type->name == "bpchar") + expr = random_string_literal(); else if (type->name == "uuid") if (d6() == 1) expr = "UUID '00000000-0000-0000-0000-000000000000'"; @@ -533,8 +600,16 @@ void atomic_subselect::out(std::ostream &out) void window_function::out(std::ostream &out) { indent(out); - out << *aggregate << " over (partition by "; - + if (aggregate) + out << *aggregate; + else { + out << funcname << "("; + if (arg) + out << *arg; + out << extra_args << ")"; + } + out << " over (partition by "; + for (auto ref = partition_by.begin(); ref != partition_by.end(); ref++) { out << **ref; if (ref+1 != partition_by.end()) @@ -542,24 +617,71 @@ void window_function::out(std::ostream &out) } out << " order by "; - + for (auto ref = order_by.begin(); ref != order_by.end(); ref++) { out << **ref; if (ref+1 != order_by.end()) out << ","; } + out << frame; out << ")"; } +/* Random window frame clause, with leading space. Only combinations + Materialize supports: GROUPS mode and frames mixing an UNBOUNDED + bound with an offset bound are rejected. */ +static std::string random_frame_clause() +{ + if (d6() < 4) + return ""; + static const char *frames[] = { + " range between unbounded preceding and current row", + " rows between unbounded preceding and current row", + " rows between unbounded preceding and unbounded following", + " rows between current row and unbounded following", + " rows between 2 preceding and current row", + " rows between current row and 3 following", + " rows between 2 preceding and 3 following", + " rows between 1 following and 3 following", + " rows between 3 preceding and 1 preceding", + }; + return frames[d9() - 1]; +} + window_function::window_function(prod *p, sqltype *type_constraint) : value_expr(p) { match(); - //bool agg = d6() > 1; - bool agg = true; - aggregate = make_shared(this, type_constraint, true, agg); - type = aggregate->type; + int kind = d12(); + if (kind <= 6) { + /* aggregate window function */ + aggregate = make_shared(this, type_constraint, true, true); + type = aggregate->type; + if (!concrete_result_type(type)) + fail("window aggregate with pseudo result type"); + frame = random_frame_clause(); + } else if (kind <= 9) { + /* ranking window function */ + static const char *ranking[] = {"row_number", "rank", "dense_rank"}; + funcname = ranking[d6() % 3]; + type = scope->schema->types_by_name["int8"]; + if (type_constraint && !type_constraint->consistent(type)) + fail("ranking window function does not match type constraint"); + } else { + /* value window function */ + static const char *value_funcs[] = {"lag", "lead", "first_value", + "last_value"}; + funcname = value_funcs[d6() % 4]; + arg = value_expr::factory(this, type_constraint, false); + type = arg->type; + if (funcname == "lag" || funcname == "lead") { + if (d6() < 3) + extra_args = ", " + std::to_string(d6()); + } else { + frame = random_frame_clause(); + } + } partition_by.push_back(make_shared(this)); while(d6() > 4) partition_by.push_back(make_shared(this)); @@ -571,8 +693,12 @@ window_function::window_function(prod *p, sqltype *type_constraint) bool window_function::allowed(prod *p) { - if (dynamic_cast(p)) - return dynamic_cast(p->pprod) ? true : false; + if (dynamic_cast(p)) { + /* only in the select list of a query_spec, and not in a grouped + query where the select list items become grouping keys */ + query_spec *q = dynamic_cast(p->pprod); + return q && !q->has_group_by; + } if (dynamic_cast(p)) return false; if (dynamic_cast(p)) diff --git a/expr.hh b/expr.hh index 1833e5c..4750bf3 100644 --- a/expr.hh +++ b/expr.hh @@ -194,11 +194,23 @@ struct window_function : value_expr { window_function(prod *p, sqltype *type_constraint); vector > partition_by; vector > order_by; + /// Set for aggregate window functions, null for ranking/value functions. shared_ptr aggregate; + /// Function name for ranking/value window functions (row_number, lag, ...). + string funcname; + /// Argument for value window functions (lag, lead, first_value, last_value). + shared_ptr arg; + /// Extra literal arguments (lag/lead offset, ntile bucket count). + string extra_args; + /// Frame clause including leading space, or empty. + string frame; static bool allowed(prod *pprod); virtual void accept(prod_visitor *v) { v->visit(this); - aggregate->accept(v); + if (aggregate) + aggregate->accept(v); + if (arg) + arg->accept(v); for (auto p : partition_by) p->accept(v); for (auto p : order_by) diff --git a/grammar.cc b/grammar.cc index 8daae85..a94ef39 100644 --- a/grammar.cc +++ b/grammar.cc @@ -15,17 +15,17 @@ using namespace std; shared_ptr table_ref::factory(prod *p) { try { if (p->level < 6 + d6()) { - if (d6() > 3 && p->level < 3 + d6() && g_joins > 0) + if (d6() > 3 && p->level < 3 + d6() && g_joins > 0) { g_joins--; return make_shared(p); - // ERROR: Expected ON, or USING after JOIN, found JOIN - if (d6() > 3 && g_joins > 0) - { + } + if (d6() > 3 && g_joins > 0) { g_joins--; return make_shared(p); } + if (d6() == 6) + return make_shared(p); } - //if (d6() > 3) return make_shared(p); // Syntax: ERROR: Expected joined table, found dot //else @@ -96,6 +96,79 @@ table_subquery::table_subquery(prod *p, bool lateral) table_subquery::~table_subquery() { } +table_function_ref::table_function_ref(prod *p) + : table_ref(p), with_ordinality(d6() == 6) +{ + match(); + auto need = [this](const char *name) { + sqltype *t = scope->schema->types_by_name[name]; + if (!t) + fail("table function argument type not in schema"); + return t; + }; + string alias = scope->stmt_uid("tf"); + + switch (d6()) { + case 1: + case 2: + funcname = "unnest"; + if (d6() < 4) { + args.push_back(value_expr::factory(this, need("_int4"), false)); + derived_table.columns().push_back(column("c0", need("int4"))); + } else { + args.push_back(value_expr::factory(this, need("_text"), false)); + derived_table.columns().push_back(column("c0", need("text"))); + } + break; + case 3: + case 4: + /* small bounds, generate_series over large ranges can go OoM */ + funcname = "generate_series"; + literal_args = std::to_string(d100()) + ", " + std::to_string(d100()); + derived_table.columns().push_back(column("c0", need("int4"))); + break; + case 5: + funcname = "jsonb_each"; + args.push_back(value_expr::factory(this, need("jsonb"), false)); + derived_table.columns().push_back(column("c0", need("text"))); + derived_table.columns().push_back(column("c1", need("jsonb"))); + break; + default: + funcname = "jsonb_array_elements"; + args.push_back(value_expr::factory(this, need("jsonb"), false)); + derived_table.columns().push_back(column("c0", need("jsonb"))); + break; + } + + if (with_ordinality) { + ostringstream name; + name << "c" << derived_table.columns().size(); + derived_table.columns().push_back(column(name.str(), need("int8"))); + } + + refs.push_back(make_shared(alias, &derived_table)); +} + +void table_function_ref::out(std::ostream &out) { + out << funcname << "(" << literal_args; + for (auto arg = args.begin(); arg != args.end(); arg++) { + out << **arg; + if (arg+1 != args.end()) + out << ", "; + } + out << ")"; + if (with_ordinality) + out << " with ordinality"; + out << " as " << refs[0]->ident() << " ("; + auto &cols = derived_table.columns(); + for (size_t i = 0; i < cols.size(); i++) { + out << cols[i].name; + if (i+1 != cols.size()) + out << ", "; + } + out << ")"; +} + void table_subquery::accept(prod_visitor *v) { query->accept(v); v->visit(this); @@ -164,14 +237,15 @@ joined_table::joined_table(prod *p) : table_ref(p) { condition = join_cond::factory(this, *lhs, *rhs); - // Syntax: ERROR: Expected ON, or USING after JOIN, found INNER - //if (d6()<4) { - // type = "inner"; - //} else if (d6()<4) { - // type = "left"; - //} else { - // type = "right"; - //} + if (d6() < 4) { + type = "inner"; + } else if (d6() < 3) { + type = "left"; + } else if (d6() < 3) { + type = "right"; + } else { + type = "full outer"; + } for (auto ref: lhs->refs) refs.push_back(ref); @@ -180,9 +254,22 @@ joined_table::joined_table(prod *p) : table_ref(p) { } void joined_table::out(std::ostream &out) { + /* nested joins need parentheses, the parser does not accept + an unparenthesized join as the operand of another join */ + bool lhs_join = 0 != dynamic_cast(&*lhs); + bool rhs_join = 0 != dynamic_cast(&*rhs); + if (lhs_join) + out << "("; out << *lhs; + if (lhs_join) + out << ")"; indent(out); - out << type << " join " << *rhs; + out << type << " join "; + if (rhs_join) + out << "("; + out << *rhs; + if (rhs_join) + out << ")"; indent(out); out << "on (" << *condition << ")"; } @@ -222,17 +309,39 @@ from_clause::from_clause(prod *p) : prod(p) { } } -select_list::select_list(prod *p) : prod(p) +/* Pseudo result types poison the enclosing query: a select list item of + pseudo type draws "cannot reference pseudo type" errors. */ +bool concrete_result_type(sqltype *t) +{ + const string &n = t->name; + return !(n.rfind("any", 0) == 0 || n == "record" || n == "unknown" + || n == "cstring" || n == "internal" || n == "void"); +} + +select_list::select_list(prod *p, vector *templ) : prod(p) { + size_t i = 0; do { - shared_ptr e = value_expr::factory(this, nullptr, true); + shared_ptr e; + if (templ) { + /* consistent is not enough, set operation operands and WITH + MUTUALLY RECURSIVE bindings need the exact type */ + sqltype *want = (*templ)[i++]; + e = value_expr::factory(this, want, true); + while (e->type != want) { + retry(); + e = value_expr::factory(this, want, true); + } + } else { + e = value_expr::factory(this, nullptr, true); + } value_exprs.push_back(e); ostringstream name; name << "c" << columns++; sqltype *t=e->type; assert(t); derived_table.columns().push_back(column(name.str(), t)); - } while (d6() > 1); + } while (templ ? i < templ->size() : d6() > 1); } void select_list::out(std::ostream &out) @@ -255,6 +364,28 @@ void query_spec::out(std::ostream &out) { indent(out); out << "where "; out << *search; + if (has_group_by) { + indent(out); + out << "group by "; + for (size_t i = 1; i <= group_by_cols; i++) { + out << i; + if (i != group_by_cols) + out << ", "; + } + if (having_agg) { + indent(out); + out << "having (" << *having_agg << ") " << having_op + << " (" << *having_rhs << ")"; + } + } + if (order_clause.size()) { + indent(out); + out << order_clause; + } + if (limit_clause.size()) { + indent(out); + out << limit_clause; + } } struct for_update_verify : prod_visitor { @@ -304,6 +435,9 @@ select_for_update::select_for_update(prod *p, struct scope *s, bool lateral) } lockmode = modes[d6()%(sizeof(modes)/sizeof(*modes))]; set_quantifier = ""; // disallow distinct + // the parser does not accept ORDER BY/LIMIT before FOR + order_clause = ""; + limit_clause = ""; } void select_for_update::out(std::ostream &out) { @@ -314,7 +448,8 @@ void select_for_update::out(std::ostream &out) { } } -query_spec::query_spec(prod *p, struct scope *s, bool lateral) : +query_spec::query_spec(prod *p, struct scope *s, bool lateral, + vector *templ) : prod(p), myscope(s) { scope = &myscope; @@ -323,12 +458,68 @@ query_spec::query_spec(prod *p, struct scope *s, bool lateral) : if (lateral) scope->refs = s->refs; + /* must be decided before the select list is generated, see + window_function::allowed(). Grouping and a column template are + mutually exclusive since grouping appends aggregate columns. */ + has_group_by = !templ && d9() == 1; + from_clause = make_shared(this); - select_list = make_shared(this); - + select_list = make_shared(this, templ); + + if (has_group_by) { + group_by_cols = select_list->value_exprs.size(); + do { + auto agg = make_shared(this, nullptr, false, true); + if (!concrete_result_type(agg->type)) { + retry(); + continue; + } + select_list->value_exprs.push_back(agg); + ostringstream name; + name << "c" << select_list->columns++; + select_list->derived_table.columns().push_back( + column(name.str(), agg->type)); + } while (d6() > 4); + + if (d6() > 4) { + auto agg = make_shared(this, nullptr, false, true); + const string &n = agg->type->name; + /* map/list instantiations that look identical to the type system + can differ at the SQL level, so comparing them draws errors */ + if (concrete_result_type(agg->type) && n.rfind("map", 0) != 0 + && n.rfind("list", 0) != 0) { + having_agg = agg; + having_rhs = make_shared(this, agg->type); + static const char *ops[] = {"=", "<>", "<", "<=", ">", ">="}; + having_op = ops[d6() - 1]; + } + } + } + set_quantifier = (d100() == 1) ? "distinct" : ""; search = bool_expr::factory(this); + + if (d6() == 6) { + auto &cols = select_list->derived_table.columns(); + order_clause = "order by "; + size_t n = 1 + d6() % cols.size(); + for (size_t i = 0; i < n; i++) { + order_clause += cols[d100() % cols.size()].name; + if (d6() < 3) + order_clause += (d6() < 4) ? " asc" : " desc"; + if (d6() == 6) + order_clause += (d6() < 4) ? " nulls first" : " nulls last"; + if (i+1 != n) + order_clause += ", "; + } + } + + if (d6() == 6) { + limit_clause = "limit " + std::to_string(d100()); + if (d6() > 4) + limit_clause += " offset " + std::to_string(d6()); + } } long prepare_stmt::seq; @@ -485,6 +676,104 @@ upsert_stmt::upsert_stmt(prod *p, struct scope *s, table *v) constraint = random_pick(victim->constraints); } +set_op_query::set_op_query(prod *parent, struct scope *s) + : prod(parent), myscope(s) +{ + scope = &myscope; + + operands.push_back(make_shared(this, s)); + + vector templ; + for (auto &c : operands[0]->select_list->derived_table.columns()) { + /* pseudo types cannot be cast to, and map/list instantiations that + look identical to the type system can differ at the SQL level, + producing "cannot be matched" errors */ + const string &n = c.type->name; + if (n.rfind("any", 0) == 0 || n.rfind("map", 0) == 0 + || n.rfind("list", 0) == 0 || n == "record" || n == "unknown" + || n == "cstring" || n == "internal" || n == "void") + fail("set operation over unsupported column type"); + templ.push_back(c.type); + } + + do { + static const char *ops[] = {"union", "union all", "intersect", + "intersect all", "except", "except all"}; + setops.push_back(ops[d6() - 1]); + operands.push_back(make_shared(this, s, false, &templ)); + } while (d6() == 6); +} + +void set_op_query::out(std::ostream &out) +{ + out << "(" << *operands[0] << ")"; + for (size_t i = 1; i < operands.size(); i++) { + indent(out); + out << setops[i-1] << " (" << *operands[i] << ")"; + } +} + +wmr_query::wmr_query(prod *parent, struct scope *s) + : prod(parent), myscope(s) +{ + scope = &myscope; + scope->tables = s->tables; + recursion_limit = 10 + d100(); + + /* concrete types whose names are valid in a column declaration list */ + static const char *type_pool[] = {"int2", "int4", "int8", "bool", "text", + "numeric", "float8", "timestamp", + "jsonb"}; + + int nbindings = 1 + (d6() > 4 ? 1 : 0); + for (int i = 0; i < nbindings; i++) { + auto rel = make_shared(); + vector types; + int ncols = 1 + d6() / 2; + for (int j = 0; j < ncols; j++) { + const char *name = type_pool[d9() - 1]; + sqltype *t = scope->schema->types_by_name[name]; + if (!t) + fail("binding column type not in schema"); + types.push_back(t); + rel->cols.push_back(column("c" + std::to_string(j), t)); + } + auto arel = make_shared(scope->stmt_uid("m"), &*rel); + bindings.push_back(arel); + binding_rels.push_back(rel); + binding_types.push_back(types); + /* visible to all binding queries, enabling (mutual) recursion */ + scope->tables.push_back(&*arel); + } + + for (int i = 0; i < nbindings; i++) + binding_queries.push_back( + make_shared(this, scope, false, &binding_types[i])); + + query = make_shared(this, scope); +} + +void wmr_query::out(std::ostream &out) +{ + out << "WITH MUTUALLY RECURSIVE (RETURN AT RECURSION LIMIT " + << recursion_limit << ")"; + for (size_t i = 0; i < bindings.size(); i++) { + indent(out); + out << bindings[i]->ident() << " ("; + auto &cols = bindings[i]->columns(); + for (size_t j = 0; j < cols.size(); j++) { + out << cols[j].name << " " << cols[j].type->name; + if (j+1 != cols.size()) + out << ", "; + } + out << ") AS (" << *binding_queries[i] << ")"; + if (i+1 != bindings.size()) + out << ","; + } + indent(out); + out << *query; +} + shared_ptr statement_factory(struct scope *s, long max_joins, struct prod *parent) { try { @@ -504,8 +793,13 @@ shared_ptr statement_factory(struct scope *s, long max_joins, struct prod // return make_shared(parent, s); else if (d42() < 3) return make_shared(parent, s); - else if (d6() > 4) - return make_shared(parent, s); + else if (d42() < 4) + return make_shared(parent, s); + else if (d42() < 5) + return make_shared(parent, s); + // Syntax: ERROR: Expected end of statement, found FOR + //else if (d6() > 4) + // return make_shared(parent, s); else if (d6() > 5) return make_shared(parent, s); return make_shared(parent, s); @@ -520,8 +814,13 @@ shared_ptr explain_factory(struct scope *s, long max_joins) std::shared_ptr p; g_joins = max_joins; s->new_stmt(); - if (d6() > 5) + int roll = d9(); + if (roll == 1) p = make_shared((struct prod *)0, s); + else if (roll == 2) + p = make_shared((struct prod *)0, s); + else if (roll == 3) + p = make_shared((struct prod *)0, s); else p = make_shared((struct prod *)0, s); return make_shared((struct prod *)0, p); diff --git a/grammar.hh b/grammar.hh index 704e2f2..ba38cd2 100644 --- a/grammar.hh +++ b/grammar.hh @@ -61,6 +61,24 @@ struct lateral_subquery : table_subquery { : table_subquery(p, true) { } }; +/// Table function call in a FROM clause, e.g. unnest(...) with ordinality. +struct table_function_ref : table_ref { + virtual void out(std::ostream &out); + table_function_ref(prod *p); + virtual ~table_function_ref() { } + virtual void accept(prod_visitor *v) { + v->visit(this); + for (auto p : args) + p->accept(v); + } + string funcname; + vector > args; + /// Literal arguments emitted before the value_expr args. + string literal_args; + bool with_ordinality; + relation derived_table; +}; + struct join_cond : prod { static shared_ptr factory(prod *p, table_ref &lhs, table_ref &rhs); join_cond(prod *p, table_ref &lhs, table_ref &rhs) @@ -119,7 +137,9 @@ struct select_list : prod { std::vector > value_exprs; relation derived_table; int columns = 0; - select_list(prod *p); + /// Without a template, generates a random number of columns of random + /// types. With a template, generates exactly one column per type. + select_list(prod *p, vector *templ = 0); virtual void out(std::ostream &out); ~select_list() { } virtual void accept(prod_visitor *v) { @@ -134,14 +154,29 @@ struct query_spec : prod { shared_ptr from_clause; shared_ptr select_list; shared_ptr search; + /// When set, the plain select list items become grouping keys + /// (referenced by ordinal) and aggregates are appended to the select + /// list. Checked by window_function::allowed() during generation. + bool has_group_by = false; + size_t group_by_cols = 0; + shared_ptr having_agg; + shared_ptr having_rhs; + const char *having_op = 0; + std::string order_clause; + std::string limit_clause; struct scope myscope; virtual void out(std::ostream &out); - query_spec(prod *p, struct scope *s, bool lateral = 0); + query_spec(prod *p, struct scope *s, bool lateral = 0, + vector *templ = 0); virtual void accept(prod_visitor *v) { v->visit(this); select_list->accept(v); from_clause->accept(v); search->accept(v); + if (having_agg) + having_agg->accept(v); + if (having_rhs) + having_rhs->accept(v); } }; @@ -323,6 +358,45 @@ struct update_returning : update_stmt { } }; +/// Set operation over two or more type-compatible SELECTs, e.g. +/// (q1) union all (q2) except (q3). +struct set_op_query : prod { + struct scope myscope; + vector > operands; + vector setops; + set_op_query(prod *p, struct scope *s); + virtual void out(std::ostream &out); + virtual void accept(prod_visitor *v) { + v->visit(this); + for (auto q : operands) + q->accept(v); + } +}; + +/// WITH MUTUALLY RECURSIVE query. Always emits RETURN AT RECURSION +/// LIMIT so non-converging bindings terminate instead of timing out. +struct wmr_query : prod { + struct scope myscope; + long recursion_limit; + vector > bindings; + vector > binding_rels; + vector > binding_types; + vector > binding_queries; + shared_ptr query; + wmr_query(prod *p, struct scope *s); + virtual void out(std::ostream &out); + virtual void accept(prod_visitor *v) { + v->visit(this); + for (auto q : binding_queries) + q->accept(v); + query->accept(v); + } +}; + +/// Whether a result type is concrete enough to be used as a select list +/// column without drawing "cannot reference pseudo type" errors. +bool concrete_result_type(sqltype *t); + shared_ptr statement_factory(struct scope *s, long max_joins=1, struct prod *parent = 0); struct explain_stmt : prod { diff --git a/log.cc b/log.cc index ac394fd..3db1de4 100644 --- a/log.cc +++ b/log.cc @@ -142,6 +142,13 @@ json_logger::json_logger() void json_logger::report() { + ostringstream s; + impedance::report(s); + try { + data["impedance"] = json::parse(s.str())["impedance"]; + } catch (json::parse_error &e) { + /* don't lose the run report over malformed impedance stats */ + } std::cout << data.dump(4) << std::endl; } diff --git a/postgres.cc b/postgres.cc index ad28d22..153a876 100644 --- a/postgres.cc +++ b/postgres.cc @@ -120,7 +120,7 @@ schema_pqxx::schema_pqxx(std::string &conninfo, bool no_catalog, bool dump_state w.exec("SET TRANSACTION_ISOLATION TO 'SERIALIZABLE'"); pqxx::result r; - string procedure_is_aggregate = "mz_functions.name in ('array_agg', 'avg', 'avg_internal_v1', 'bit_and', 'bit_or', 'bit_xor', 'bool_and', 'bool_or', 'count', 'every', 'json_agg', 'jsonb_agg', 'json_object_agg', 'jsonb_object_agg', 'list_agg', 'max', 'min', 'range_agg', 'range_intersect_agg', 'string_agg', 'sum', 'xmlagg', 'corr', 'covar_pop', 'covar_samp', 'regr_avgx', 'regr_avgy', 'regr_count', 'regr_intercept', 'regr_r2', 'regr_slope', 'regr_sxx', 'regr_sxy', 'stddev', 'stddev_pop', 'stddev_samp', 'variance', 'var_pop', 'var_samp', 'mode', 'percentile_cont', 'percentile_disc', 'rank', 'dense_rank', 'percent_rank', 'grouping', 'mz_all', 'mz_any')"; + string procedure_is_aggregate = "mz_functions.name in ('array_agg', 'avg', 'avg_internal_v1', 'bit_and', 'bit_or', 'bit_xor', 'bool_and', 'bool_or', 'count', 'every', 'json_agg', 'jsonb_agg', 'json_object_agg', 'jsonb_object_agg', 'list_agg', 'map_agg', 'max', 'min', 'range_agg', 'range_intersect_agg', 'string_agg', 'sum', 'xmlagg', 'corr', 'covar_pop', 'covar_samp', 'regr_avgx', 'regr_avgy', 'regr_count', 'regr_intercept', 'regr_r2', 'regr_slope', 'regr_sxx', 'regr_sxy', 'stddev', 'stddev_pop', 'stddev_samp', 'variance', 'var_pop', 'var_samp', 'mode', 'percentile_cont', 'percentile_disc', 'rank', 'dense_rank', 'percent_rank', 'grouping', 'mz_all', 'mz_any')"; string procedure_is_window = "mz_functions.name in ('row_number', 'rank', 'dense_rank', 'percent_rank', 'cume_dist', 'ntile', 'lag', 'lead', 'first_value', 'last_value', 'nth_value')"; cerr << "Loading types..."; diff --git a/schema.cc b/schema.cc index 958ddae..2c2c6ca 100644 --- a/schema.cc +++ b/schema.cc @@ -14,6 +14,7 @@ void schema::generate_indexes() { for (auto &type: types) { assert(type); + types_by_name[type->name] = type; for(auto &r: aggregates) { if (type->consistent(r.restype)) aggregates_returning_type[type].push_back(&r); diff --git a/schema.hh b/schema.hh index 3935545..7f4446a 100644 --- a/schema.hh +++ b/schema.hh @@ -20,7 +20,9 @@ struct schema { sqltype *arraytype; std::vector types; - + /// Lookup of types by name, filled in generate_indexes(). + std::map types_by_name; + std::vector tables; std::vector operators; std::vector routines;