diff options
Diffstat (limited to 'src/backend/parser/analyze.c')
-rw-r--r-- | src/backend/parser/analyze.c | 492 |
1 files changed, 132 insertions, 360 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 63a5999a401..a4e4418b145 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1,12 +1,26 @@ /*------------------------------------------------------------------------- * * analyze.c - * transform the parse tree into a query tree + * transform the raw parse tree into a query tree + * + * For optimizable statements, we are careful to obtain a suitable lock on + * each referenced table, and other modules of the backend preserve or + * re-obtain these locks before depending on the results. It is therefore + * okay to do significant semantic analysis of these statements. For + * utility commands, no locks are obtained here (and if they were, we could + * not be sure we'd still have them at execution). Hence the general rule + * for utility commands is to just dump them into a Query node untransformed. + * parse_analyze does do some purely syntactic transformations on CREATE TABLE + * and ALTER TABLE, but that's about it. In cases where this module contains + * mechanisms that are useful for utility statements, we provide separate + * subroutines that should be called at the beginning of utility execution; + * an example is analyzeIndexStmt. + * * * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.361 2007/02/20 17:32:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.362 2007/03/13 00:33:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -93,26 +107,17 @@ typedef struct static List *do_parse_analyze(Node *parseTree, ParseState *pstate); static Query *transformStmt(ParseState *pstate, Node *stmt, List **extras_before, List **extras_after); -static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt, - List **extras_before, List **extras_after); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt, List **extras_before, List **extras_after); static List *transformInsertRow(ParseState *pstate, List *exprlist, List *stmtcols, List *icolumns, List *attrnos); static List *transformReturningList(ParseState *pstate, List *returningList); -static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt); -static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt, - List **extras_before, List **extras_after); static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt); static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt); static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); -static Query *transformDeclareCursorStmt(ParseState *pstate, - DeclareCursorStmt *stmt); -static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt); -static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt); static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt, List **extras_before, List **extras_after); static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, @@ -155,7 +160,7 @@ static bool check_parameter_resolution_walker(Node *node, * * The result is a List of Query nodes (we need a list since some commands * produce multiple Queries). Optimizable statements require considerable - * transformation, while many utility-type statements are simply hung off + * transformation, while most utility-type statements are simply hung off * a dummy CMD_UTILITY Query node. */ List * @@ -315,59 +320,12 @@ transformStmt(ParseState *pstate, Node *parseTree, extras_before, extras_after); break; - case T_IndexStmt: - result = transformIndexStmt(pstate, (IndexStmt *) parseTree); - break; - - case T_RuleStmt: - result = transformRuleStmt(pstate, (RuleStmt *) parseTree, - extras_before, extras_after); - break; - - case T_ViewStmt: - result = transformViewStmt(pstate, (ViewStmt *) parseTree, - extras_before, extras_after); - break; - - case T_ExplainStmt: - { - ExplainStmt *n = (ExplainStmt *) parseTree; - - result = makeNode(Query); - result->commandType = CMD_UTILITY; - n->query = transformStmt(pstate, (Node *) n->query, - extras_before, extras_after); - result->utilityStmt = (Node *) parseTree; - } - break; - - case T_CopyStmt: - { - CopyStmt *n = (CopyStmt *) parseTree; - - result = makeNode(Query); - result->commandType = CMD_UTILITY; - if (n->query) - n->query = transformStmt(pstate, (Node *) n->query, - extras_before, extras_after); - result->utilityStmt = (Node *) parseTree; - } - break; - case T_AlterTableStmt: result = transformAlterTableStmt(pstate, (AlterTableStmt *) parseTree, extras_before, extras_after); break; - case T_PrepareStmt: - result = transformPrepareStmt(pstate, (PrepareStmt *) parseTree); - break; - - case T_ExecuteStmt: - result = transformExecuteStmt(pstate, (ExecuteStmt *) parseTree); - break; - /* * Optimizable statements */ @@ -397,16 +355,11 @@ transformStmt(ParseState *pstate, Node *parseTree, } break; - case T_DeclareCursorStmt: - result = transformDeclareCursorStmt(pstate, - (DeclareCursorStmt *) parseTree); - break; - default: /* - * other statements don't require any transformation-- just return - * the original parsetree, yea! + * other statements don't require any transformation; just return + * the original parsetree with a Query node plastered on top. */ result = makeNode(Query); result->commandType = CMD_UTILITY; @@ -432,54 +385,6 @@ transformStmt(ParseState *pstate, Node *parseTree, return result; } -static Query * -transformViewStmt(ParseState *pstate, ViewStmt *stmt, - List **extras_before, List **extras_after) -{ - Query *result = makeNode(Query); - - result->commandType = CMD_UTILITY; - result->utilityStmt = (Node *) stmt; - - stmt->query = transformStmt(pstate, (Node *) stmt->query, - extras_before, extras_after); - - /* - * If a list of column names was given, run through and insert these into - * the actual query tree. - thomas 2000-03-08 - * - * Outer loop is over targetlist to make it easier to skip junk targetlist - * entries. - */ - if (stmt->aliases != NIL) - { - ListCell *alist_item = list_head(stmt->aliases); - ListCell *targetList; - - foreach(targetList, stmt->query->targetList) - { - TargetEntry *te = (TargetEntry *) lfirst(targetList); - - Assert(IsA(te, TargetEntry)); - /* junk columns don't get aliases */ - if (te->resjunk) - continue; - te->resname = pstrdup(strVal(lfirst(alist_item))); - alist_item = lnext(alist_item); - if (alist_item == NULL) - break; /* done assigning aliases */ - } - - if (alist_item != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("CREATE VIEW specifies more column " - "names than columns"))); - } - - return result; -} - /* * transformDeleteStmt - * transforms a Delete Statement @@ -1278,8 +1183,13 @@ transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt, /* * transformInhRelation * - * Change the LIKE <subtable> portion of a CREATE TABLE statement into the - * column definitions which recreate the user defined column portions of <subtable>. + * Change the LIKE <subtable> portion of a CREATE TABLE statement into + * column definitions which recreate the user defined column portions of + * <subtable>. + * + * Note: because we do this at parse analysis time, any change in the + * referenced table between parse analysis and execution won't be reflected + * into the new table. Is this OK? */ static void transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, @@ -1644,7 +1554,9 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) * that strikes me as too anal-retentive. - tgl 2001-02-14 * * XXX in ALTER TABLE case, it'd be nice to look for duplicate - * pre-existing indexes, too. + * pre-existing indexes, too. However, that seems to risk race + * conditions since we can't be sure the command will be executed + * immediately. */ Assert(cxt->alist == NIL); if (cxt->pkey != NULL) @@ -1746,37 +1658,55 @@ transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt, } /* - * transformIndexStmt - - * transforms the qualification of the index statement + * analyzeIndexStmt - perform parse analysis for CREATE INDEX + * + * Note that this has to be performed during execution not parse analysis, so + * it's called by ProcessUtility. (Most other callers don't need to bother, + * because this is a no-op for an index not using either index expressions or + * a predicate expression.) */ -static Query * -transformIndexStmt(ParseState *pstate, IndexStmt *stmt) +IndexStmt * +analyzeIndexStmt(IndexStmt *stmt, const char *queryString) { - Query *qry; - RangeTblEntry *rte = NULL; + Relation rel; + ParseState *pstate; + RangeTblEntry *rte; ListCell *l; - qry = makeNode(Query); - qry->commandType = CMD_UTILITY; + /* + * We must not scribble on the passed-in IndexStmt, so copy it. (This + * is overkill, but easy.) + */ + stmt = (IndexStmt *) copyObject(stmt); - /* take care of the where clause */ - if (stmt->whereClause) - { - /* - * Put the parent table into the rtable so that the WHERE clause can - * refer to its fields without qualification. Note that this only - * works if the parent table already exists --- so we can't easily - * support predicates on indexes created implicitly by CREATE TABLE. - * Fortunately, that's not necessary. - */ - rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true); + /* + * Open the parent table with appropriate locking. We must do this + * because addRangeTableEntry() would acquire only AccessShareLock, + * leaving DefineIndex() needing to do a lock upgrade with consequent + * risk of deadlock. Make sure this stays in sync with the type of + * lock DefineIndex() wants. + */ + rel = heap_openrv(stmt->relation, + (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock)); + + /* Set up pstate */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + /* + * Put the parent table into the rtable so that the expressions can + * refer to its fields without qualification. + */ + rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true); - /* no to join list, yes to namespaces */ - addRTEtoQuery(pstate, rte, false, true, true); + /* no to join list, yes to namespaces */ + addRTEtoQuery(pstate, rte, false, true, true); - stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, + /* take care of the where clause */ + if (stmt->whereClause) + stmt->whereClause = transformWhereClause(pstate, + stmt->whereClause, "WHERE"); - } /* take care of any index expressions */ foreach(l, stmt->indexParams) @@ -1785,14 +1715,6 @@ transformIndexStmt(ParseState *pstate, IndexStmt *stmt) if (ielem->expr) { - /* Set up rtable as for predicate, see notes above */ - if (rte == NULL) - { - rte = addRangeTableEntry(pstate, stmt->relation, NULL, - false, true); - /* no to join list, yes to namespaces */ - addRTEtoQuery(pstate, rte, false, true, true); - } ielem->expr = transformExpr(pstate, ielem->expr); /* @@ -1807,32 +1729,44 @@ transformIndexStmt(ParseState *pstate, IndexStmt *stmt) } } - qry->hasSubLinks = pstate->p_hasSubLinks; - stmt->rangetable = pstate->p_rtable; + /* + * Check that only the base rel is mentioned. + */ + if (list_length(pstate->p_rtable) != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("index expressions and predicates can refer only to the table being indexed"))); - qry->utilityStmt = (Node *) stmt; + release_pstate_resources(pstate); + pfree(pstate); - return qry; + /* Close relation, but keep the lock */ + heap_close(rel, NoLock); + + return stmt; } + /* - * transformRuleStmt - - * transform a Create Rule Statement. The actions is a list of parse - * trees which is transformed into a list of query trees. + * analyzeRuleStmt - + * transform a Create Rule Statement. The action is a list of parse + * trees which is transformed into a list of query trees, and we also + * transform the WHERE clause if any. + * + * Note that this has to be performed during execution not parse analysis, + * so it's called by DefineRule. Also note that we must not scribble on + * the passed-in RuleStmt, so we do copyObject() on the actions and WHERE + * clause. */ -static Query * -transformRuleStmt(ParseState *pstate, RuleStmt *stmt, - List **extras_before, List **extras_after) +void +analyzeRuleStmt(RuleStmt *stmt, const char *queryString, + List **actions, Node **whereClause) { - Query *qry; Relation rel; + ParseState *pstate; RangeTblEntry *oldrte; RangeTblEntry *newrte; - qry = makeNode(Query); - qry->commandType = CMD_UTILITY; - qry->utilityStmt = (Node *) stmt; - /* * To avoid deadlock, make sure the first thing we do is grab * AccessExclusiveLock on the target relation. This will be needed by @@ -1841,12 +1775,15 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, */ rel = heap_openrv(stmt->relation, AccessExclusiveLock); + /* Set up pstate */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + /* * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2. * Set up their RTEs in the main pstate for use in parsing the rule * qualification. */ - Assert(pstate->p_rtable == NIL); oldrte = addRangeTableEntryForRelation(pstate, rel, makeAlias("*OLD*", NIL), false, false); @@ -1886,8 +1823,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, } /* take care of the where clause */ - stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, - "WHERE"); + *whereClause = transformWhereClause(pstate, + (Node *) copyObject(stmt->whereClause), + "WHERE"); if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */ ereport(ERROR, @@ -1900,9 +1838,6 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate function in rule WHERE condition"))); - /* save info about sublinks in where clause */ - qry->hasSubLinks = pstate->p_hasSubLinks; - /* * 'instead nothing' rules with a qualification need a query rangetable so * the rewrite handler can add the negated rule qualification to the @@ -1917,7 +1852,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, nothing_qry->rtable = pstate->p_rtable; nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */ - stmt->actions = list_make1(nothing_qry); + *actions = list_make1(nothing_qry); } else { @@ -1930,13 +1865,21 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, foreach(l, stmt->actions) { Node *action = (Node *) lfirst(l); - ParseState *sub_pstate = make_parsestate(pstate->parentParseState); + ParseState *sub_pstate = make_parsestate(NULL); Query *sub_qry, *top_subqry; + List *extras_before = NIL; + List *extras_after = NIL; bool has_old, has_new; /* + * Since outer ParseState isn't parent of inner, have to pass + * down the query text by hand. + */ + sub_pstate->p_sourcetext = queryString; + + /* * Set up OLD/NEW in the rtable for this statement. The entries * are added only to relnamespace, not varnamespace, because we * don't want them to be referred to by unqualified field names @@ -1955,8 +1898,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, addRTEtoQuery(sub_pstate, newrte, false, true, false); /* Transform the rule action statement */ - top_subqry = transformStmt(sub_pstate, action, - extras_before, extras_after); + top_subqry = transformStmt(sub_pstate, + (Node *) copyObject(action), + &extras_before, &extras_after); /* * We cannot support utility-statement actions (eg NOTIFY) with @@ -1964,7 +1908,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, * the utility action execute conditionally. */ if (top_subqry->commandType == CMD_UTILITY && - stmt->whereClause != NULL) + *whereClause != NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE actions"))); @@ -1982,7 +1926,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, * perhaps be relaxed someday, but for now, we may as well reject * such a rule immediately. */ - if (sub_qry->setOperations != NULL && stmt->whereClause != NULL) + if (sub_qry->setOperations != NULL && *whereClause != NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented"))); @@ -1992,10 +1936,10 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, */ has_old = rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) || - rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0); + rangeTableEntry_used(*whereClause, PRS2_OLD_VARNO, 0); has_new = rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) || - rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0); + rangeTableEntry_used(*whereClause, PRS2_NEW_VARNO, 0); switch (stmt->event) { @@ -2063,27 +2007,28 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, sub_qry->jointree->fromlist = sub_pstate->p_joinlist; } + newactions = list_concat(newactions, extras_before); newactions = lappend(newactions, top_subqry); + newactions = list_concat(newactions, extras_after); release_pstate_resources(sub_pstate); pfree(sub_pstate); } - stmt->actions = newactions; + *actions = newactions; } + release_pstate_resources(pstate); + pfree(pstate); + /* Close relation, but keep the exclusive lock */ heap_close(rel, NoLock); - - return qry; } /* * transformSelectStmt - * transforms a Select Statement - * - * Note: this is also used for DECLARE CURSOR statements. */ static Query * transformSelectStmt(ParseState *pstate, SelectStmt *stmt) @@ -2991,6 +2936,11 @@ transformReturningList(ParseState *pstate, List *returningList) /* * transformAlterTableStmt - * transform an Alter Table Statement + * + * CAUTION: resist the temptation to do any work here that depends on the + * current state of the table. Actual execution of the command might not + * occur till some future transaction. Hence, we do only purely syntactic + * transformations here, comparable to the processing of CREATE TABLE. */ static Query * transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, @@ -3162,184 +3112,6 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, return qry; } -static Query * -transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) -{ - Query *result = makeNode(Query); - List *extras_before = NIL, - *extras_after = NIL; - - result->commandType = CMD_UTILITY; - result->utilityStmt = (Node *) stmt; - - /* - * Don't allow both SCROLL and NO SCROLL to be specified - */ - if ((stmt->options & CURSOR_OPT_SCROLL) && - (stmt->options & CURSOR_OPT_NO_SCROLL)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), - errmsg("cannot specify both SCROLL and NO SCROLL"))); - - stmt->query = (Node *) transformStmt(pstate, stmt->query, - &extras_before, &extras_after); - - /* Shouldn't get any extras, since grammar only allows SelectStmt */ - if (extras_before || extras_after) - elog(ERROR, "unexpected extra stuff in cursor statement"); - if (!IsA(stmt->query, Query) || - ((Query *) stmt->query)->commandType != CMD_SELECT) - elog(ERROR, "unexpected non-SELECT command in cursor statement"); - - /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */ - if (((Query *) stmt->query)->into) - ereport(ERROR, - (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), - errmsg("DECLARE CURSOR cannot specify INTO"))); - - return result; -} - - -static Query * -transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt) -{ - Query *result = makeNode(Query); - List *argtype_oids; /* argtype OIDs in a list */ - Oid *argtoids = NULL; /* and as an array */ - int nargs; - List *queries; - int i; - - result->commandType = CMD_UTILITY; - result->utilityStmt = (Node *) stmt; - - /* Transform list of TypeNames to list (and array) of type OIDs */ - nargs = list_length(stmt->argtypes); - - if (nargs) - { - ListCell *l; - - argtoids = (Oid *) palloc(nargs * sizeof(Oid)); - i = 0; - - foreach(l, stmt->argtypes) - { - TypeName *tn = lfirst(l); - Oid toid = typenameTypeId(pstate, tn); - - argtoids[i++] = toid; - } - } - - /* - * Analyze the statement using these parameter types (any parameters - * passed in from above us will not be visible to it), allowing - * information about unknown parameters to be deduced from context. - */ - queries = parse_analyze_varparams((Node *) stmt->query, - pstate->p_sourcetext, - &argtoids, &nargs); - - /* - * Shouldn't get any extra statements, since grammar only allows - * OptimizableStmt - */ - if (list_length(queries) != 1) - elog(ERROR, "unexpected extra stuff in prepared statement"); - - /* - * Check that all parameter types were determined, and convert the array - * of OIDs into a list for storage. - */ - argtype_oids = NIL; - for (i = 0; i < nargs; i++) - { - Oid argtype = argtoids[i]; - - if (argtype == InvalidOid || argtype == UNKNOWNOID) - ereport(ERROR, - (errcode(ERRCODE_INDETERMINATE_DATATYPE), - errmsg("could not determine data type of parameter $%d", - i + 1))); - - argtype_oids = lappend_oid(argtype_oids, argtype); - } - - stmt->argtype_oids = argtype_oids; - stmt->query = linitial(queries); - return result; -} - -static Query * -transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt) -{ - Query *result = makeNode(Query); - List *paramtypes; - - result->commandType = CMD_UTILITY; - result->utilityStmt = (Node *) stmt; - - paramtypes = FetchPreparedStatementParams(stmt->name); - - if (stmt->params || paramtypes) - { - int nparams = list_length(stmt->params); - int nexpected = list_length(paramtypes); - ListCell *l, - *l2; - int i = 1; - - if (nparams != nexpected) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("wrong number of parameters for prepared statement \"%s\"", - stmt->name), - errdetail("Expected %d parameters but got %d.", - nexpected, nparams))); - - forboth(l, stmt->params, l2, paramtypes) - { - Node *expr = lfirst(l); - Oid expected_type_id = lfirst_oid(l2); - Oid given_type_id; - - expr = transformExpr(pstate, expr); - - /* Cannot contain subselects or aggregates */ - if (pstate->p_hasSubLinks) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot use subquery in EXECUTE parameter"))); - if (pstate->p_hasAggs) - ereport(ERROR, - (errcode(ERRCODE_GROUPING_ERROR), - errmsg("cannot use aggregate function in EXECUTE parameter"))); - - given_type_id = exprType(expr); - - expr = coerce_to_target_type(pstate, expr, given_type_id, - expected_type_id, -1, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST); - - if (expr == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("parameter $%d of type %s cannot be coerced to the expected type %s", - i, - format_type_be(given_type_id), - format_type_be(expected_type_id)), - errhint("You will need to rewrite or cast the expression."))); - - lfirst(l) = expr; - i++; - } - } - - return result; -} /* exported so planner can check again after rewriting, query pullup, etc */ void |