diff --git a/regress/expected/name_validation.out b/regress/expected/name_validation.out index a1bc779c3..232582bc2 100644 --- a/regress/expected/name_validation.out +++ b/regress/expected/name_validation.out @@ -369,6 +369,33 @@ SELECT * from cypher('graph123', $$ CREATE (:A)-[:`mylabel2`]->(:C) $$) as (a ag --- (0 rows) +-- user label validation +-- invalid +SELECT * from cypher('graph123', $$ return is_valid_label_name('1label') $$) as (result agtype); + result +-------- + false +(1 row) + +SELECT * from cypher('graph123', $$ return is_valid_label_name('2label') $$) as (result agtype); + result +-------- + false +(1 row) + +-- valid +SELECT * from cypher('graph123', $$ return is_valid_label_name('label1') $$) as (result agtype); + result +-------- + true +(1 row) + +SELECT * from cypher('graph123', $$ return is_valid_label_name('label2') $$) as (result agtype); + result +-------- + true +(1 row) + -- clean up SELECT drop_graph('graph123', true); NOTICE: drop cascades to 18 other objects diff --git a/regress/sql/name_validation.sql b/regress/sql/name_validation.sql index b67d113ae..bfb5d886b 100644 --- a/regress/sql/name_validation.sql +++ b/regress/sql/name_validation.sql @@ -145,6 +145,14 @@ SELECT * from cypher('graph123', $$ CREATE (:A)-[:`my&label2`]->(:C) $$) as (a a SELECT * from cypher('graph123', $$ CREATE (a:`mylabel`) $$) as (a agtype); SELECT * from cypher('graph123', $$ CREATE (:A)-[:`mylabel2`]->(:C) $$) as (a agtype); +-- user label validation +-- invalid +SELECT * from cypher('graph123', $$ return is_valid_label_name('1label') $$) as (result agtype); +SELECT * from cypher('graph123', $$ return is_valid_label_name('2label') $$) as (result agtype); +-- valid +SELECT * from cypher('graph123', $$ return is_valid_label_name('label1') $$) as (result agtype); +SELECT * from cypher('graph123', $$ return is_valid_label_name('label2') $$) as (result agtype); + -- clean up SELECT drop_graph('graph123', true); diff --git a/sql/agtype_string.sql b/sql/agtype_string.sql index 430b189b5..e7485769d 100644 --- a/sql/agtype_string.sql +++ b/sql/agtype_string.sql @@ -51,3 +51,10 @@ CREATE FUNCTION ag_catalog.age_eq_tilde(agtype, agtype) STABLE PARALLEL SAFE AS 'MODULE_PATHNAME'; + +CREATE FUNCTION ag_catalog.age_is_valid_label_name(agtype) + RETURNS boolean + LANGUAGE c + IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index 8542c41c8..888d09827 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -80,7 +80,52 @@ static void range_var_callback_for_remove_relation(const RangeVar *rel, Oid odl_rel_oid, void *arg); +PG_FUNCTION_INFO_V1(age_is_valid_label_name); +Datum age_is_valid_label_name(PG_FUNCTION_ARGS) +{ + agtype *agt_arg = NULL; + agtype_value *agtv_value = NULL; + char *label_name = NULL; + bool is_valid = false; + + if (PG_ARGISNULL(0)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("label name must not be NULL"))); + } + + agt_arg = AG_GET_ARG_AGTYPE_P(0); + + if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("is_valid_label_name() only supports scalar arguments"))); + } + + agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + + if (agtv_value->type != AGTV_STRING) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("is_valid_label_name() only supports string arguments"))); + } + + label_name = pnstrdup(agtv_value->val.string.val, + agtv_value->val.string.len); + + is_valid = is_valid_label(label_name, 0); + pfree(label_name); + + if (is_valid) + { + PG_RETURN_BOOL(true); + } + + PG_RETURN_BOOL(false); +} PG_FUNCTION_INFO_V1(create_vlabel);