NAME
REST::Neo4p::Constrain - Create and apply Neo4j app-level constraints
SYNOPSIS
use REST::Neo4p;
use REST::Neo4p::Constrain qw(:all); # not included by REST::Neo4p
# create some constraints
create_constraint (
tag => 'owner',
type => 'node_property',
condition => 'only',
constraints => {
name => qr/[a-z]+/i,
species => 'human'
}
);
create_constraint(
tag => 'pet',
type => 'node_property',
condition => 'all',
constraints => {
name => qr/[a-z]+/i,
species => qr/^dog|cat|ferret|mole rat|platypus$/
}
);
create_constraint(
tag => 'OWNS_props',
type => 'relationship_property',
rtype => 'OWNS',
condition => 'all',
constraints => {
year_purchased => qr/^20[0-9]{2}$/
}
);
create_constraint(
tag => 'owners_own_pets',
type => 'relationship',
rtype => 'OWNS',
constraints => [{ owner => 'pet' }] # note arrayref
);
create_constraint(
tag => 'loves',
type => 'relationship',
rtype => 'LOVES',
constraints => [{ pet => 'owner' },
{ owner => 'pet' }] # both directions ok
);
create_constraint(
tag => 'ignore'
type => 'relationship',
rtype => 'IGNORES',
constraints => [{ pet => 'owner' },
{ owner => 'pet' }] # both directions ok
);
create_constraint(
tag => 'allowed_rtypes',
type => 'relationship_type',
constraints => [qw( OWNS FEEDS LOVES )]
# IGNORES is missing, see below
);
# constrain by automatic exception-throwing
constrain();
$fred = REST::Neo4p::Node->new(
{ name => 'fred', species => 'human' }
);
$fluffy = REST::Neo4p::Node->new(
{ name => 'fluffy', species => 'mole rat' }
);
$r1 = $fred->relate_to(
$fluffy, 'OWNS', {year_purchased => 2010}
); # valid
eval {
$r2 = $fluffy->relate_to($fred, 'OWNS', {year_purchased => 2010});
};
if (my $e = REST::Neo4p::ConstraintException->caught()) {
print STDERR "Pet can't own an owner, ignored\n";
}
eval {
$r3 = $fluffy->relate_to($fred, 'IGNORES');
};
if (my $e = REST::Neo4p::ConstraintException->caught()) {
print STDERR "Pet can't ignore an owner, ignored\n";
}
# allow relationship types that are not explictly
# allowed -- a relationship constraint is still required
$REST::Neo4p::Constraint::STRICT_RELN_TYPES = 0;
$r3 = $fluffy->relate_to($fred, 'IGNORES'); # no throw now
relax(); # stop automatic constraints
# use validation
$r2 = $fluffy->relate_to(
$fred, 'OWNS',
{year_purchased => 2010}
); # not valid, but auto-constraint not in force
if ( validate_properties($r2) ) {
print STDERR "Relationship properties are valid\n";
}
if ( !validate_relationship($r2) ) {
print STDERR
"Relationship does not meet constraints, ignoring...\n";
}
# try a relationship
if ( validate_relationship( $fred => $fluffy, 'LOVES' ) {
$fred->relate_to($fluffy, 'LOVES');
}
else {
print STDERR
"Prospective relationship fails constraints, ignoring...\n";
}
# try a relationship type
if ( validate_relationship( $fred => $fluffy, 'EATS' ) {
$fred->relate_to($fluffy, 'EATS');
}
else {
print STDERR
"Relationship type disallowed, ignoring...\n";
}
# serialize all constraints
open $f, ">my_constraints.json";
print $f serialize_constraints();
close $f;
# remove current constraints
while ( my ($tag, $constraint) =
each REST::Neo4p::Constraint->get_all_constraints ) {
$constraint->drop;
}
# restore constraints
open $f, "my_constraints.json";
local $/ = undef;
$json = <$f>;
load_constraints($json);
DESCRIPTION
Neo4j, as a NoSQL database, is intentionally lenient. One of its only hardwired constraints is its refusal to remove a Node that is involved in a Relationship. Other constraints to database content (properties and their values, "kinds" of relationships, and relationship types) must be applied at the application level.
REST::Neo4p::Constrain and REST::Neo4p::Constraint attempt to provide a flexible framework for creating and enforcing Neo4j content constraints for applications using REST::Neo4p.
The use case that inspired these modules is the following: You start out with a set of well categorized things, that have some well defined relationships. Each thing will be represented as a node, that's fine. But you want to guarantee (to your client, for example) that
- 1. You can classify every node you add or read unambiguously into a well-defined group;
- 2. You never relate two nodes belonging to particular groups in a way that doesn't make sense according to your well-defined relationships.
These modules allow you to create a set of constraints on node and relationship properties, relationships themselves, and relationship types to meet this use case and others. It is flexible, in that you can choose the level at which the validation is applied:
You can make REST::Neo4p throw exceptions when registered constraints are violated before object creation/database insertion or updating;
You can validate properties and relationships using methods in the code;
You can check the validity of Node or Relationship objects as retrieved from the database
The "SYNOPSIS" is a full example.
Types of Constraints
REST::Neo4p::Constrain handles four types of constraints.
Node property constraints
A node property constraint specifies the presence/absence of properties, and can specify the allowable values a property must (or must not) take.
Relationship property constraints
A relationship property constraint specifies the presence/absence of properties, and can specify the allowable values a property must (or must not) take. In addition, a relationship property constraint can be linked to a given relationship type, so that, e.g., the creation of a relationship of a given type can be forced to have specified relationship properties.
Relationship constraints
A relationship constraint specifies which "kinds" of nodes can participate in a relationship of a given type. A node's "kind" is determined by what node property constraint its properties satisfy.
Relationship type constraints
A relationship type constraint simply enumerates the allowable (or disallowed) relationship types.
Specifying Constraints
REST::Neo4p::Constrain exports create_constraint()
, which creates and registers the different constraint types. (It also returns the REST::Neo4p::Constraint object so created, which can be useful.)
create_constraint
accepts a hash of parameters. The following are required:
create_constraint(
tag => $tag, # a (preferably) simple and meaningful alias for this
# constraint
type => $type, # node_property|relationship_property|
# relationship|relationship_type
priority => $integer_priority, # to determine which constraints
# are evaluated first during validation
constraints => $constraints, # a reference that depends on the
# constraint type, see below
);
Other parameters and the form of the constraint values depend on the constraint type:
Node property
The constraints are specified as a hashref whose keys are the property names and values are the constraints on the property values.
constraints => { # property must be present, may have any value prop_1 => '', # property must be present, and value must eq 'value' prop_2 => 'value', # property must be present, and value must match qr/.alue/ prop_3 => qr/.alue/, # property may be present, and may have any value prop_4 => [], # property may be present, if present # value must match the given condition prop_5 => [<string|regexp>], # (use regexps for enumerations) prop_6 => qr/^value1|value2|value3$/ }
A
condition
parameter can be specified:condition => 'all' # all the specified constraints must be met, and # other properties not in the constraint list may # be added freely condition => 'only' # all the specified constraint must be met, and # no other properties may be added condition => 'none' # reject if any of the specified constraints is # satisfied ('blacklist')
condition
defaults to 'all'.Relationship property
Constraints on properties are specified as for node properties above.
A relationship type can be associated with the relationship property constraint with the parameter
rtype
:rtype => $relationship_type # any relationship type name, or '*' for all types
The
condition
parameter works as for node properties above.Relationship
The basic constraint on a relationship is specified as a hashref that maps a "kind" of from-node to a "kind" of to-node. The "kind" of node is indicated by the tag of the node property constraint it satisfies.
The
constraints
parameter takes an arrayref of these one-row hashrefs.The
rtype
parameter specifies the relationship type to which the constraint applies.Here's an example. Create the following node property constraints:
create_constraint( tag => 'owner', type => 'node_property', constraints => { name => qr/a-z/i, species => 'human' } ); create_constraint( tag => 'pet', type => 'node_property', constraints => { name => qr/a-z/i, species => qr/^dog|cat|ferret|mole rat|platypus$/ } );
Then a relationship constraint that specifies owners can own pets is
create_constraint( tag => 'owners2pets', type => 'relationship', rtype => 'OWNS', constraints => [{ owner => 'pet' }] # note arrayref );
In REST::Neo4p terms, if this constraint (and only this one) is registered,
$fred = REST::Neo4p::Node->new( { name => 'fred', species => 'human' } ); $fluffy = REST::Neo4p::Node->new( { name => 'fluffy', species => 'mole rat' } ); $r1 = $fred->relate_to($fluffy, 'OWNS'); # valid $r2 = $fluffy->relate_to($fred, 'OWNS'); # NOT VALID, throws when # constrain() is in force
Relationship type
The relationship type constraint is just an arrayref of relationship types.
constraints => [@rel_types]
The
condition
parameter can take the following values:condition => 'only' # new relationships must have one of the listed # types (whitelist) condition => 'none' # no new relationship may have any of the listed # types (blacklist)
Using Constraints
create_constraint()
registers the created constraint so that it is included in all relevant validations.
drop_constraint()
deregisters and removes the constraint specified by its tag:
drop_constraint('owner');
drop_constraint('pet');
Automatic validation
Execute constrain()
to force REST::Neo4p to raise a REST::Neo4p::ConstraintException whenever the construction or modification of a Node or Relationship would violate the registered constraints.
Executing relax()
causes REST::Neo4p to ignore all constraint and create and modify entities as usual.
constrain()
and relax()
can be used anywhere at any time. The effects are global.
When constrain()
is in force, any new constraints created are immediately available to the validation.
"Manual" validation
To control validation directly, use the :validate
export tag:
use REST::Neo4p::Constrain qw(:validate);
This provides three functions for checking properties, relationships, and relationship types against registered constraints. They return true if the object or spec satisfies the current constraints and false if it violates the current constraints. No constraint exceptions are raised.
Controlling relationship validation strictness
You can set whether relationship types or relationship properties are strictly validated or not, even when constraints are in force. Relaxing one or both of these can allow you to follow constraints you have defined strictly, while enabling other kinds of relationships to be created ad hoc outside of validation.
See REST::Neo4p::Constraint for details.
FUNCTIONS
Exported by default
- create_constraint()
-
create_constraint( tag => $meaningful_tag, type => $constraint_type, # [node_property|relationship_property| # relationship|relationship_type] condition => $condition # all|only|none, depends on type rtype => $relationship_type # relationship type tag constraints => $spec_ref # hashref or arrayref, depends on type );
Creates and registers a constraint. Returns the created REST::Neo4p::Constraint object.
See "Specifying Constraints" for details.
- drop_constraint()
-
drop_constraint($constraint_tag);
Deregisters a constraint identified by its tag. Returns the constraint object.
- constrain()
- relax()
-
constrain(); eval { $node = REST::Neo4p::Node->create({foo => bar, baz => 1}); }; if ($e = REST::Neo4p::ConstraintException->caught()) { relax(); print "Got ".$e->msg.", but creating anyway\n"; $node = REST::Neo4p::Node->create({foo => bar, baz => 1}); }
constrain() forces REST::Neo4p constructors and property setters to comply with the currently registered constraints. REST::Neo4p::Exceptionss are thrown if constraints are not met.
relax() turns off the automatic validation of constrain().
Effects are global.
Serialization functions
Use these functions to freeze and thaw the currently registered constraints to and from a JSON representation.
Import with
use REST::Neo4p::Constrain qw(:serialize);
- serialize_constraints()
-
open $f, ">constraints.json"; print $f serialize_constraints();
Returns a JSON-formatted representation of all currently registered constraints.
- load_constraints()
-
open $f, "constraints.json"; { local $/ = undef; load_constraints(<$f>); }
Creates and registers a list of constraints specified by a JSON string as produced by "serialize_constraints()".
Validation functions
Functional interface. Returns the registered constraint object with the highest priority that the argument satisfies, or false if no constraint is satisfied.
Import with
use REST::Neo4p::Constrain qw(:validate);
- validate_properties()
-
validate_properties( $node_object ) validate_properties( $relationship_object ); validate_properties( { name => 'Steve', instrument => 'banjo' } );
- validate_relationship()
-
validate_relationship ( $relationship_object ); validate_relationship ( $node_object1 => $node_object2, $reln_type ); validate_relationship ( { name => 'Steve', instrument => 'banjo' } => { name => 'Marcia', instrument => 'blunt' }, 'avoids' );
- validate_relationship_type()
-
validate_relationship_type( 'avoids' )
These methods can also be exported from REST::Neo4p::Constraint.
SEE ALSO
REST::Neo4p, REST::Neo4p::Constraint
AUTHOR
Mark A. Jensen
CPAN ID: MAJENSEN
majensen -at- cpan -dot- org
LICENSE
Copyright (c) 2012-2022 Mark A. Jensen. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.