* Copyright (c) Huawei Technologies Co., Ltd. 2024-2026. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* --------------------------------------------------------------------------------------
*
* tablecmds.cpp
* Shark's Commands for creating and altering table structures and settings
*
* Portions Copyright (c) 2026, Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 2020, AWS
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2010-2012 Postgres-XC Development Group
*
*
* IDENTIFICATION
* contrib/shark/src/tablecmds.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_session.h"
#include "access/genam.h"
#include "commands/tablecmds.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_expr.h"
#include "parser/parse_type.h"
#include "parser/parse_collate.h"
#include "catalog/heap.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_attrdef.h"
typedef struct ComputedColumnContextData
{
Relation rel;
ParseState *pstate;
List *gen_column_list;
} ComputedColumnContextData;
typedef ComputedColumnContextData *ComputedColumnContext;
void assign_tablecmds_hook(void);
static void pltsql_PreDropColumnHook(Relation rel, AttrNumber attnum);
static void pltsql_PreAddConstraintsHook(Relation rel, ParseState *pstate, List *newColDefaults);
static void pltsql_TransformSelectForLimitHook(SelectStmt* stmt);
static void pltsql_RecomputeLimitsHook(float8 val);
static bool check_nested_computed_column(Node *node, void *context);
static void* prev_InvokePreDropColumnHook = NULL;
static void* prev_InvokePreAddConstraintsHook = NULL;
static void* prev_InvokeTransformSelectForLimitHook = NULL;
static void* prev_RecomputeLimitsHook = NULL;
void assign_tablecmds_hook(void)
{
if (u_sess->hook_cxt.invokePreDropColumnHook) {
prev_InvokePreDropColumnHook = u_sess->hook_cxt.invokePreDropColumnHook;
}
u_sess->hook_cxt.invokePreDropColumnHook = (void*)&pltsql_PreDropColumnHook;
if (u_sess->hook_cxt.invokePreAddConstraintsHook) {
prev_InvokePreAddConstraintsHook = u_sess->hook_cxt.invokePreAddConstraintsHook;
}
u_sess->hook_cxt.invokePreAddConstraintsHook = (void*)&pltsql_PreAddConstraintsHook;
if (u_sess->hook_cxt.invokeTransformSelectForLimitHook) {
prev_InvokeTransformSelectForLimitHook = u_sess->hook_cxt.invokeTransformSelectForLimitHook;
}
u_sess->hook_cxt.invokeTransformSelectForLimitHook = (void*)&pltsql_TransformSelectForLimitHook;
if (u_sess->hook_cxt.recomputeLimitsHook) {
prev_RecomputeLimitsHook = u_sess->hook_cxt.recomputeLimitsHook;
}
u_sess->hook_cxt.recomputeLimitsHook = (void*)&pltsql_RecomputeLimitsHook;
}
static void pltsql_PreDropColumnHook(Relation rel, AttrNumber attnum)
{
if (!DB_IS_CMPT(D_FORMAT)) {
return;
}
Relation depRel;
ScanKeyData key[3];
SysScanDesc scan;
HeapTuple depTup, tuple;
char *schema_name = NULL,
*minor_name = NULL;
if (prev_InvokePreDropColumnHook) {
((InvokePreDropColumnHookType)(prev_InvokePreDropColumnHook))(rel, attnum);
}
* Find everything that depends on the column. If we can find a
* computed column dependent on this column, will throw an error.
*/
depRel = relation_open(DependRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_depend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationRelationId));
ScanKeyInit(&key[1],
Anum_pg_depend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(rel)));
ScanKeyInit(&key[2],
Anum_pg_depend_refobjsubid,
BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum((int32) attnum));
scan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 3, key);
while (HeapTupleIsValid(depTup = systable_getnext(scan))) {
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
ObjectAddress foundObject;
foundObject.classId = foundDep->classid;
foundObject.objectId = foundDep->objid;
foundObject.objectSubId = foundDep->objsubid;
* Below logic has been taken from backend's ATExecAlterColumnType
* function
*/
if (getObjectClass(&foundObject) == OCLASS_CLASS) {
if (IsComputedColumn(foundObject.objectId, foundObject.objectSubId) &&
(foundObject.objectId != RelationGetRelid(rel) ||
foundObject.objectSubId != attnum)) {
Form_pg_attribute att = TupleDescAttr(rel->rd_att, attnum - 1);
* This must be a reference from the expression of a generated
* column elsewhere in the same table. Dropping the type of a
* column that is used by a generated column is not allowed by
* SQL standard.
*/
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot drop a column used by a generated column"),
errdetail("Column \"%s\" is used by generated column \"%s\".",
NameStr(att->attname),
get_attname(foundObject.objectId, foundObject.objectSubId, false))));
}
}
}
systable_endscan(scan);
relation_close(depRel, AccessShareLock);
}
static void pltsql_PreAddConstraintsHook(Relation rel, ParseState *pstate, List *newColDefaults)
{
if (!DB_IS_CMPT(D_FORMAT)) {
return;
}
ListCell *cell;
Relation attrelation = NULL;
ComputedColumnContext context;
if (prev_InvokePreAddConstraintsHook) {
((InvokePreAddConstraintsHookType)(prev_InvokePreAddConstraintsHook))(rel, pstate, newColDefaults);
}
* TSQL: Support for computed columns
*
* For a computed column, datatype is not provided by the user. Hence,
* we've to evaluate the computed column expression in order to determine
* the datatype. By now, we should've already made an entry for the
* relatio in the catalog, which means we can execute transformExpr on the
* computed column expression. Once we determine the datatype of the
* column, we'll update the corresponding entry in the catalog.
*/
context = (ComputedColumnContext)palloc0(sizeof(ComputedColumnContextData));
context->pstate = pstate;
context->rel = rel;
context->gen_column_list = NIL;
* Collect the names of all computed columns first. We need this in order
* to detect nested computed columns later.
*/
foreach(cell, newColDefaults) {
RawColumnDefault *colDef = (RawColumnDefault *)lfirst(cell);
Form_pg_attribute atp = TupleDescAttr(rel->rd_att, colDef->attnum - 1);
if (colDef->generatedCol != ATTRIBUTE_GENERATED_PERSISTED) {
continue;
}
context->gen_column_list = lappend(context->gen_column_list,
NameStr(atp->attname));
}
foreach(cell, newColDefaults) {
RawColumnDefault *colDef = (RawColumnDefault *) lfirst(cell);
Node *expr;
Oid targettype;
int32 targettypmod;
HeapTuple heapTup;
Type targetType;
Form_pg_attribute attTup;
Form_pg_type tform;
if (colDef->generatedCol != ATTRIBUTE_GENERATED_PERSISTED) {
continue;
}
* Since we're using a dummy datatype for a computed column, we need
* to check for a nested computed column usage in the expression
* before evaluating the expression through transformExpr. N.B. When
* we add a new column through ALTER command, it's possible that the
* expression includes another computed column in the table. We'll
* not be able to detetct that case here. That'll be handled later in
* check_nested_generated that works on the executable expression.
*/
Assert(context->gen_column_list != NULL);
check_nested_computed_column(colDef->raw_default, context);
* transform raw parsetree to executable expression.
*/
expr = transformExpr(pstate, colDef->raw_default, EXPR_KIND_GENERATED_COLUMN);
targettype = exprType(expr);
targettypmod = exprTypmod(expr);
if (!RelationIsValid(attrelation)) {
attrelation = relation_open(AttributeRelationId, RowExclusiveLock);
}
heapTup = SearchSysCacheCopyAttNum(RelationGetRelid(rel), colDef->attnum);
if (!HeapTupleIsValid(heapTup)) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column number %d of relation \"%s\" does not exist",
colDef->attnum, RelationGetRelationName(rel))));
}
attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
targetType = typeidType(targettype);
tform = (Form_pg_type) GETSTRUCT(targetType);
attTup->atttypid = targettype;
attTup->atttypmod = targettypmod;
* The target column should already be having a collation associated
* with it due to explicit COLLATE clause If suppose collation is not
* valid or there is no explicit COLLATE clause, we try to find column
* collation from finished expession.
*/
if (!OidIsValid(attTup->attcollation)) {
Oid targetcollid;
assign_expr_collations(pstate, expr);
targetcollid = exprCollation(expr);
if (OidIsValid(targetcollid)) {
attTup->attcollation = targetcollid;
} else {
attTup->attcollation = tform->typcollation;
}
}
attTup->attndims = tform->typndims;
attTup->attlen = tform->typlen;
attTup->attbyval = tform->typbyval;
attTup->attalign = tform->typalign;
attTup->attstorage = tform->typstorage;
CatalogTupleUpdate(attrelation, &heapTup->t_self, heapTup);
ReleaseSysCache((HeapTuple) targetType);
heap_freetuple(heapTup);
}
if (RelationIsValid(attrelation)) {
relation_close(attrelation, RowExclusiveLock);
CommandCounterIncrement();
}
list_free(context->gen_column_list);
pfree(context);
}
static void pltsql_TransformSelectForLimitHook(SelectStmt* stmt)
{
if (DB_IS_CMPT(D_FORMAT) && stmt->limitWithTies && stmt->sortClause == NIL) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmodule(MOD_EXECUTOR),
errmsg("The WITH TIES clause is not allowed without a corresponding ORDER BY clause.")));
}
}
static void pltsql_RecomputeLimitsHook(float8 val)
{
if (DB_IS_CMPT(D_FORMAT) && (val < 0 || val > 100)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
errmodule(MOD_EXECUTOR),
errmsg("Percent values must be between 0 and 100.")));
}
}
static bool check_nested_computed_column(Node *node, void *context)
{
if (node == NULL) {
return false;
} else if (IsA(node, ColumnRef)) {
ColumnRef *cref = (ColumnRef *)node;
ParseState *pstate = ((ComputedColumnContext) context)->pstate;
switch (list_length(cref->fields)) {
case 1: {
Node *field1 = (Node *)linitial(cref->fields);
List *colList;
char *col1name;
ListCell *lc;
Relation rel;
colList = ((ComputedColumnContext)context)->gen_column_list;
rel = ((ComputedColumnContext)context)->rel;
Assert(IsA(field1, String));
col1name = strVal(field1);
foreach(lc, colList) {
char *col2name = (char *)lfirst(lc);
if (strcmp(col1name, col2name) == 0) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("computed column \"%s\" in table \"%s\" is not allowed to "
"be used in another computed-column definition",
col2name, RelationGetRelationName(rel)),
parser_errposition(pstate, cref->location)));
}
}
} break;
default:
* In CREATE/ALTER TABLE command, the name of the column
* should have only one field.
*/
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper column name in CREATE/ALTER TABLE(too many dotted names): %s",
NameListToString(cref->fields)),
parser_errposition(pstate, cref->location)));
}
}
return raw_expression_tree_walker(node, (bool (*)())check_nested_computed_column, (void*)context);
}