[dw-free] move cgi-bin/lj*.pl files into proper modules (in cgi-bin/LJ)
[commit: http://hg.dwscoalition.org/dw-free/rev/b06a6502feca]
http://bugs.dwscoalition.org/show_bug.cgi?id=1726
Move ljrelation.pl to LJ/User.pm
Patch by
kareila.
Files modified:
http://bugs.dwscoalition.org/show_bug.cgi?id=1726
Move ljrelation.pl to LJ/User.pm
Patch by
Files modified:
- cgi-bin/LJ/User.pm
- cgi-bin/ljlib.pl
- cgi-bin/ljrelation.pl
--------------------------------------------------------------------------------
diff -r 628592b2c07e -r b06a6502feca cgi-bin/LJ/User.pm
--- a/cgi-bin/LJ/User.pm Fri Nov 11 21:25:34 2011 +0800
+++ b/cgi-bin/LJ/User.pm Wed Nov 16 13:27:24 2011 +0800
@@ -7247,6 +7247,7 @@
### 16. Entry-Related Functions
### 19. OpenID and Identity Functions
### 21. Password Functions
+### 23. Relationship Functions
### 24. Styles and S2-Related Functions
########################################################################
@@ -8588,6 +8589,551 @@
########################################################################
+### 23. Relationship Functions
+
+=head2 Relationship Functions (formerly ljrelation.pl)
+=cut
+
+# <LJFUNC>
+# name: LJ::get_reluser_id
+# des: for [dbtable[reluser2]], numbers 1 - 31999 are reserved for
+# livejournal stuff, whereas numbers 32000-65535 are used for local sites.
+# info: If you wish to add your own hooks to this, you should define a
+# hook "get_reluser_id" in ljlib-local.pl. No reluser2 [special[reluserdefs]]
+# types can be a single character, those are reserved for
+# the [dbtable[reluser]] table, so we don't have namespace problems.
+# args: type
+# des-type: the name of the type you're trying to access, e.g. "hide_comm_assoc"
+# returns: id of type, 0 means it's not a reluser2 type
+# </LJFUNC>
+sub get_reluser_id {
+ my $type = shift;
+ return 0 if length $type == 1; # must be more than a single character
+ my $val =
+ {
+ 'hide_comm_assoc' => 1,
+ }->{$type}+0;
+ return $val if $val;
+ return 0 unless $type =~ /^local-/;
+ return LJ::Hooks::run_hook('get_reluser_id', $type)+0;
+}
+
+# <LJFUNC>
+# name: LJ::load_rel_user
+# des: Load user relationship information. Loads all relationships of type 'type' in
+# which user 'userid' participates on the left side (is the source of the
+# relationship).
+# args: db?, userid, type
+# des-userid: userid or a user hash to load relationship information for.
+# des-type: type of the relationship
+# returns: reference to an array of userids
+# </LJFUNC>
+sub load_rel_user {
+ my $db = LJ::DB::isdb( $_[0] ) ? shift : undef;
+ my ($userid, $type) = @_;
+ return undef unless $type and $userid;
+ my $u = LJ::want_user($userid);
+ $userid = LJ::want_userid($userid);
+ my $typeid = LJ::get_reluser_id($type)+0;
+ if ($typeid) {
+ # clustered reluser2 table
+ $db = LJ::get_cluster_reader($u);
+ return $db->selectcol_arrayref("SELECT targetid FROM reluser2 WHERE userid=? AND type=?",
+ undef, $userid, $typeid);
+ } else {
+ # non-clustered reluser global table
+ $db ||= LJ::get_db_reader();
+ return $db->selectcol_arrayref("SELECT targetid FROM reluser WHERE userid=? AND type=?",
+ undef, $userid, $type);
+ }
+}
+
+# <LJFUNC>
+# name: LJ::load_rel_user_cache
+# des: Loads user relationship information of the type 'type' where user
+# 'targetid' participates on the left side (is the source of the relationship)
+# trying memcache first. The results from this sub should be
+# <strong>treated as inaccurate and out of date</strong>.
+# args: userid, type
+# des-userid: userid or a user hash to load relationship information for.
+# des-type: type of the relationship
+# returns: reference to an array of userids
+# </LJFUNC>
+sub load_rel_user_cache {
+ my ($userid, $type) = @_;
+ return undef unless $type && $userid;
+
+ my $u = LJ::want_user($userid);
+ return undef unless $u;
+ $userid = $u->{'userid'};
+
+ my $key = [ $userid, "reluser:$userid:$type" ];
+ my $res = LJ::MemCache::get($key);
+
+ return $res if $res;
+
+ $res = LJ::load_rel_user($userid, $type);
+
+ my $exp = time() + 60*30; # 30 min
+ LJ::MemCache::set($key, $res, $exp);
+
+ return $res;
+}
+
+# <LJFUNC>
+# name: LJ::load_rel_target
+# des: Load user relationship information. Loads all relationships of type 'type' in
+# which user 'targetid' participates on the right side (is the target of the
+# relationship).
+# args: db?, targetid, type
+# des-targetid: userid or a user hash to load relationship information for.
+# des-type: type of the relationship
+# returns: reference to an array of userids
+# </LJFUNC>
+sub load_rel_target {
+ my $db = LJ::DB::isdb( $_[0] ) ? shift : undef;
+ my ($targetid, $type) = @_;
+ return undef unless $type and $targetid;
+ my $u = LJ::want_user($targetid);
+ $targetid = LJ::want_userid($targetid);
+ my $typeid = LJ::get_reluser_id($type)+0;
+ if ($typeid) {
+ # clustered reluser2 table
+ $db = LJ::get_cluster_reader($u);
+ return $db->selectcol_arrayref("SELECT userid FROM reluser2 WHERE targetid=? AND type=?",
+ undef, $targetid, $typeid);
+ } else {
+ # non-clustered reluser global table
+ $db ||= LJ::get_db_reader();
+ return $db->selectcol_arrayref("SELECT userid FROM reluser WHERE targetid=? AND type=?",
+ undef, $targetid, $type);
+ }
+}
+
+# <LJFUNC>
+# name: LJ::load_rel_target_cache
+# des: Loads user relationship information of the type 'type' where user
+# 'targetid' participates on the right side (is the target of the relationship)
+# trying memcache first. The results from this sub should be
+# <strong>treated as inaccurate and out of date</strong>.
+# args: targetid, type
+# des-userid: userid or a user hash to load relationship information for.
+# des-type: type of the relationship
+# returns: reference to an array of userids
+# </LJFUNC>
+sub load_rel_target_cache {
+ my ($userid, $type) = @_;
+ return undef unless $type && $userid;
+
+ my $u = LJ::want_user($userid);
+ return undef unless $u;
+ $userid = $u->{'userid'};
+
+ my $key = [ $userid, "reluser_rev:$userid:$type" ];
+ my $res = LJ::MemCache::get($key);
+
+ return $res if $res;
+
+ $res = LJ::load_rel_target($userid, $type);
+
+ my $exp = time() + 60*30; # 30 min
+ LJ::MemCache::set($key, $res, $exp);
+
+ return $res;
+}
+
+# <LJFUNC>
+# name: LJ::_get_rel_memcache
+# des: Helper function: returns memcached value for a given (userid, targetid, type) triple, if valid.
+# args: userid, targetid, type
+# des-userid: source userid, nonzero
+# des-targetid: target userid, nonzero
+# des-type: type (reluser) or typeid (rel2) of the relationship
+# returns: undef on failure, 0 or 1 depending on edge existence
+# </LJFUNC>
+sub _get_rel_memcache {
+ return undef unless @LJ::MEMCACHE_SERVERS;
+ return undef unless LJ::is_enabled('memcache_reluser');
+
+ my ($userid, $targetid, $type) = @_;
+ return undef unless $userid && $targetid && defined $type;
+
+ # memcache keys
+ my $relkey = [$userid, "rel:$userid:$targetid:$type"]; # rel $uid->$targetid edge
+ my $modukey = [$userid, "relmodu:$userid:$type" ]; # rel modtime for uid
+ my $modtkey = [$targetid, "relmodt:$targetid:$type" ]; # rel modtime for targetid
+
+ # do a get_multi since $relkey and $modukey are both hashed on $userid
+ my $memc = LJ::MemCache::get_multi($relkey, $modukey);
+ return undef unless $memc && ref $memc eq 'HASH';
+
+ # [{0|1}, modtime]
+ my $rel = $memc->{$relkey->[1]};
+ return undef unless $rel && ref $rel eq 'ARRAY';
+
+ # check rel modtime for $userid
+ my $relmodu = $memc->{$modukey->[1]};
+ return undef if ! $relmodu || $relmodu > $rel->[1];
+
+ # check rel modtime for $targetid
+ my $relmodt = LJ::MemCache::get($modtkey);
+ return undef if ! $relmodt || $relmodt > $rel->[1];
+
+ # return memcache value if it's up-to-date
+ return $rel->[0] ? 1 : 0;
+}
+
+# <LJFUNC>
+# name: LJ::_set_rel_memcache
+# des: Helper function: sets memcache values for a given (userid, targetid, type) triple
+# args: userid, targetid, type
+# des-userid: source userid, nonzero
+# des-targetid: target userid, nonzero
+# des-type: type (reluser) or typeid (rel2) of the relationship
+# returns: 1 on success, undef on failure
+# </LJFUNC>
+sub _set_rel_memcache {
+ return 1 unless @LJ::MEMCACHE_SERVERS;
+
+ my ($userid, $targetid, $type, $val) = @_;
+ return undef unless $userid && $targetid && defined $type;
+ $val = $val ? 1 : 0;
+
+ # memcache keys
+ my $relkey = [$userid, "rel:$userid:$targetid:$type"]; # rel $uid->$targetid edge
+ my $modukey = [$userid, "relmodu:$userid:$type" ]; # rel modtime for uid
+ my $modtkey = [$targetid, "relmodt:$targetid:$type" ]; # rel modtime for targetid
+
+ my $now = time();
+ my $exp = $now + 3600*6; # 6 hour
+ LJ::MemCache::set($relkey, [$val, $now], $exp);
+ LJ::MemCache::set($modukey, $now, $exp);
+ LJ::MemCache::set($modtkey, $now, $exp);
+
+ # Also, delete these keys, since the contents have changed.
+ LJ::MemCache::delete([$userid, "reluser:$userid:$type"]);
+ LJ::MemCache::delete([$targetid, "reluser_rev:$targetid:$type"]);
+
+ return 1;
+}
+
+# <LJFUNC>
+# name: LJ::check_rel
+# des: Checks whether two users are in a specified relationship to each other.
+# args: userid, targetid, type
+# des-userid: source userid, nonzero; may also be a user hash.
+# des-targetid: target userid, nonzero; may also be a user hash.
+# des-type: type of the relationship
+# returns: 1 if the relationship exists, 0 otherwise
+# </LJFUNC>
+sub check_rel {
+ my ($userid, $targetid, $type) = @_;
+ return undef unless $type && $userid && $targetid;
+
+ my $u = LJ::want_user($userid);
+ $userid = LJ::want_userid($userid);
+ $targetid = LJ::want_userid($targetid);
+
+ my $typeid = LJ::get_reluser_id($type)+0;
+ my $eff_type = $typeid || $type;
+
+ my $key = "$userid-$targetid-$eff_type";
+ return $LJ::REQ_CACHE_REL{$key} if defined $LJ::REQ_CACHE_REL{$key};
+
+ # did we get something from memcache?
+ my $memval = LJ::_get_rel_memcache($userid, $targetid, $eff_type);
+ return $memval if defined $memval;
+
+ # are we working on reluser or reluser2?
+ my ( $db, $table );
+ if ($typeid) {
+ # clustered reluser2 table
+ $db = LJ::get_cluster_reader($u);
+ $table = "reluser2";
+ } else {
+ # non-clustered reluser table
+ $db = LJ::get_db_reader();
+ $table = "reluser";
+ }
+
+ # get data from db, force result to be {0|1}
+ my $dbval = $db->selectrow_array("SELECT COUNT(*) FROM $table ".
+ "WHERE userid=? AND targetid=? AND type=? ",
+ undef, $userid, $targetid, $eff_type)
+ ? 1 : 0;
+
+ # set in memcache
+ LJ::_set_rel_memcache($userid, $targetid, $eff_type, $dbval);
+
+ # return and set request cache
+ return $LJ::REQ_CACHE_REL{$key} = $dbval;
+}
+
+# <LJFUNC>
+# name: LJ::set_rel
+# des: Sets relationship information for two users.
+# args: dbs?, userid, targetid, type
+# des-dbs: Deprecated; optional, a master/slave set of database handles.
+# des-userid: source userid, or a user hash
+# des-targetid: target userid, or a user hash
+# des-type: type of the relationship
+# returns: 1 if set succeeded, otherwise undef
+# </LJFUNC>
+sub set_rel {
+ my ($userid, $targetid, $type) = @_;
+ return undef unless $type and $userid and $targetid;
+
+ my $u = LJ::want_user($userid);
+ $userid = LJ::want_userid($userid);
+ $targetid = LJ::want_userid($targetid);
+
+ my $typeid = LJ::get_reluser_id($type)+0;
+ my $eff_type = $typeid || $type;
+
+ # working on reluser or reluser2?
+ my ($db, $table);
+ if ($typeid) {
+ # clustered reluser2 table
+ $db = LJ::get_cluster_master($u);
+ $table = "reluser2";
+ } else {
+ # non-clustered reluser global table
+ $db = LJ::get_db_writer();
+ $table = "reluser";
+ }
+ return undef unless $db;
+
+ # set in database
+ $db->do("REPLACE INTO $table (userid, targetid, type) VALUES (?, ?, ?)",
+ undef, $userid, $targetid, $eff_type);
+ return undef if $db->err;
+
+ # set in memcache
+ LJ::_set_rel_memcache($userid, $targetid, $eff_type, 1);
+
+ return 1;
+}
+
+# <LJFUNC>
+# name: LJ::set_rel_multi
+# des: Sets relationship edges for lists of user tuples.
+# args: edges
+# des-edges: array of arrayrefs of edges to set: [userid, targetid, type].
+# Where:
+# userid: source userid, or a user hash;
+# targetid: target userid, or a user hash;
+# type: type of the relationship.
+# returns: 1 if all sets succeeded, otherwise undef
+# </LJFUNC>
+sub set_rel_multi {
+ return _mod_rel_multi({ mode => 'set', edges => \@_ });
+}
+
+# <LJFUNC>
+# name: LJ::clear_rel_multi
+# des: Clear relationship edges for lists of user tuples.
+# args: edges
+# des-edges: array of arrayrefs of edges to clear: [userid, targetid, type].
+# Where:
+# userid: source userid, or a user hash;
+# targetid: target userid, or a user hash;
+# type: type of the relationship.
+# returns: 1 if all clears succeeded, otherwise undef
+# </LJFUNC>
+sub clear_rel_multi {
+ return _mod_rel_multi({ mode => 'clear', edges => \@_ });
+}
+
+# <LJFUNC>
+# name: LJ::_mod_rel_multi
+# des: Sets/Clears relationship edges for lists of user tuples.
+# args: keys, edges
+# des-keys: keys: mode => {clear|set}.
+# des-edges: edges => array of arrayrefs of edges to set: [userid, targetid, type]
+# Where:
+# userid: source userid, or a user hash;
+# targetid: target userid, or a user hash;
+# type: type of the relationship.
+# returns: 1 if all updates succeeded, otherwise undef
+# </LJFUNC>
+sub _mod_rel_multi {
+ my $opts = shift;
+ return undef unless @{$opts->{edges}};
+
+ my $mode = $opts->{mode} eq 'clear' ? 'clear' : 'set';
+ my $memval = $mode eq 'set' ? 1 : 0;
+
+ my @reluser = (); # [userid, targetid, type]
+ my @reluser2 = ();
+ foreach my $edge (@{$opts->{edges}}) {
+ my ($userid, $targetid, $type) = @$edge;
+ $userid = LJ::want_userid($userid);
+ $targetid = LJ::want_userid($targetid);
+ next unless $type && $userid && $targetid;
+
+ my $typeid = LJ::get_reluser_id($type)+0;
+ my $eff_type = $typeid || $type;
+
+ # working on reluser or reluser2?
+ push @{$typeid ? \@reluser2 : \@reluser}, [$userid, $targetid, $eff_type];
+ }
+
+ # now group reluser2 edges by clusterid
+ my %reluser2 = (); # cid => [userid, targetid, type]
+ my $users = LJ::load_userids(map { $_->[0] } @reluser2);
+ foreach (@reluser2) {
+ my $cid = $users->{$_->[0]}->{clusterid} or next;
+ push @{$reluser2{$cid}}, $_;
+ }
+ @reluser2 = ();
+
+ # try to get all required cluster masters before we start doing database updates
+ my %cache_dbcm = ();
+ foreach my $cid (keys %reluser2) {
+ next unless @{$reluser2{$cid}};
+
+ # return undef immediately if we won't be able to do all the updates
+ $cache_dbcm{$cid} = LJ::get_cluster_master($cid)
+ or return undef;
+ }
+
+ # if any error occurs with a cluster, we'll skip over that cluster and continue
+ # trying to process others since we've likely already done some amount of db
+ # updates already, but we'll return undef to signify that everything did not
+ # go smoothly
+ my $ret = 1;
+
+ # do clustered reluser2 updates
+ foreach my $cid (keys %cache_dbcm) {
+ # array of arrayrefs: [userid, targetid, type]
+ my @edges = @{$reluser2{$cid}};
+
+ # set in database, then in memcache. keep the two atomic per clusterid
+ my $dbcm = $cache_dbcm{$cid};
+
+ my @vals = map { @$_ } @edges;
+
+ if ($mode eq 'set') {
+ my $bind = join(",", map { "(?,?,?)" } @edges);
+ $dbcm->do("REPLACE INTO reluser2 (userid, targetid, type) VALUES $bind",
+ undef, @vals);
+ }
+
+ if ($mode eq 'clear') {
+ my $where = join(" OR ", map { "(userid=? AND targetid=? AND type=?)" } @edges);
+ $dbcm->do("DELETE FROM reluser2 WHERE $where", undef, @vals);
+ }
+
+ # don't update memcache if db update failed for this cluster
+ if ($dbcm->err) {
+ $ret = undef;
+ next;
+ }
+
+ # updates to this cluster succeeded, set memcache
+ LJ::_set_rel_memcache(@$_, $memval) foreach @edges;
+ }
+
+ # do global reluser updates
+ if (@reluser) {
+
+ # nothing to do after this block but return, so we can
+ # immediately return undef from here if there's a problem
+ my $dbh = LJ::get_db_writer()
+ or return undef;
+
+ my @vals = map { @$_ } @reluser;
+
+ if ($mode eq 'set') {
+ my $bind = join(",", map { "(?,?,?)" } @reluser);
+ $dbh->do("REPLACE INTO reluser (userid, targetid, type) VALUES $bind",
+ undef, @vals);
+ }
+
+ if ($mode eq 'clear') {
+ my $where = join(" OR ", map { "userid=? AND targetid=? AND type=?" } @reluser);
+ $dbh->do("DELETE FROM reluser WHERE $where", undef, @vals);
+ }
+
+ # don't update memcache if db update failed for this cluster
+ return undef if $dbh->err;
+
+ # $_ = [userid, targetid, type] for each iteration
+ LJ::_set_rel_memcache(@$_, $memval) foreach @reluser;
+ }
+
+ return $ret;
+}
+
+
+# <LJFUNC>
+# name: LJ::clear_rel
+# des: Deletes a relationship between two users or all relationships of a particular type
+# for one user, on either side of the relationship.
+# info: One of userid,targetid -- bit not both -- may be '*'. In that case,
+# if, say, userid is '*', then all relationship edges with target equal to
+# targetid and of the specified type are deleted.
+# If both userid and targetid are numbers, just one edge is deleted.
+# args: dbs?, userid, targetid, type
+# des-dbs: Deprecated; optional, a master/slave set of database handles.
+# des-userid: source userid, or a user hash, or '*'
+# des-targetid: target userid, or a user hash, or '*'
+# des-type: type of the relationship
+# returns: 1 if clear succeeded, otherwise undef
+# </LJFUNC>
+sub clear_rel {
+ my ($userid, $targetid, $type) = @_;
+ return undef if $userid eq '*' and $targetid eq '*';
+
+ my $u;
+ $u = LJ::want_user($userid) unless $userid eq '*';
+ $userid = LJ::want_userid($userid) unless $userid eq '*';
+ $targetid = LJ::want_userid($targetid) unless $targetid eq '*';
+ return undef unless $type && $userid && $targetid;
+
+ my $typeid = LJ::get_reluser_id($type)+0;
+
+ if ($typeid) {
+ # clustered reluser2 table
+ return undef unless $u->writer;
+
+ $u->do("DELETE FROM reluser2 WHERE " . ($userid ne '*' ? "userid=$userid AND " : "") .
+ ($targetid ne '*' ? "targetid=$targetid AND " : "") . "type=$typeid");
+
+ return undef if $u->err;
+ } else {
+ # non-clustered global reluser table
+ my $dbh = LJ::get_db_writer()
+ or return undef;
+
+ my $qtype = $dbh->quote($type);
+ $dbh->do("DELETE FROM reluser WHERE " . ($userid ne '*' ? "userid=$userid AND " : "") .
+ ($targetid ne '*' ? "targetid=$targetid AND " : "") . "type=$qtype");
+
+ return undef if $dbh->err;
+ }
+
+ # if one of userid or targetid are '*', then we need to note the modtime
+ # of the reluser edge from the specified id (the one that's not '*')
+ # so that subsequent gets on rel:userid:targetid:type will know to ignore
+ # what they got from memcache
+ my $eff_type = $typeid || $type;
+ if ($userid eq '*') {
+ LJ::MemCache::set([$targetid, "relmodt:$targetid:$eff_type"], time());
+ } elsif ($targetid eq '*') {
+ LJ::MemCache::set([$userid, "relmodu:$userid:$eff_type"], time());
+
+ # if neither userid nor targetid are '*', then just call _set_rel_memcache
+ # to update the rel:userid:targetid:type memcache key as well as the
+ # userid and targetid modtime keys
+ } else {
+ LJ::_set_rel_memcache($userid, $targetid, $eff_type, 0);
+ }
+
+ return 1;
+}
+
+########################################################################
### 24. Styles and S2-Related Functions
=head2 Styles and S2-Related Functions (LJ)
diff -r 628592b2c07e -r b06a6502feca cgi-bin/ljlib.pl
--- a/cgi-bin/ljlib.pl Fri Nov 11 21:25:34 2011 +0800
+++ b/cgi-bin/ljlib.pl Wed Nov 16 13:27:24 2011 +0800
@@ -107,7 +107,6 @@
require "ljtimeutil.pl";
use LJ::Capabilities;
use DW::Mood;
-require "ljrelation.pl";
use LJ::Global::Img; # defines LJ::Img
require "$LJ::HOME/cgi-bin/ljlib-local.pl"
diff -r 628592b2c07e -r b06a6502feca cgi-bin/ljrelation.pl
--- a/cgi-bin/ljrelation.pl Fri Nov 11 21:25:34 2011 +0800
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,561 +0,0 @@
-# This code was forked from the LiveJournal project owned and operated
-# by Live Journal, Inc. The code has been modified and expanded by
-# Dreamwidth Studios, LLC. These files were originally licensed under
-# the terms of the license supplied by Live Journal, Inc, which can
-# currently be found at:
-#
-# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
-#
-# In accordance with the original license, this code and all its
-# modifications are provided under the GNU General Public License.
-# A copy of that license can be found in the LICENSE file included as
-# part of this distribution.
-
-package LJ;
-use strict;
-
-# <LJFUNC>
-# name: LJ::get_reluser_id
-# des: for [dbtable[reluser2]], numbers 1 - 31999 are reserved for
-# livejournal stuff, whereas numbers 32000-65535 are used for local sites.
-# info: If you wish to add your own hooks to this, you should define a
-# hook "get_reluser_id" in ljlib-local.pl. No reluser2 [special[reluserdefs]]
-# types can be a single character, those are reserved for
-# the [dbtable[reluser]] table, so we don't have namespace problems.
-# args: type
-# des-type: the name of the type you're trying to access, e.g. "hide_comm_assoc"
-# returns: id of type, 0 means it's not a reluser2 type
-# </LJFUNC>
-sub get_reluser_id {
- my $type = shift;
- return 0 if length $type == 1; # must be more than a single character
- my $val =
- {
- 'hide_comm_assoc' => 1,
- }->{$type}+0;
- return $val if $val;
- return 0 unless $type =~ /^local-/;
- return LJ::Hooks::run_hook('get_reluser_id', $type)+0;
-}
-
-# <LJFUNC>
-# name: LJ::load_rel_user
-# des: Load user relationship information. Loads all relationships of type 'type' in
-# which user 'userid' participates on the left side (is the source of the
-# relationship).
-# args: db?, userid, type
-# des-userid: userid or a user hash to load relationship information for.
-# des-type: type of the relationship
-# returns: reference to an array of userids
-# </LJFUNC>
-sub load_rel_user
-{
- my $db = LJ::DB::isdb( $_[0] ) ? shift : undef;
- my ($userid, $type) = @_;
- return undef unless $type and $userid;
- my $u = LJ::want_user($userid);
- $userid = LJ::want_userid($userid);
- my $typeid = LJ::get_reluser_id($type)+0;
- if ($typeid) {
- # clustered reluser2 table
- $db = LJ::get_cluster_reader($u);
- return $db->selectcol_arrayref("SELECT targetid FROM reluser2 WHERE userid=? AND type=?",
- undef, $userid, $typeid);
- } else {
- # non-clustered reluser global table
- $db ||= LJ::get_db_reader();
- return $db->selectcol_arrayref("SELECT targetid FROM reluser WHERE userid=? AND type=?",
- undef, $userid, $type);
- }
-}
-
-# <LJFUNC>
-# name: LJ::load_rel_user_cache
-# des: Loads user relationship information of the type 'type' where user
-# 'targetid' participates on the left side (is the source of the relationship)
-# trying memcache first. The results from this sub should be
-# <strong>treated as inaccurate and out of date</strong>.
-# args: userid, type
-# des-userid: userid or a user hash to load relationship information for.
-# des-type: type of the relationship
-# returns: reference to an array of userids
-# </LJFUNC>
-sub load_rel_user_cache
-{
- my ($userid, $type) = @_;
- return undef unless $type && $userid;
-
- my $u = LJ::want_user($userid);
- return undef unless $u;
- $userid = $u->{'userid'};
-
- my $key = [ $userid, "reluser:$userid:$type" ];
- my $res = LJ::MemCache::get($key);
-
- return $res if $res;
-
- $res = LJ::load_rel_user($userid, $type);
-
- my $exp = time() + 60*30; # 30 min
- LJ::MemCache::set($key, $res, $exp);
-
- return $res;
-}
-
-# <LJFUNC>
-# name: LJ::load_rel_target
-# des: Load user relationship information. Loads all relationships of type 'type' in
-# which user 'targetid' participates on the right side (is the target of the
-# relationship).
-# args: db?, targetid, type
-# des-targetid: userid or a user hash to load relationship information for.
-# des-type: type of the relationship
-# returns: reference to an array of userids
-# </LJFUNC>
-sub load_rel_target
-{
- my $db = LJ::DB::isdb( $_[0] ) ? shift : undef;
- my ($targetid, $type) = @_;
- return undef unless $type and $targetid;
- my $u = LJ::want_user($targetid);
- $targetid = LJ::want_userid($targetid);
- my $typeid = LJ::get_reluser_id($type)+0;
- if ($typeid) {
- # clustered reluser2 table
- $db = LJ::get_cluster_reader($u);
- return $db->selectcol_arrayref("SELECT userid FROM reluser2 WHERE targetid=? AND type=?",
- undef, $targetid, $typeid);
- } else {
- # non-clustered reluser global table
- $db ||= LJ::get_db_reader();
- return $db->selectcol_arrayref("SELECT userid FROM reluser WHERE targetid=? AND type=?",
- undef, $targetid, $type);
- }
-}
-
-# <LJFUNC>
-# name: LJ::load_rel_target_cache
-# des: Loads user relationship information of the type 'type' where user
-# 'targetid' participates on the right side (is the target of the relationship)
-# trying memcache first. The results from this sub should be
-# <strong>treated as inaccurate and out of date</strong>.
-# args: targetid, type
-# des-userid: userid or a user hash to load relationship information for.
-# des-type: type of the relationship
-# returns: reference to an array of userids
-# </LJFUNC>
-sub load_rel_target_cache
-{
- my ($userid, $type) = @_;
- return undef unless $type && $userid;
-
- my $u = LJ::want_user($userid);
- return undef unless $u;
- $userid = $u->{'userid'};
-
- my $key = [ $userid, "reluser_rev:$userid:$type" ];
- my $res = LJ::MemCache::get($key);
-
- return $res if $res;
-
- $res = LJ::load_rel_target($userid, $type);
-
- my $exp = time() + 60*30; # 30 min
- LJ::MemCache::set($key, $res, $exp);
-
- return $res;
-}
-
-# <LJFUNC>
-# name: LJ::_get_rel_memcache
-# des: Helper function: returns memcached value for a given (userid, targetid, type) triple, if valid.
-# args: userid, targetid, type
-# des-userid: source userid, nonzero
-# des-targetid: target userid, nonzero
-# des-type: type (reluser) or typeid (rel2) of the relationship
-# returns: undef on failure, 0 or 1 depending on edge existence
-# </LJFUNC>
-sub _get_rel_memcache {
- return undef unless @LJ::MEMCACHE_SERVERS;
- return undef unless LJ::is_enabled('memcache_reluser');
-
- my ($userid, $targetid, $type) = @_;
- return undef unless $userid && $targetid && defined $type;
-
- # memcache keys
- my $relkey = [$userid, "rel:$userid:$targetid:$type"]; # rel $uid->$targetid edge
- my $modukey = [$userid, "relmodu:$userid:$type" ]; # rel modtime for uid
- my $modtkey = [$targetid, "relmodt:$targetid:$type" ]; # rel modtime for targetid
-
- # do a get_multi since $relkey and $modukey are both hashed on $userid
- my $memc = LJ::MemCache::get_multi($relkey, $modukey);
- return undef unless $memc && ref $memc eq 'HASH';
-
- # [{0|1}, modtime]
- my $rel = $memc->{$relkey->[1]};
- return undef unless $rel && ref $rel eq 'ARRAY';
-
- # check rel modtime for $userid
- my $relmodu = $memc->{$modukey->[1]};
- return undef if ! $relmodu || $relmodu > $rel->[1];
-
- # check rel modtime for $targetid
- my $relmodt = LJ::MemCache::get($modtkey);
- return undef if ! $relmodt || $relmodt > $rel->[1];
-
- # return memcache value if it's up-to-date
- return $rel->[0] ? 1 : 0;
-}
-
-# <LJFUNC>
-# name: LJ::_set_rel_memcache
-# des: Helper function: sets memcache values for a given (userid, targetid, type) triple
-# args: userid, targetid, type
-# des-userid: source userid, nonzero
-# des-targetid: target userid, nonzero
-# des-type: type (reluser) or typeid (rel2) of the relationship
-# returns: 1 on success, undef on failure
-# </LJFUNC>
-sub _set_rel_memcache {
- return 1 unless @LJ::MEMCACHE_SERVERS;
-
- my ($userid, $targetid, $type, $val) = @_;
- return undef unless $userid && $targetid && defined $type;
- $val = $val ? 1 : 0;
-
- # memcache keys
- my $relkey = [$userid, "rel:$userid:$targetid:$type"]; # rel $uid->$targetid edge
- my $modukey = [$userid, "relmodu:$userid:$type" ]; # rel modtime for uid
- my $modtkey = [$targetid, "relmodt:$targetid:$type" ]; # rel modtime for targetid
-
- my $now = time();
- my $exp = $now + 3600*6; # 6 hour
- LJ::MemCache::set($relkey, [$val, $now], $exp);
- LJ::MemCache::set($modukey, $now, $exp);
- LJ::MemCache::set($modtkey, $now, $exp);
-
- # Also, delete these keys, since the contents have changed.
- LJ::MemCache::delete([$userid, "reluser:$userid:$type"]);
- LJ::MemCache::delete([$targetid, "reluser_rev:$targetid:$type"]);
-
- return 1;
-}
-
-# <LJFUNC>
-# name: LJ::check_rel
-# des: Checks whether two users are in a specified relationship to each other.
-# args: userid, targetid, type
-# des-userid: source userid, nonzero; may also be a user hash.
-# des-targetid: target userid, nonzero; may also be a user hash.
-# des-type: type of the relationship
-# returns: 1 if the relationship exists, 0 otherwise
-# </LJFUNC>
-sub check_rel {
- my ($userid, $targetid, $type) = @_;
- return undef unless $type && $userid && $targetid;
-
- my $u = LJ::want_user($userid);
- $userid = LJ::want_userid($userid);
- $targetid = LJ::want_userid($targetid);
-
- my $typeid = LJ::get_reluser_id($type)+0;
- my $eff_type = $typeid || $type;
-
- my $key = "$userid-$targetid-$eff_type";
- return $LJ::REQ_CACHE_REL{$key} if defined $LJ::REQ_CACHE_REL{$key};
-
- # did we get something from memcache?
- my $memval = LJ::_get_rel_memcache($userid, $targetid, $eff_type);
- return $memval if defined $memval;
-
- # are we working on reluser or reluser2?
- my ( $db, $table );
- if ($typeid) {
- # clustered reluser2 table
- $db = LJ::get_cluster_reader($u);
- $table = "reluser2";
- } else {
- # non-clustered reluser table
- $db = LJ::get_db_reader();
- $table = "reluser";
- }
-
- # get data from db, force result to be {0|1}
- my $dbval = $db->selectrow_array("SELECT COUNT(*) FROM $table ".
- "WHERE userid=? AND targetid=? AND type=? ",
- undef, $userid, $targetid, $eff_type)
- ? 1 : 0;
-
- # set in memcache
- LJ::_set_rel_memcache($userid, $targetid, $eff_type, $dbval);
-
- # return and set request cache
- return $LJ::REQ_CACHE_REL{$key} = $dbval;
-}
-
-# <LJFUNC>
-# name: LJ::set_rel
-# des: Sets relationship information for two users.
-# args: dbs?, userid, targetid, type
-# des-dbs: Deprecated; optional, a master/slave set of database handles.
-# des-userid: source userid, or a user hash
-# des-targetid: target userid, or a user hash
-# des-type: type of the relationship
-# returns: 1 if set succeeded, otherwise undef
-# </LJFUNC>
-sub set_rel {
- my ($userid, $targetid, $type) = @_;
- return undef unless $type and $userid and $targetid;
-
- my $u = LJ::want_user($userid);
- $userid = LJ::want_userid($userid);
- $targetid = LJ::want_userid($targetid);
-
- my $typeid = LJ::get_reluser_id($type)+0;
- my $eff_type = $typeid || $type;
-
- # working on reluser or reluser2?
- my ($db, $table);
- if ($typeid) {
- # clustered reluser2 table
- $db = LJ::get_cluster_master($u);
- $table = "reluser2";
- } else {
- # non-clustered reluser global table
- $db = LJ::get_db_writer();
- $table = "reluser";
- }
- return undef unless $db;
-
- # set in database
- $db->do("REPLACE INTO $table (userid, targetid, type) VALUES (?, ?, ?)",
- undef, $userid, $targetid, $eff_type);
- return undef if $db->err;
-
- # set in memcache
- LJ::_set_rel_memcache($userid, $targetid, $eff_type, 1);
-
- return 1;
-}
-
-# <LJFUNC>
-# name: LJ::set_rel_multi
-# des: Sets relationship edges for lists of user tuples.
-# args: edges
-# des-edges: array of arrayrefs of edges to set: [userid, targetid, type].
-# Where:
-# userid: source userid, or a user hash;
-# targetid: target userid, or a user hash;
-# type: type of the relationship.
-# returns: 1 if all sets succeeded, otherwise undef
-# </LJFUNC>
-sub set_rel_multi {
- return _mod_rel_multi({ mode => 'set', edges => \@_ });
-}
-
-# <LJFUNC>
-# name: LJ::clear_rel_multi
-# des: Clear relationship edges for lists of user tuples.
-# args: edges
-# des-edges: array of arrayrefs of edges to clear: [userid, targetid, type].
-# Where:
-# userid: source userid, or a user hash;
-# targetid: target userid, or a user hash;
-# type: type of the relationship.
-# returns: 1 if all clears succeeded, otherwise undef
-# </LJFUNC>
-sub clear_rel_multi {
- return _mod_rel_multi({ mode => 'clear', edges => \@_ });
-}
-
-# <LJFUNC>
-# name: LJ::_mod_rel_multi
-# des: Sets/Clears relationship edges for lists of user tuples.
-# args: keys, edges
-# des-keys: keys: mode => {clear|set}.
-# des-edges: edges => array of arrayrefs of edges to set: [userid, targetid, type]
-# Where:
-# userid: source userid, or a user hash;
-# targetid: target userid, or a user hash;
-# type: type of the relationship.
-# returns: 1 if all updates succeeded, otherwise undef
-# </LJFUNC>
-sub _mod_rel_multi
-{
- my $opts = shift;
- return undef unless @{$opts->{edges}};
-
- my $mode = $opts->{mode} eq 'clear' ? 'clear' : 'set';
- my $memval = $mode eq 'set' ? 1 : 0;
-
- my @reluser = (); # [userid, targetid, type]
- my @reluser2 = ();
- foreach my $edge (@{$opts->{edges}}) {
- my ($userid, $targetid, $type) = @$edge;
- $userid = LJ::want_userid($userid);
- $targetid = LJ::want_userid($targetid);
- next unless $type && $userid && $targetid;
-
- my $typeid = LJ::get_reluser_id($type)+0;
- my $eff_type = $typeid || $type;
-
- # working on reluser or reluser2?
- push @{$typeid ? \@reluser2 : \@reluser}, [$userid, $targetid, $eff_type];
- }
-
- # now group reluser2 edges by clusterid
- my %reluser2 = (); # cid => [userid, targetid, type]
- my $users = LJ::load_userids(map { $_->[0] } @reluser2);
- foreach (@reluser2) {
- my $cid = $users->{$_->[0]}->{clusterid} or next;
- push @{$reluser2{$cid}}, $_;
- }
- @reluser2 = ();
-
- # try to get all required cluster masters before we start doing database updates
- my %cache_dbcm = ();
- foreach my $cid (keys %reluser2) {
- next unless @{$reluser2{$cid}};
-
- # return undef immediately if we won't be able to do all the updates
- $cache_dbcm{$cid} = LJ::get_cluster_master($cid)
- or return undef;
- }
-
- # if any error occurs with a cluster, we'll skip over that cluster and continue
- # trying to process others since we've likely already done some amount of db
- # updates already, but we'll return undef to signify that everything did not
- # go smoothly
- my $ret = 1;
-
- # do clustered reluser2 updates
- foreach my $cid (keys %cache_dbcm) {
- # array of arrayrefs: [userid, targetid, type]
- my @edges = @{$reluser2{$cid}};
-
- # set in database, then in memcache. keep the two atomic per clusterid
- my $dbcm = $cache_dbcm{$cid};
-
- my @vals = map { @$_ } @edges;
-
- if ($mode eq 'set') {
- my $bind = join(",", map { "(?,?,?)" } @edges);
- $dbcm->do("REPLACE INTO reluser2 (userid, targetid, type) VALUES $bind",
- undef, @vals);
- }
-
- if ($mode eq 'clear') {
- my $where = join(" OR ", map { "(userid=? AND targetid=? AND type=?)" } @edges);
- $dbcm->do("DELETE FROM reluser2 WHERE $where", undef, @vals);
- }
-
- # don't update memcache if db update failed for this cluster
- if ($dbcm->err) {
- $ret = undef;
- next;
- }
-
- # updates to this cluster succeeded, set memcache
- LJ::_set_rel_memcache(@$_, $memval) foreach @edges;
- }
-
- # do global reluser updates
- if (@reluser) {
-
- # nothing to do after this block but return, so we can
- # immediately return undef from here if there's a problem
- my $dbh = LJ::get_db_writer()
- or return undef;
-
- my @vals = map { @$_ } @reluser;
-
- if ($mode eq 'set') {
- my $bind = join(",", map { "(?,?,?)" } @reluser);
- $dbh->do("REPLACE INTO reluser (userid, targetid, type) VALUES $bind",
- undef, @vals);
- }
-
- if ($mode eq 'clear') {
- my $where = join(" OR ", map { "userid=? AND targetid=? AND type=?" } @reluser);
- $dbh->do("DELETE FROM reluser WHERE $where", undef, @vals);
- }
-
- # don't update memcache if db update failed for this cluster
- return undef if $dbh->err;
-
- # $_ = [userid, targetid, type] for each iteration
- LJ::_set_rel_memcache(@$_, $memval) foreach @reluser;
- }
-
- return $ret;
-}
-
-
-# <LJFUNC>
-# name: LJ::clear_rel
-# des: Deletes a relationship between two users or all relationships of a particular type
-# for one user, on either side of the relationship.
-# info: One of userid,targetid -- bit not both -- may be '*'. In that case,
-# if, say, userid is '*', then all relationship edges with target equal to
-# targetid and of the specified type are deleted.
-# If both userid and targetid are numbers, just one edge is deleted.
-# args: dbs?, userid, targetid, type
-# des-dbs: Deprecated; optional, a master/slave set of database handles.
-# des-userid: source userid, or a user hash, or '*'
-# des-targetid: target userid, or a user hash, or '*'
-# des-type: type of the relationship
-# returns: 1 if clear succeeded, otherwise undef
-# </LJFUNC>
-sub clear_rel {
- my ($userid, $targetid, $type) = @_;
- return undef if $userid eq '*' and $targetid eq '*';
-
- my $u;
- $u = LJ::want_user($userid) unless $userid eq '*';
- $userid = LJ::want_userid($userid) unless $userid eq '*';
- $targetid = LJ::want_userid($targetid) unless $targetid eq '*';
- return undef unless $type && $userid && $targetid;
-
- my $typeid = LJ::get_reluser_id($type)+0;
-
- if ($typeid) {
- # clustered reluser2 table
- return undef unless $u->writer;
-
- $u->do("DELETE FROM reluser2 WHERE " . ($userid ne '*' ? "userid=$userid AND " : "") .
- ($targetid ne '*' ? "targetid=$targetid AND " : "") . "type=$typeid");
-
- return undef if $u->err;
- } else {
- # non-clustered global reluser table
- my $dbh = LJ::get_db_writer()
- or return undef;
-
- my $qtype = $dbh->quote($type);
- $dbh->do("DELETE FROM reluser WHERE " . ($userid ne '*' ? "userid=$userid AND " : "") .
- ($targetid ne '*' ? "targetid=$targetid AND " : "") . "type=$qtype");
-
- return undef if $dbh->err;
- }
-
- # if one of userid or targetid are '*', then we need to note the modtime
- # of the reluser edge from the specified id (the one that's not '*')
- # so that subsequent gets on rel:userid:targetid:type will know to ignore
- # what they got from memcache
- my $eff_type = $typeid || $type;
- if ($userid eq '*') {
- LJ::MemCache::set([$targetid, "relmodt:$targetid:$eff_type"], time());
- } elsif ($targetid eq '*') {
- LJ::MemCache::set([$userid, "relmodu:$userid:$eff_type"], time());
-
- # if neither userid nor targetid are '*', then just call _set_rel_memcache
- # to update the rel:userid:targetid:type memcache key as well as the
- # userid and targetid modtime keys
- } else {
- LJ::_set_rel_memcache($userid, $targetid, $eff_type, 0);
- }
-
- return 1;
-}
-
-1;
--------------------------------------------------------------------------------
