my $PackVer="==01"; local *unescape=*Everything::unescape; local *packVars=sub { my $varsref = $_[0]; my $seenref = $_[1]; return unless $varsref; return if $seenref->{0+$varsref}++; if (eval { ($varsref->[0]||1) }) { return join "^","$PackVer:A", map { my $typ; my $v=ref $_ ? packVars( $_, $seenref ) : defined($_) ? $_ : ''; $v=~s/([~^=])/sprintf '~%02x', ord($1)/ge; $v; } @$varsref; } elsif (eval { ($varsref->{''}||1) }) { return join "&", "$PackVer:H", map { my $v = $varsref->{$_}; $v=ref $v ? packVars( $v, $seenref ) : defined($v) ? $v : ''; join '=', map {s/([%&=])/ sprintf '%%%02x', ord($1) /ge; $_} $_, $v; } sort keys %$varsref; } else { die "Can't handle vars of type ".ref($varsref)."\n"; } }; local *unpackVars=sub { my $vars_str = $_[0]; return {} unless $vars_str; # version 00: original format # version 01: keys are escaped, not just values, handles nested refs. my $vars; #print "$vars_str\n"; $vars_str =~ s/^(==\d\d):([AH])(.)//; my ($format_version,$type,$split)=($1,$2,$3); #print "$format_version : $type : $split\n"; unless (defined $format_version) { # No format/format version "00" my %vars = map split(/=/, $_, 2), split /&/, $vars_str; unescape( values %vars ); $vars{$_} eq ' ' and $vars{$_} = '' for keys %vars; return \%vars; } elsif ($format_version le $PackVer) { if ($type eq 'H') { for (split /\Q$split\E/, $vars_str) { my ($k,$v) = map { s/%(\w\w)/ chr(hex($1)) /ge; $_ } split /=/, $_, -1; # special data to unpack? if ($v=~/^==\d\d:[AH]/) { # nested value $v = unpackVars($v); } $vars->{$k} = $v; } } elsif ($type eq 'A') { for (split /\Q$split\E/, $vars_str) { s/~(\w\w)/ chr(hex($1)) /ge; # special data to unpack? if (/^==\d\d:[AH]/) { # nested value $_ = unpackVars($_); } push @{$vars},$_; } } else { return "Unknown type! $type"; } } else { return "Version number too high! ($format_version gt $PackVer)"; } return $vars; }; local *cmpVars=sub { my ($var1str, $var2str) = @_; my $v1new=$var1str =~ /^==\d\d:[AH]/; my $v2new=$var2str =~ /^==\d\d:[AH]/; if ($v1new eq $v2new) { return $var1str cmp $var2str; } else { return ( $v1new ? $var1str : packVars( unpackVars ( $var1str ) ) ) cmp ( $v2new ? $var2str : packVars( unpackVars ( $var2str ) ) ) } }; local *_p_str=sub { foreach my $str (@_) { print "L: ".length($str)." >".substr($str,0,40)."\n"; } }; local *new_getVars=sub { my ($NODE) = @_; getRef $NODE; return if ($NODE == -1); return unpackVars($NODE->{vars}); }; local *new_setVars=sub { my ($NODE, $varsref) = @_; my $str; getRef($NODE); unless (exists $$NODE{vars}) { warn ("setVars:\t'vars' field does not exist for node ".getId($NODE)." perhaps it doesn't join on the settings table?\n"); } $str = packVars($varsref); return unless ($str ne $$NODE{vars}); #we don't need to update... # The new vars are different from what this user node contains, force # an update on the user info. $$NODE{vars} = $str; my $superuser = -1; $DB->updateNode($NODE, $superuser); }; #[id://399747] #[id://396815] my $NODE1=getNodeById(399747); my $NODE2=getNodeById(396815); use Benchmark qw(cmpthese); cmpthese -1, { old => sub { my $h=getVars($NODE1); $h->{_test}++; setVars($NODE1,$h); }, new => sub { my $h=new_getVars($NODE2); $h->{_test}++; new_setVars($NODE2,$h); }, };