/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "postgres.h" #include "access/genam.h" #include "access/heapam.h" #include "access/xact.h" #include "catalog/dependency.h" #include "catalog/objectaddress.h" #include "commands/defrem.h" #include "commands/schemacmds.h" #include "commands/tablecmds.h" #include "fmgr.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodes.h" #include "nodes/parsenodes.h" #include "nodes/pg_list.h" #include "nodes/value.h" #include "parser/parser.h" #include "utils/rel.h" #include "utils/relcache.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "commands/label_commands.h" #include "utils/graphid.h" #include "utils/name_validation.h" #include "pg_fix.h" /* * Schema name doesn't have to be graph name but the same name is used so * that users can find the backed schema for a graph only by its name. */ #define gen_graph_namespace_name(graph_name) (graph_name) static Oid create_schema_for_graph(const Name graph_name); static void drop_schema_for_graph(char *graph_name_str, const bool cascade); static void remove_schema(Node *schema_name, DropBehavior behavior); static void rename_graph(const Name graph_name, const Name new_name); PG_FUNCTION_INFO_V1(create_graph); /* function that is evoked for creating a graph */ Datum create_graph(PG_FUNCTION_ARGS) { char *graph; Name graph_name; char *graph_name_str; Oid nsp_id; //if no argument is passed with the function, graph name cannot be null if (PG_ARGISNULL(0)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("graph name can not be NULL"))); } //gets graph name as function argument graph_name = PG_GETARG_NAME(0); graph_name_str = NameStr(*graph_name); //checking if the name of the graph falls under the pre-decided graph naming conventions(regex) if (!is_valid_graph_name(graph_name_str)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("graph name is invalid"))); } //graph name must be unique, a graph with the same name should not exist if (graph_exists(graph_name_str)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" already exists", graph_name_str))); } nsp_id = create_schema_for_graph(graph_name); //inserts the graph info into the relation which has all the other existing graphs info insert_graph(graph_name, nsp_id); //Increment the Command counter before create the generic labels. CommandCounterIncrement(); //Create the default label tables graph = graph_name->data; create_label(graph, AG_DEFAULT_LABEL_VERTEX, LABEL_TYPE_VERTEX, NIL); create_label(graph, AG_DEFAULT_LABEL_EDGE, LABEL_TYPE_EDGE, NIL); ereport(NOTICE, (errmsg("graph \"%s\" has been created", NameStr(*graph_name)))); //according to postgres specification of c-language functions if function returns void this is the syntax PG_RETURN_VOID(); } static Oid create_schema_for_graph(const Name graph_name) { char *graph_name_str = NameStr(*graph_name); CreateSchemaStmt *schema_stmt; CreateSeqStmt *seq_stmt; TypeName *integer; DefElem *data_type; DefElem *maxvalue; DefElem *cycle; Oid nsp_id; /* * This is the same with running the following SQL statement. * * CREATE SCHEMA `graph_name` * CREATE SEQUENCE `LABEL_ID_SEQ_NAME` * AS integer * MAXVALUE `LABEL_ID_MAX` * CYCLE * * The sequence will be used to assign a unique id to a label in the graph. * * schemaname doesn't have to be graph_name but the same name is used so * that users can find the backed schema for a graph only by its name. * * ProcessUtilityContext of this command is PROCESS_UTILITY_SUBCOMMAND * so the event trigger will not be fired. */ schema_stmt = makeNode(CreateSchemaStmt); schema_stmt->schemaname = gen_graph_namespace_name(graph_name_str); schema_stmt->authrole = NULL; seq_stmt = makeNode(CreateSeqStmt); seq_stmt->sequence = makeRangeVar(graph_name_str, LABEL_ID_SEQ_NAME, -1); integer = SystemTypeName("int4"); data_type = makeDefElem("as", (Node *)integer, -1); maxvalue = makeDefElem("maxvalue", (Node *)makeInteger(LABEL_ID_MAX), -1); cycle = makeDefElem("cycle", (Node *)makeInteger(true), -1); seq_stmt->options = list_make3(data_type, maxvalue, cycle); seq_stmt->ownerId = InvalidOid; seq_stmt->for_identity = false; seq_stmt->if_not_exists = false; schema_stmt->schemaElts = list_make1(seq_stmt); schema_stmt->if_not_exists = false; nsp_id = CreateSchemaCommand(schema_stmt, "(generated CREATE SCHEMA command)", -1, -1); // CommandCounterIncrement() is called in CreateSchemaCommand() return nsp_id; } PG_FUNCTION_INFO_V1(drop_graph); Datum drop_graph(PG_FUNCTION_ARGS) { Name graph_name; char *graph_name_str; bool cascade; if (PG_ARGISNULL(0)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("graph name can not be NULL"))); } graph_name = PG_GETARG_NAME(0); cascade = PG_GETARG_BOOL(1); graph_name_str = NameStr(*graph_name); if (!graph_exists(graph_name_str)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist", graph_name_str))); } drop_schema_for_graph(graph_name_str, cascade); delete_graph(graph_name); CommandCounterIncrement(); ereport(NOTICE, (errmsg("graph \"%s\" has been dropped", graph_name_str))); PG_RETURN_VOID(); } static void drop_schema_for_graph(char *graph_name_str, const bool cascade) { DropStmt *drop_stmt; Value *schema_name; List *label_id_seq_name; DropBehavior behavior; /* * ProcessUtilityContext of commands below is PROCESS_UTILITY_SUBCOMMAND * so the event triggers will not be fired. */ // DROP SEQUENCE `graph_name_str`.`LABEL_ID_SEQ_NAME` drop_stmt = makeNode(DropStmt); schema_name = makeString(get_graph_namespace_name(graph_name_str)); label_id_seq_name = list_make2(schema_name, makeString(LABEL_ID_SEQ_NAME)); drop_stmt->objects = list_make1(label_id_seq_name); drop_stmt->removeType = OBJECT_SEQUENCE; drop_stmt->behavior = DROP_RESTRICT; drop_stmt->missing_ok = false; drop_stmt->concurrent = false; RemoveRelations(drop_stmt); // CommandCounterIncrement() is called in RemoveRelations() // DROP SCHEMA `graph_name_str` [ CASCADE ] behavior = cascade ? DROP_CASCADE : DROP_RESTRICT; remove_schema((Node *)schema_name, behavior); // CommandCounterIncrement() is called in performDeletion() } // See RemoveObjects() for more details. static void remove_schema(Node *schema_name, DropBehavior behavior) { ObjectAddress address; Relation relation; address = get_object_address(OBJECT_SCHEMA, schema_name, &relation, AccessExclusiveLock, false); // since the target object is always a schema, relation is NULL Assert(!relation); if (!OidIsValid(address.objectId)) { // missing_ok is always false /* * before calling this function, this condition is already checked in * drop_graph() */ ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("ag_graph catalog is corrupted"), errhint("Schema \"%s\" does not exist", strVal(schema_name)))); } // removeType is always OBJECT_SCHEMA /* * Check permissions. Since the target object is always a schema, the * original logic is simplified. */ check_object_ownership(GetUserId(), OBJECT_SCHEMA, address, schema_name, NULL); // the target schema is not temporary // the target object is always a schema /* * set PERFORM_DELETION_INTERNAL flag so that object_access_hook can ignore * this deletion */ performDeletion(&address, behavior, PERFORM_DELETION_INTERNAL); } PG_FUNCTION_INFO_V1(alter_graph); /* * Function alter_graph, invoked by the sql function - * alter_graph(graph_name name, operation cstring, new_value name) * NOTE: Currently only RENAME is supported. * graph_name and new_value are case sensitive. * operation is case insensitive. */ Datum alter_graph(PG_FUNCTION_ARGS) { Name graph_name; Name new_value; char *operation; if (PG_ARGISNULL(0)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("graph_name must not be NULL"))); } if (PG_ARGISNULL(1)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("operation must not be NULL"))); } if (PG_ARGISNULL(2)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("new_value must not be NULL"))); } graph_name = PG_GETARG_NAME(0); operation = PG_GETARG_CSTRING(1); new_value = PG_GETARG_NAME(2); if (pg_strcasecmp("RENAME", operation) == 0) { rename_graph(graph_name, new_value); } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid operation \"%s\"", operation), errhint("valid operations: RENAME"))); } PG_RETURN_VOID(); } /* * Function to rename a graph by renaming the schema (which is also the * namespace) and updating the name in ag_graph */ static void rename_graph(const Name graph_name, const Name new_name) { char *oldname = NameStr(*graph_name); char *newname = NameStr(*new_name); char *schema_name; if (!is_valid_graph_name(newname)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("new graph name is invalid"))); } /* * ProcessUtilityContext of this command is PROCESS_UTILITY_SUBCOMMAND * so the event trigger will not be fired. * * CommandCounterIncrement() does not have to be called after this. * * NOTE: If graph_name and schema_name are decoupled, this operation does * not required. */ schema_name = get_graph_namespace_name(oldname); RenameSchema(schema_name, newname); update_graph_name(graph_name, new_name); CommandCounterIncrement(); ereport(NOTICE, (errmsg("graph \"%s\" renamed to \"%s\"", oldname, newname))); } // returns a list containing the name of every graph in the database List *get_graphnames(void) { TupleTableSlot *slot; Relation ag_graph; SysScanDesc scan_desc; HeapTuple tuple; List *graphnames = NIL; char *str; ag_graph = table_open(ag_graph_relation_id(), RowExclusiveLock); scan_desc = systable_beginscan(ag_graph, ag_graph_name_index_id(), true, NULL, 0, NULL); slot = MakeTupleTableSlot(RelationGetDescr(ag_graph), &TTSOpsHeapTuple); for (;;) { tuple = systable_getnext(scan_desc); if (!HeapTupleIsValid(tuple)) break; ExecClearTuple(slot); ExecStoreHeapTuple(tuple, slot, false); slot_getallattrs(slot); str = DatumGetCString(slot->tts_values[Anum_ag_graph_name - 1]); graphnames = lappend(graphnames, str); } ExecDropSingleTupleTableSlot(slot); systable_endscan(scan_desc); table_close(ag_graph, RowExclusiveLock); return graphnames; } // deletes all the graphs in the list. void drop_graphs(List *graphnames) { ListCell *lc; foreach(lc, graphnames) { char *graphname = lfirst(lc); DirectFunctionCall2( drop_graph, CStringGetDatum(graphname), BoolGetDatum(true)); } }