| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #$Id$ | 
| 2 |  |  |  |  |  |  | package REST::Neo4p::Constraint; | 
| 3 | 4 |  |  | 4 |  | 29360 | use base 'Exporter'; | 
|  | 4 |  |  |  |  | 20 |  | 
|  | 4 |  |  |  |  | 829 |  | 
| 4 | 4 |  |  | 4 |  | 37 | use REST::Neo4p; | 
|  | 4 |  |  |  |  | 10 |  | 
|  | 4 |  |  |  |  | 142 |  | 
| 5 | 4 |  |  | 4 |  | 40 | use REST::Neo4p::Exceptions; | 
|  | 4 |  |  |  |  | 12 |  | 
|  | 4 |  |  |  |  | 135 |  | 
| 6 | 4 |  |  | 4 |  | 23 | use JSON; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 85 |  | 
| 7 | 4 |  |  | 4 |  | 1224 | use Data::Dumper; | 
|  | 4 |  |  |  |  | 13 |  | 
|  | 4 |  |  |  |  | 399 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 4 |  |  | 4 |  | 39 | use Scalar::Util qw(looks_like_number); | 
|  | 4 |  |  |  |  | 16 |  | 
|  | 4 |  |  |  |  | 341 |  | 
| 10 | 4 |  |  | 4 |  | 39 | use strict; | 
|  | 4 |  |  |  |  | 12 |  | 
|  | 4 |  |  |  |  | 155 |  | 
| 11 | 4 |  |  | 4 |  | 36 | use warnings; | 
|  | 4 |  |  |  |  | 13 |  | 
|  | 4 |  |  |  |  | 1087 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | our @EXPORT = qw(serialize_constraints load_constraints); | 
| 14 |  |  |  |  |  |  | our @VALIDATE = qw(validate_properties validate_relationship validate_relationship_type); | 
| 15 |  |  |  |  |  |  | our @EXPORT_OK = (@VALIDATE); | 
| 16 |  |  |  |  |  |  | our %EXPORT_TAGS = ( | 
| 17 |  |  |  |  |  |  | validate => \@VALIDATE, | 
| 18 |  |  |  |  |  |  | auto => [@EXPORT], | 
| 19 |  |  |  |  |  |  | all => [@EXPORT,@EXPORT_OK] | 
| 20 |  |  |  |  |  |  | ); | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | our $jobj = JSON->new->utf8; | 
| 23 |  |  |  |  |  |  | $jobj->allow_blessed(1); | 
| 24 |  |  |  |  |  |  | $jobj->convert_blessed(1); | 
| 25 |  |  |  |  |  |  | my $regex_to_json = sub { | 
| 26 | 3 |  |  | 3 |  | 6 | my $qr = shift; | 
| 27 | 3 |  |  |  |  | 12 | local $Data::Dumper::Terse=1; | 
| 28 | 3 |  |  |  |  | 22 | $qr = Dumper $qr; | 
| 29 | 3 |  |  |  |  | 309 | chomp $qr; | 
| 30 | 3 |  |  |  |  | 18 | return $qr; | 
| 31 |  |  |  |  |  |  | }; | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | BEGIN { | 
| 34 | 4 |  |  | 4 |  | 1561 | $REST::Neo4p::Constraint::VERSION = '0.4000'; | 
| 35 |  |  |  |  |  |  | } | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | # valid constraint types | 
| 38 |  |  |  |  |  |  | our @CONSTRAINT_TYPES = qw( node_property relationship_property | 
| 39 |  |  |  |  |  |  | relationship_type relationship ); | 
| 40 |  |  |  |  |  |  | our $CONSTRAINT_TABLE = {}; | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | # flag - when set, disallow relationships that are not allowed by current | 
| 44 |  |  |  |  |  |  | # relationship types | 
| 45 |  |  |  |  |  |  | # default strict | 
| 46 |  |  |  |  |  |  | $REST::Neo4p::Constraint::STRICT_RELN_TYPES = 1; | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | # flag - when set, require strict checking of relationship properties when | 
| 49 |  |  |  |  |  |  | # validating relationships -- i.e., a relationship with no properties is | 
| 50 |  |  |  |  |  |  | # disallowed unless there is a specific relationship_property constraint | 
| 51 |  |  |  |  |  |  | # allow this | 
| 52 |  |  |  |  |  |  | # default relaxed | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | $REST::Neo4p::Constraint::STRICT_RELN_PROPS = 0; | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | # flag - when set, use the database to store constraints | 
| 57 |  |  |  |  |  |  | $REST::Neo4p::Constraint::USE_NEO4J = 0; | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | sub new { | 
| 61 | 34 |  |  | 34 | 1 | 2890 | my $class = shift; | 
| 62 | 34 |  |  |  |  | 74 | my ($tag, $constraints) = @_; | 
| 63 | 34 |  |  |  |  | 71 | my $self = bless {}, $class; | 
| 64 | 34 | 100 |  |  |  | 88 | unless (defined $tag) { | 
| 65 | 1 |  |  |  |  | 54 | REST::Neo4p::LocalException->throw("New constraint requires tag as arg 1\n"); | 
| 66 |  |  |  |  |  |  | } | 
| 67 | 33 | 100 |  |  |  | 145 | unless ($tag =~ /^[a-z0-9_.]+$/i) { | 
| 68 | 1 |  |  |  |  | 5 | REST::Neo4p::LocalException->throw("Constraint tag may contain only alphanumerics chars, underscore and period\n"); | 
| 69 |  |  |  |  |  |  | } | 
| 70 | 32 | 50 |  |  |  | 519 | if ( !grep /^$tag$/,keys %$CONSTRAINT_TABLE ) { | 
| 71 | 32 |  |  |  |  | 143 | $self->{_tag} = $tag; | 
| 72 |  |  |  |  |  |  | } | 
| 73 |  |  |  |  |  |  | else { | 
| 74 | 0 |  |  |  |  | 0 | REST::Neo4p::LocalException->throw("Constraint with tag '$tag' is already defined\n"); | 
| 75 |  |  |  |  |  |  | } | 
| 76 | 32 |  |  |  |  | 129 | $self->new_from_constraint_hash($constraints); | 
| 77 | 31 |  |  |  |  | 101 | $CONSTRAINT_TABLE->{$tag} = $self; | 
| 78 |  |  |  |  |  |  | } | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | sub new_from_constraint_hash { | 
| 81 | 0 |  |  | 0 | 0 | 0 | REST::Neo4p::AbstractMethodException->throw("new_from_constraint_hash() is an abstract method of ".__PACKAGE__."\n"); | 
| 82 |  |  |  |  |  |  | } | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | sub TO_JSON { | 
| 85 | 4 |  |  | 4 |  | 39 | no warnings qw(redefine); | 
|  | 4 |  |  |  |  | 12 |  | 
|  | 4 |  |  |  |  | 8214 |  | 
| 86 | 11 |  |  | 11 | 0 | 21 | my $self = shift; | 
| 87 | 11 |  |  |  |  | 14 | my $store; | 
| 88 | 11 |  |  |  |  | 20 | my $old = *Regexp::TO_JSON{CODE}; | 
| 89 | 11 |  |  |  |  | 20 | *Regexp::TO_JSON = $regex_to_json; | 
| 90 | 11 |  |  |  |  | 26 | $store = $self->constraints; | 
| 91 | 11 |  |  |  |  | 30 | $store->{_condition} = $self->condition; | 
| 92 | 11 |  |  |  |  | 21 | $store->{_priority} = $self->priority; | 
| 93 | 11 | 100 |  |  |  | 65 | $store->{_relationship_type} = $self->rtype if $self->can('rtype'); | 
| 94 | 11 |  |  |  |  | 25 | my $ret  = $jobj->encode({tag => $self->tag, type => $self->type, | 
| 95 |  |  |  |  |  |  | _constraint_hash => $store }); | 
| 96 | 11 | 100 |  |  |  | 38 | *Regexp::TO_JSON = $old if $old; | 
| 97 | 11 |  |  |  |  | 69 | return $ret; | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | sub new_from_json { | 
| 101 | 11 |  |  | 11 | 0 | 321 | my $class = shift; | 
| 102 | 11 |  |  |  |  | 23 | my ($json) = @_; | 
| 103 | 11 | 50 |  |  |  | 25 | unless (ref($json)) { | 
| 104 | 11 |  |  |  |  | 112 | $json = decode_json($json); | 
| 105 |  |  |  |  |  |  | } | 
| 106 | 11 | 50 | 33 |  |  | 55 | unless ( $json->{tag} && $json->{type} ) { | 
| 107 | 0 |  |  |  |  | 0 | REST::Neo4p::LocalException->throw("json does not correctly specify a constraint object\n"); | 
| 108 |  |  |  |  |  |  | } | 
| 109 | 11 |  |  |  |  | 21 | my $subclass = $json->{type}; | 
| 110 | 11 |  |  |  |  | 30 | _fix_constraints($json->{_constraint_hash}); | 
| 111 | 11 |  |  |  |  | 72 | $subclass =~ s/^(.)/\U$1\E/; | 
| 112 | 11 |  |  |  |  | 43 | $subclass =~ s/_(.)/\U$1\E/; | 
| 113 | 11 |  |  |  |  | 32 | $subclass = 'REST::Neo4p::Constraint::'.$subclass; | 
| 114 | 11 |  |  |  |  | 52 | $subclass->new($json->{tag}, $json->{_constraint_hash}); | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | sub _fix_constraints { | 
| 118 |  |  |  |  |  |  | # make qr// strings into Regexp objects | 
| 119 | 78 |  |  | 78 |  | 115 | local $_ = shift; | 
| 120 | 78 | 100 |  |  |  | 249 | if (ref eq 'HASH') { | 
|  |  | 100 |  |  |  |  |  | 
| 121 | 17 |  |  |  |  | 61 | while (my ($k, $v) = each %$_) { | 
| 122 | 50 | 100 | 100 |  |  | 180 | if ($v && ($v =~ /^qr\//)) { | 
| 123 | 3 | 50 |  |  |  | 11 | if ($v =~ /\(\?(\^|-[a-z]+):.*\)/) { | 
| 124 | 0 |  |  |  |  | 0 | $v =~ s{/\(\?(\^|-[a-z]+):}{/}; # kludge - eval wants to wrap (?:^...) around a qr string | 
| 125 | 0 |  |  |  |  | 0 | $v =~ s{\)/}{/}; # kludge -      even if one is there already | 
| 126 |  |  |  |  |  |  | } | 
| 127 | 3 |  |  |  |  | 263 | $_->{$k} = eval $v; # replace with Regexp | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  | else { | 
| 130 | 47 |  |  |  |  | 89 | _fix_constraints($v); | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  | elsif (ref eq 'ARRAY') { | 
| 135 | 11 |  |  |  |  | 27 | foreach my $v (@$_) { | 
| 136 | 20 |  |  |  |  | 33 | _fix_constraints($v); | 
| 137 |  |  |  |  |  |  | } | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 88 |  |  | 88 | 1 | 351 | sub tag { shift->{_tag} } | 
| 142 | 281 |  |  | 281 | 1 | 911 | sub type { shift->{_type} } | 
| 143 | 109 |  |  | 109 | 1 | 2111 | sub condition { shift->{_constraints}{_condition} } ## | 
| 144 | 338 |  |  | 338 | 1 | 556 | sub priority { shift->{_constraints}{_priority} } ## | 
| 145 | 483 |  |  | 483 | 1 | 1385 | sub constraints { shift->{_constraints} } | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | sub set_priority { | 
| 148 | 3 |  |  | 3 | 1 | 9 | my $self = shift; | 
| 149 | 3 |  |  |  |  | 7 | my ($priority_value) = @_; | 
| 150 | 3 | 50 |  |  |  | 14 | unless (looks_like_number($priority_value)) { | 
| 151 | 0 |  |  |  |  | 0 | REST::Neo4p::LocalException->throw("Priority value must be numeric\n"); | 
| 152 |  |  |  |  |  |  | } | 
| 153 | 3 |  |  |  |  | 16 | return $self->{_constraints}{_priority} = $priority_value; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | sub get_constraint { | 
| 157 | 4 |  |  | 4 | 1 | 69 | my $class = shift; | 
| 158 | 4 | 100 |  |  |  | 19 | if (ref $class) { | 
| 159 | 1 |  |  |  |  | 32 | REST::Neo4p::ClassOnlyException->throw("get_constraint is a class method only\n"); | 
| 160 |  |  |  |  |  |  | } | 
| 161 | 3 |  |  |  |  | 12 | my ($tag) = @_; | 
| 162 | 3 |  |  |  |  | 17 | return $CONSTRAINT_TABLE->{$tag}; | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | sub get_all_constraints { | 
| 166 | 0 |  |  | 0 | 1 | 0 | my $class = shift; | 
| 167 | 0 | 0 |  |  |  | 0 | if (ref $class) { | 
| 168 | 0 |  |  |  |  | 0 | REST::Neo4p::ClassOnlyException->throw("get_constraint is a class method only\n"); | 
| 169 |  |  |  |  |  |  | } | 
| 170 | 0 |  |  |  |  | 0 | return %{$CONSTRAINT_TABLE}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | sub drop { | 
| 174 | 11 |  |  | 11 | 0 | 2557 | my $self = shift; | 
| 175 | 11 |  |  |  |  | 34 | delete $CONSTRAINT_TABLE->{$self->tag}; | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | sub drop_constraint { | 
| 179 | 1 |  |  | 1 | 0 | 3 | my $class = shift; | 
| 180 | 1 | 50 |  |  |  | 3 | if (ref $class) { | 
| 181 | 0 |  |  |  |  | 0 | REST::Neo4p::ClassOnlyException->throw("get_constraint is a class method only\n"); | 
| 182 |  |  |  |  |  |  | } | 
| 183 | 1 |  |  |  |  | 2 | my ($tag) = @_; | 
| 184 | 1 |  |  |  |  | 17 | delete $CONSTRAINT_TABLE->{$tag}; | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | sub add_constraint { | 
| 188 | 0 |  |  | 0 | 1 | 0 | REST::Neo4p::AbstractMethodException->throw("Cannot call add_constraint() from the Constraint parent class\n"); | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | sub remove_constraint { | 
| 192 | 0 |  |  | 0 | 1 | 0 | REST::Neo4p::AbstractMethodException->throw("Cannot call remove_constraint() from the Constraint parent class\n"); | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | sub set_condition { | 
| 196 | 0 |  |  | 0 | 1 | 0 | REST::Neo4p::AbstractMethodException->throw("Cannot call set_condition() from the Constraint parent class\n"); | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | # return the first property constraint according to priority | 
| 200 |  |  |  |  |  |  | # that the property hash arg satisfies, or false if no match | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | sub validate_properties { | 
| 203 |  |  |  |  |  |  | #  my $class = shift; | 
| 204 |  |  |  |  |  |  | # Exported | 
| 205 | 20 |  |  | 20 | 1 | 31 | my ($properties) = @_; | 
| 206 | 20 | 50 |  |  |  | 39 | return unless defined $properties; | 
| 207 |  |  |  |  |  |  | # if (ref $class) { | 
| 208 |  |  |  |  |  |  | #   REST::Neo4p::ClassOnlyException->throw("validate_properties() is a class-only method\n"); | 
| 209 |  |  |  |  |  |  | # } | 
| 210 |  |  |  |  |  |  |  | 
| 211 | 20 | 50 | 33 |  |  | 88 | unless ( (ref($properties) =~ /Neo4p::(Node|Relationship)$/) || | 
| 212 |  |  |  |  |  |  | (ref($properties) eq 'HASH') ) { | 
| 213 | 0 |  |  |  |  | 0 | REST::Neo4p::LocalException->throw("Arg to validate_properties() must be a hashref, a Node object, or a Relationship object"); | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  | my $type = (ref($properties) =~ /Neo4p/) ? $properties->entity_type : | 
| 216 | 20 | 50 | 50 |  |  | 85 | (delete $properties->{__type} || ''); | 
| 217 | 20 |  |  |  |  | 49 | my @prop_constraints = grep { $_->type =~ /${type}_property$/ } values %$CONSTRAINT_TABLE; | 
|  | 160 |  |  |  |  | 299 |  | 
| 218 | 20 |  |  |  |  | 64 | @prop_constraints = sort {$b->priority <=> $a->priority} @prop_constraints; | 
|  | 160 |  |  |  |  | 271 |  | 
| 219 | 20 |  |  |  |  | 32 | my $ret; | 
| 220 | 20 |  |  |  |  | 36 | foreach (@prop_constraints) { | 
| 221 | 50 | 100 |  |  |  | 108 | if ($_->validate($properties)) { | 
| 222 | 19 |  |  |  |  | 31 | $ret = $_; | 
| 223 | 19 |  |  |  |  | 27 | last; | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  | } | 
| 226 | 20 |  |  |  |  | 57 | return $ret; | 
| 227 |  |  |  |  |  |  | } | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | sub validate_relationship { | 
| 230 |  |  |  |  |  |  | #  my $class = shift; | 
| 231 |  |  |  |  |  |  | # Exported | 
| 232 | 2 |  |  | 2 | 1 | 12 | my ($from, $to, $reln_type, $reln_props) = @_; | 
| 233 | 2 |  |  |  |  | 7 | my ($reln) = @_; | 
| 234 |  |  |  |  |  |  | # if (ref $class) { | 
| 235 |  |  |  |  |  |  | #   REST::Neo4p::ClassOnlyException->throw("validate_relationship() is a class-only method\n"); | 
| 236 |  |  |  |  |  |  | # } | 
| 237 | 2 | 50 |  |  |  | 7 | return unless defined $from; | 
| 238 | 2 | 50 | 33 |  |  | 34 | unless ( (ref($reln) =~ /Neo4p::Relationship$/) || | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 239 |  |  |  |  |  |  | ( (ref($from) =~ /Neo4p::Node|HASH$/) && (ref($to) =~ /Neo4p::Node|HASH$/) && | 
| 240 |  |  |  |  |  |  | defined $reln_type ) ) { | 
| 241 | 0 |  |  |  |  | 0 | REST::Neo4p::LocalException->throw("validate_relationship() requires a Relationship object, or two property hashrefs or nodes followed by a relationship type\n"); | 
| 242 |  |  |  |  |  |  | } | 
| 243 | 2 |  |  |  |  | 10 | my @reln_constraints = grep {$_->type eq 'relationship'} values %$CONSTRAINT_TABLE; | 
|  | 16 |  |  |  |  | 28 |  | 
| 244 | 2 |  |  |  |  | 9 | @reln_constraints = sort {$a->priority <=> $b->priority} @reln_constraints; | 
|  | 2 |  |  |  |  | 9 |  | 
| 245 | 2 |  |  |  |  | 4 | my $ret; | 
| 246 | 2 |  |  |  |  | 5 | foreach (@reln_constraints) { | 
| 247 | 3 | 100 |  |  |  | 11 | if ($_->validate($from => $to, $reln_type, $reln_props)) { | 
| 248 | 1 |  |  |  |  | 3 | $ret = $_; | 
| 249 | 1 |  |  |  |  | 2 | last; | 
| 250 |  |  |  |  |  |  | } | 
| 251 |  |  |  |  |  |  | } | 
| 252 | 2 |  |  |  |  | 11 | return $ret; | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | sub validate_relationship_type { | 
| 256 |  |  |  |  |  |  | #  my $class = shift; | 
| 257 |  |  |  |  |  |  | # Exported | 
| 258 | 11 |  |  | 11 | 1 | 23 | my ($reln_type) = @_; | 
| 259 |  |  |  |  |  |  | # if (ref $class) { | 
| 260 |  |  |  |  |  |  | #   REST::Neo4p::ClassOnlyException->throw("validate_relationhip_type() is a class-only method\n"); | 
| 261 |  |  |  |  |  |  | # } | 
| 262 | 11 | 50 |  |  |  | 23 | return unless defined $reln_type; | 
| 263 | 11 |  |  |  |  | 33 | my @type_constraints = grep {$_->type eq 'relationship_type'} values %$CONSTRAINT_TABLE; | 
|  | 88 |  |  |  |  | 158 |  | 
| 264 | 11 |  |  |  |  | 29 | @type_constraints = sort {$a->priority <=> $b->priority} @type_constraints; | 
|  | 0 |  |  |  |  | 0 |  | 
| 265 | 11 |  |  |  |  | 18 | my $ret; | 
| 266 | 11 |  |  |  |  | 21 | foreach (@type_constraints) { | 
| 267 | 11 | 100 |  |  |  | 32 | if ($_->validate($reln_type)) { | 
| 268 | 10 |  |  |  |  | 20 | $ret = $_; | 
| 269 | 10 |  |  |  |  | 19 | last; | 
| 270 |  |  |  |  |  |  | } | 
| 271 |  |  |  |  |  |  | } | 
| 272 | 11 |  |  |  |  | 38 | return $ret; | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | sub serialize_constraints { | 
| 276 | 1 |  |  | 1 | 1 | 4 | my $json = sprintf "%s", join(", ", map { $jobj->encode($_) } values %$CONSTRAINT_TABLE); | 
|  | 6 |  |  |  |  | 30 |  | 
| 277 | 1 |  |  |  |  | 7 | return "[$json]"; | 
| 278 |  |  |  |  |  |  | } | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | sub load_constraints { | 
| 281 | 1 |  |  | 1 | 1 | 561 | my ($json) = @_; | 
| 282 | 1 |  |  |  |  | 3 | eval { | 
| 283 | 1 |  |  |  |  | 24 | $json = decode_json($json); | 
| 284 |  |  |  |  |  |  | }; | 
| 285 | 1 | 50 |  |  |  | 17 | if (my $e = Exception::Class->caught()) { | 
| 286 | 0 |  |  |  |  | 0 | REST::Neo4p::LocalException->throw("JSON error: $e"); | 
| 287 |  |  |  |  |  |  | } | 
| 288 | 1 |  |  |  |  | 12 | for (@$json) { | 
| 289 | 6 |  |  |  |  | 17 | REST::Neo4p::Constraint->new_from_json($_); | 
| 290 |  |  |  |  |  |  | } | 
| 291 | 1 |  |  |  |  | 4 | return 1; | 
| 292 |  |  |  |  |  |  | } | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | =head1 NAME | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | REST::Neo4p::Constraint - Application-level Neo4j Constraints | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | See L, | 
| 301 |  |  |  |  |  |  | L, | 
| 302 |  |  |  |  |  |  | L for examples. | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | Objects of class REST::Neo4p::Constraint are used to capture and | 
| 307 |  |  |  |  |  |  | organize L application level constraints on Neo4j Node | 
| 308 |  |  |  |  |  |  | and Relationship content. | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | The L module provides a more convenient | 
| 311 |  |  |  |  |  |  | factory for REST::Neo4p::Constraint subclasses that specify L | 
| 312 |  |  |  |  |  |  | property|REST::Neo4p::Constraint::Property>, L | 
| 313 |  |  |  |  |  |  | property|REST::Neo4p::Property>, | 
| 314 |  |  |  |  |  |  | L, and | 
| 315 |  |  |  |  |  |  | L | 
| 316 |  |  |  |  |  |  | constraints. | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | =head1 FLAGS | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | =over | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | =item C<$REST::Neo4p::Constraint::STRICT_RELN_TYPES> | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | When true, relationships are disallowed if the relationship type does | 
| 325 |  |  |  |  |  |  | not meet any current relationship type constraint. Default is true. | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | =item C<$REST::Neo4p::Constraint::STRICT_RELN_PROPS> | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | When true, relationships are disallowed if their relationship | 
| 330 |  |  |  |  |  |  | properties do not meet any current relationship property constraint. | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | Default is false. This is so relationships without properties can be | 
| 333 |  |  |  |  |  |  | made freely. When relationship property checking is strict, you can | 
| 334 |  |  |  |  |  |  | allow relationships without properties by setting the following | 
| 335 |  |  |  |  |  |  | constraint: | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | create_constraint( | 
| 338 |  |  |  |  |  |  | tag => 'free_reln_prop', | 
| 339 |  |  |  |  |  |  | type => 'relationship_property', | 
| 340 |  |  |  |  |  |  | rtype => '*', | 
| 341 |  |  |  |  |  |  | condition => 'all', | 
| 342 |  |  |  |  |  |  | constraints => {} | 
| 343 |  |  |  |  |  |  | ); | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | =back | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | =head1 METHODS | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | =head2 Class Methods | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | =over | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | =item new() | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | $reln_pc = REST::Neo4p::Constraint::RelationshipProperty->new($constraints); | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | Constructor.  Construction also registers the constraint for | 
| 358 |  |  |  |  |  |  | validation. See subclass pod for details. | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | =item get_constraint() | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | $c = REST::Neo4p::Constraint->get_constraint('spiffy_node'); | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | Get a registered constraint by constraint tag. Returns false if none found. | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | =item get_all_constraints() | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | %constraints = REST::Neo4p::Constraint->get_all_constraints(); | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | Get a hash of all registered constraint objects, keyed by constraint tag. | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | =back | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | =head2 Instance Methods | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | =over | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | =item tag() | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | =item type() | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | =item condition() | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | =item set_condition() | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | $reln_c->set_condition('only'); | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | Set the group condition for the constraint. See subclass pod for details. | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | =item priority() | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | =item set_priority() | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | $node_pc->set_priority(10); | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | Constraints with larger priority values are checked before those with | 
| 397 |  |  |  |  |  |  | smaller values by the L|/Functional interface for | 
| 398 |  |  |  |  |  |  | validation> functions. | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | =item constraints() | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | Returns the hashref of constraints. Format depends on the subclass. | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | =item add_constraint() | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | $node_pc->add_constraint( 'warning_level' => qr/^[0-9]$/ ); | 
| 407 |  |  |  |  |  |  | $reln_c->add_constraint( { 'species' => 'genus' } ); | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | Add an individual constraint specification to an existing constraint | 
| 410 |  |  |  |  |  |  | object. See subclass pod for details. | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | =item remove_constraint() | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | $node_pc->remove_constraint( 'warning_level' ); | 
| 415 |  |  |  |  |  |  | $reln_c->remove_constraint( { 'genus' => 'species' } ); | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | Remove an individual constraint specification from an existing | 
| 418 |  |  |  |  |  |  | constraint object. See subclass pod for details. | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | =back | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | =head2 Functional interface for validation | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | =over | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | =item validate_properties() | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | validate_properties( $node_object ) | 
| 429 |  |  |  |  |  |  | validate_properties( $relationship_object ); | 
| 430 |  |  |  |  |  |  | validate_properties( { name => 'Steve', instrument => 'banjo' } ); | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | =item validate_relationship() | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | validate_relationship ( $relationship_object ); | 
| 435 |  |  |  |  |  |  | validate_relationship ( $node_object1 => $node_object2, | 
| 436 |  |  |  |  |  |  | $reln_type ); | 
| 437 |  |  |  |  |  |  | validate_relationship ( { name => 'Steve', instrument => 'banjo' } => | 
| 438 |  |  |  |  |  |  | { name => 'Marcia', instrument => 'blunt' }, | 
| 439 |  |  |  |  |  |  | 'avoids' ); | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | =item validate_relationship_type() | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | validate_relationship_type( 'avoids' ) | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | =back | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | Functional interface. Returns the registered constraint object with | 
| 448 |  |  |  |  |  |  | the highest priority that the argument satisfies, or false if none is | 
| 449 |  |  |  |  |  |  | satisfied. | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | These methods can be exported as follows: | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  | use REST::Neo4p::Constraint qw(:validate) | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | They can also be exported from L: | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | use REST::Neo4p::Constrain qw(:validate) | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | =head2 Serializing and loading constraints | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | =over | 
| 462 |  |  |  |  |  |  |  | 
| 463 |  |  |  |  |  |  | =item serialize_constraints() | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | open $f, ">constraints.json"; | 
| 466 |  |  |  |  |  |  | print $f serialize_constraints(); | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | Returns a JSON-formatted representation of all currently registered | 
| 469 |  |  |  |  |  |  | constraints. | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | =item load_constraints() | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  | open $f, "constraints.json"; | 
| 474 |  |  |  |  |  |  | { | 
| 475 |  |  |  |  |  |  | local $/ = undef; | 
| 476 |  |  |  |  |  |  | load_constraints(<$f>); | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | Creates and registers a list of constraints specified by a JSON string | 
| 480 |  |  |  |  |  |  | as produced by L. | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | =back | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | L,L, | 
| 487 |  |  |  |  |  |  | L, L, | 
| 488 |  |  |  |  |  |  | L. L, L, | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | =head1 AUTHOR | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | Mark A. Jensen | 
| 493 |  |  |  |  |  |  | CPAN ID: MAJENSEN | 
| 494 |  |  |  |  |  |  | majensen -at- cpan -dot- org | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | =head1 LICENSE | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | Copyright (c) 2012-2020 Mark A. Jensen. This program is free software; you | 
| 499 |  |  |  |  |  |  | can redistribute it and/or modify it under the same terms as Perl | 
| 500 |  |  |  |  |  |  | itself. | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | =cut | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | 1; |