[Bps-public-commit] djabberd-rosterstorage-sqlite branch, master, created. 18ad1289d81788a1e0de945234cd40986d666c17

Alex Vandiver alexmv at bestpractical.com
Wed May 26 01:39:35 EDT 2010


The branch, master has been created
        at  18ad1289d81788a1e0de945234cd40986d666c17 (commit)

- Log -----------------------------------------------------------------
commit ce5f2011c251f2a6c2272a388bb3fc1cf8d994d7
Author: Brad Fitzpatrick <brad at danga.com>
Date:   Wed Sep 27 21:18:06 2006 +0000

    moving the SQLite roster storage plugin
    
    
    
    git-svn-id: http://code.sixapart.com/svn/djabberd/trunk@688 43dd9337-660f-0410-bd32-e7601923a1a1

diff --git a/lib/DJabberd/RosterStorage/SQLite.pm b/lib/DJabberd/RosterStorage/SQLite.pm
new file mode 100644
index 0000000..1f629ce
--- /dev/null
+++ b/lib/DJabberd/RosterStorage/SQLite.pm
@@ -0,0 +1,382 @@
+package DJabberd::RosterStorage::SQLite;
+# abstract base class
+use strict;
+use warnings;
+use base 'DJabberd::RosterStorage';
+
+use DBI;
+use DJabberd::Log;
+our $logger = DJabberd::Log->get_logger();
+
+use vars qw($_respect_subscription);
+
+sub set_config_database {
+    my ($self, $dbfile) = @_;
+    $self->{dbfile} = $dbfile;
+    $logger->info("Loaded SQLite RosterStorage using file '$dbfile'");
+}
+
+sub finalize {
+    my $self = shift;
+    die "No 'Database' configured'" unless $self->{dbfile};
+
+    my $dbh = DBI->connect_cached("dbi:SQLite:dbname=$self->{dbfile}","","", { RaiseError => 1, PrintError => 0, AutoCommit => 1 });
+    $self->{dbh} = $dbh;
+    $self->check_install_schema;
+    return $self;
+}
+
+sub check_install_schema {
+    my $self = shift;
+    my $dbh = $self->{dbh};
+
+    eval {
+        $dbh->do(qq{
+            CREATE TABLE jidmap (
+                                 jidid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+                                 jid   VARCHAR(255) NOT NULL,
+                                 UNIQUE (jid)
+                                 )});
+        $dbh->do(qq{
+            CREATE TABLE roster (
+                                 userid        INTEGER REFERENCES jidmap NOT NULL,
+                                 contactid     INTEGER REFERENCES jidmap NOT NULL,
+                                 name          VARCHAR(255),
+                                 subscription  INTEGER NOT NULL REFERENCES substates DEFAULT 0,
+                                 PRIMARY KEY (userid, contactid)
+                                 )});
+        $dbh->do(qq{
+            CREATE TABLE rostergroup (
+                                      groupid       INTEGER PRIMARY KEY NOT NULL,
+                                      userid        INTEGER REFERENCES jidmap NOT NULL,
+                                      name          VARCHAR(255),
+                                      UNIQUE (userid, name)
+                                      )});
+        $dbh->do(qq{
+            CREATE TABLE groupitem (
+                                    groupid       INTEGER REFERENCES jidmap NOT NULL,
+                                    contactid     INTEGER REFERENCES jidmap NOT NULL,
+                                    PRIMARY KEY (groupid, contactid)
+                                    )});
+
+    };
+    if ($@ && $@ !~ /table \w+ already exists/) {
+        $logger->logdie("SQL error $@");
+        die "SQL error: $@\n";
+    }
+
+    $logger->info("Created all roster tables");
+
+}
+
+sub blocking { 1 }
+
+sub get_roster {
+    my ($self, $cb, $jid) = @_;
+
+
+    $logger->debug("Getting roster for '$jid'");
+
+    my $dbh = $self->{dbh};
+
+    my $roster = DJabberd::Roster->new;
+
+    my $sql = qq{
+        SELECT r.contactid, r.name, r.subscription, jmc.jid
+        FROM roster r, jidmap jm, jidmap jmc
+        WHERE r.userid=jm.jidid and jm.jid=? and jmc.jidid=r.contactid
+    };
+
+    # contacts is { contactid -> $row_hashref }
+    my $contacts = eval {
+        $dbh->selectall_hashref($sql, "contactid", undef, $jid->as_bare_string);
+    };
+    $logger->logdie("Failed to load roster: $@") if $@;
+
+    foreach my $contact (values %$contacts) {
+        my $item =
+          DJabberd::RosterItem->new(
+                                    jid          => $contact->{jid},
+                                    name         => $contact->{name},
+                                    subscription => DJabberd::Subscription->from_bitmask($contact->{subscription}),
+                                    );
+
+        # convert all the values in the hashref into RosterItems
+        $contacts->{$contact->{contactid}} = $item;
+        $roster->add($item);
+    }
+
+    # get all the groups, and add them to the roster items
+    eval {
+        $sql = qq{
+            SELECT rg.name, gi.contactid
+                FROM   rostergroup rg, jidmap j, groupitem gi
+                WHERE  gi.groupid=rg.groupid AND rg.userid=j.jidid AND j.jid=?
+            };
+        my $sth = $dbh->prepare($sql);
+        $sth->execute($jid->as_bare_string);
+        while (my ($group_name, $contactid) = $sth->fetchrow_array) {
+            my $ri = $contacts->{$contactid} or next;
+            $ri->add_group($group_name);
+        }
+    };
+    $logger->logdie("Failed to load roster groups: $@") if $@;
+    $logger->debug("  ... got groups, calling set_roster..");
+
+    $cb->set_roster($roster);
+
+}
+
+# to be called outside of a transaction, in auto-commit mode
+sub _jidid_alloc {
+    my ($self, $jid) = @_;
+    my $dbh  = $self->{dbh};
+    my $jids = $jid->as_bare_string;
+    my $id   = eval {
+        $dbh->selectrow_array("SELECT jidid FROM jidmap WHERE jid=?",
+                              undef, $jids);
+    };
+    $logger->logdie("Failed to select from jidmap: $@") if $@;
+    return $id if $id;
+
+    eval {
+        $dbh->do("INSERT INTO jidmap (jidid, jid) VALUES (NULL, ?)",
+                 undef, $jids);
+    };
+    $logger->logdie("_jidid_alloc failed: $@") if $@;
+
+    $id = $dbh->last_insert_id(undef, undef, "jidmap", "jidid")
+        or $logger->logdie("Failed to allocate a number in _jidid_alloc");
+
+    return $id;
+}
+
+# to be called outside of a transaction, in auto-commit mode
+sub _groupid_alloc {
+    my ($self, $userid, $name) = @_;
+    my $dbh  = $self->{dbh};
+    my $id   = eval {
+        $dbh->selectrow_array("SELECT groupid FROM rostergroup WHERE userid=? AND name=?",
+                              undef, $userid, $name);
+    };
+    $logger->logdie("Failed to select from groupid: $@") if $@;
+    return $id if $id;
+
+    eval {
+        $dbh->do("INSERT INTO rostergroup (groupid, userid, name) VALUES (NULL, ?, ?)",
+                 undef, $userid, $name);
+    };
+    $logger->logdie("_groupid_alloc failed: $@") if $@;
+
+    $id = $dbh->last_insert_id(undef, undef, "rostergroup", "groupid")
+        or $logger->logdie("Failed to allocate a number in _groupid_alloc");
+
+    return $id;
+}
+
+sub set_roster_item {
+    my ($self, $cb, $jid, $ritem) = @_;
+    local $_respect_subscription = 1;
+    $logger->debug("Set roster item");
+    $self->addupdate_roster_item($cb, $jid, $ritem);
+}
+
+sub addupdate_roster_item {
+    my ($self, $cb, $jid, $ritem) = @_;
+    my $dbh  = $self->{dbh};
+
+    my $userid    = $self->_jidid_alloc($jid);
+    my $contactid = $self->_jidid_alloc($ritem->jid);
+
+    unless ($userid && $contactid) {
+        $cb->error("no userid and contactid");
+        return;
+    }
+
+    $dbh->begin_work or
+        $logger->logdie("Failed to begin work");
+
+    my $fail = sub {
+        my $reason = shift;
+        die "Failing to addupdate: $reason";
+        $dbh->rollback;
+        $cb->error($reason);
+        return;
+    };
+
+    my $exist_row = $dbh->selectrow_hashref("SELECT * FROM roster WHERE userid=? AND contactid=?",
+                                            undef, $userid, $contactid);
+
+
+    my %in_group;  # groupname -> 1
+
+    if ($exist_row) {
+        my @groups = $self->_groups_of_contactid($userid, $contactid);
+        my %to_del; # groupname -> groupid
+        foreach my $g (@groups) {
+            $in_group{$g->[1]} = 1;
+            $to_del  {$g->[1]} = $g->[0];
+        }
+        foreach my $gname ($ritem->groups) {
+            delete $to_del{$gname};
+        }
+        if (my $in = join(",", values %to_del)) {
+            $dbh->do("DELETE FROM groupitem WHERE groupid IN ($in) AND contactid=?",
+                     undef, $contactid);
+        }
+
+        # by default, don't change subscription, unless we're being called
+        # via set_roster_item.
+        my $sub_value = "subscription";
+        if ($_respect_subscription) {
+            $sub_value = $ritem->subscription->as_bitmask;
+            $logger->debug(" sub_value = $sub_value");
+        } else {
+            # but let's set our subscription in $ritem (since it comes to
+            # us as 'none') because we have to pass it back with the real
+            # value.
+            $ritem->set_subscription(DJabberd::Subscription->from_bitmask($exist_row->{subscription}));
+        }
+
+        my $sql  = "UPDATE roster SET name=?, subscription=$sub_value WHERE userid=? AND contactid=?";
+        my @args = ($ritem->name, $userid, $contactid);
+        $dbh->do($sql, undef, @args);
+    } else {
+        $dbh->do("INSERT INTO roster (userid, contactid, name, subscription) ".
+                 "VALUES (?,?,?,?)", undef,
+                 $userid, $contactid, $ritem->name, $ritem->subscription->as_bitmask)
+    }
+
+    # add to groups
+    foreach my $gname ($ritem->groups) {
+        next if $in_group{$gname};  # already in this group, skip
+        my $gid = $self->_groupid_alloc($userid, $gname);
+        $dbh->do("INSERT OR IGNORE INTO groupitem (groupid, contactid) VALUES (?,?)",
+                 undef, $gid, $contactid);
+    }
+
+    $dbh->commit
+        or return $fail->();
+
+    $cb->done($ritem);
+}
+
+# returns ([groupid, groupname], ...)
+sub _groups_of_contactid {
+    my ($self, $userid, $contactid) = @_;
+    my @ret;
+    my $sql = qq{
+        SELECT rg.groupid, rg.name
+            FROM   rostergroup rg, groupitem gi
+            WHERE  rg.userid=? AND gi.groupid=rg.groupid AND gi.contactid=?
+        };
+    my $sth = $self->{dbh}->prepare($sql);
+    $sth->execute($userid, $contactid);
+    while (my ($gid, $name) = $sth->fetchrow_array) {
+        push @ret, [$gid, $name];
+    }
+    return @ret;
+}
+
+sub delete_roster_item {
+    my ($self, $cb, $jid, $ritem) = @_;
+    $logger->debug("delete roster item!");
+
+    my $dbh  = $self->{dbh};
+
+    my $userid    = $self->_jidid_alloc($jid);
+    my $contactid = $self->_jidid_alloc($ritem->jid);
+
+    unless ($userid && $contactid) {
+        $cb->error("no userid/contactid in delete");
+        return;
+    }
+
+    $dbh->begin_work;
+
+    my $fail = sub {
+        $dbh->rollback;
+        $cb->error;
+        return;
+    };
+
+    my @groups = $self->_groups_of_contactid($userid, $contactid);
+
+    if (my $in = join(",", map { $_->[0] } @groups)) {
+        $dbh->do("DELETE FROM groupitem WHERE groupid IN ($in) AND contactid=?",
+                 undef, $contactid);
+    }
+
+    $dbh->do("DELETE FROM roster WHERE userid=? AND contactid=?",
+             undef, $userid, $contactid)
+        or return $fail->();
+
+    $dbh->commit or $fail->();
+
+    $cb->done;
+}
+
+sub load_roster_item {
+    my ($self, $jid, $contact_jid, $cb) = @_;
+
+    my $dbh  = $self->{dbh};
+
+    my $userid    = $self->_jidid_alloc($jid);
+    my $contactid = $self->_jidid_alloc($contact_jid);
+    unless ($userid && $contactid) {
+        $cb->error("no userid/contactid in load");
+        return;
+    }
+
+    my $row = $dbh->selectrow_hashref("SELECT name, subscription FROM roster ".
+                                      "WHERE userid=? AND contactid=?",
+                                      undef, $userid, $contactid);
+    unless ($row) {
+        $cb->set(undef);
+        return;
+    }
+
+    my $item =
+        DJabberd::RosterItem->new(
+                                  jid          => $contact_jid,,
+                                  name         => $row->{name},
+                                  subscription => DJabberd::Subscription->from_bitmask($row->{subscription}),
+                                  );
+    foreach my $ga ($self->_groups_of_contactid($userid, $contactid)) {
+        $item->add_group($ga->[1]);
+    }
+
+    $cb->set($item);
+    return;
+}
+
+sub wipe_roster {
+    my ($self, $cb, $jid) = @_;
+
+    my $dbh  = $self->{dbh};
+
+    my $userid    = $self->_jidid_alloc($jid);
+    unless ($userid) {
+        $cb->error("no userid/contactid in delete");
+        return;
+    }
+
+    $dbh->begin_work;
+
+    my $fail = sub {
+        $dbh->rollback;
+        $cb->error;
+        return;
+    };
+
+    $dbh->do("DELETE FROM roster WHERE userid=?", undef, $userid)
+        or return $fail->();
+    $dbh->do("DELETE FROM rostergroup WHERE userid=?", undef, $userid)
+        or return $fail->();
+    # FIXME: clean up other tables too.
+
+    $dbh->commit or $fail->();
+    $cb->done;
+}
+
+1;

commit 29c9b648e36e71107b1ce1b0a3735f8f4ac69b1c
Author: Brad Fitzpatrick <brad at danga.com>
Date:   Thu Sep 28 21:59:39 2006 +0000

    prep for release
    
    git-svn-id: http://code.sixapart.com/svn/djabberd/trunk@718 43dd9337-660f-0410-bd32-e7601923a1a1

diff --git a/lib/DJabberd/RosterStorage/SQLite.pm b/lib/DJabberd/RosterStorage/SQLite.pm
index 1f629ce..4ed3c4c 100644
--- a/lib/DJabberd/RosterStorage/SQLite.pm
+++ b/lib/DJabberd/RosterStorage/SQLite.pm
@@ -8,7 +8,8 @@ use DBI;
 use DJabberd::Log;
 our $logger = DJabberd::Log->get_logger();
 
-use vars qw($_respect_subscription);
+use vars qw($_respect_subscription $VERSION);
+$VERSION = '1.00';
 
 sub set_config_database {
     my ($self, $dbfile) = @_;
@@ -380,3 +381,52 @@ sub wipe_roster {
 }
 
 1;
+
+__END__
+
+=head1 NAME
+
+DJabberd::RosterStorage::SQLite - store your jabber roster in SQLite
+
+=head1 SYNOPSIS
+
+ <Vhost yourserver.com>
+    ...
+    <Plugin DJabberd::RosterStorage::SQLite>
+       Database roster.sqlite
+    </Plugin>
+    ...
+  </VHost>
+
+=head1 DESCRIPTION
+
+This stores your Jabber roster ("buddy list") in an SQLite database.
+
+The schema is automatically created on first use.
+
+=head1 WARNING: BLOCKS!
+
+This plugin blocks.  That is, it doesn't do database access async in a
+separate thread.  This is not a good plugin to use if you want
+DJabberd to perform well with lots of users.
+
+That said, a certain company is using this for ~100 employees with no
+problems.
+
+=head1 COPYRIGHT
+
+This module is Copyright (c) 2006 Six Apart, Ltd.
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public
+License or the Artistic License, as specified in the Perl README file.
+
+=head1 WARRANTY
+
+This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
+
+=head1 AUTHORS
+
+Brad Fitzpatrick <brad at danga.com>
+
+Artur Bergman <sky at crucially.net>
diff --git a/t/00-use.t b/t/00-use.t
new file mode 100644
index 0000000..5cf1ccc
--- /dev/null
+++ b/t/00-use.t
@@ -0,0 +1,4 @@
+#!/usr/bin/perl
+use strict;
+use Test::More tests => 1;
+use_ok("DJabberd::RosterStorage::SQLite");

commit aaa0f48c45648a1087954f2309649db6d75ee5b8
Author: Brad Fitzpatrick <brad at danga.com>
Date:   Thu Sep 28 21:59:46 2006 +0000

    prep for release
    
    git-svn-id: http://code.sixapart.com/svn/djabberd/trunk@719 43dd9337-660f-0410-bd32-e7601923a1a1

diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..7bbf421
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,15 @@
+#!/usr/bin/perl
+use 5.008;
+use ExtUtils::MakeMaker;
+WriteMakefile(
+              NAME         => 'DJabberd::RosterStorage::SQLite',
+              VERSION_FROM => 'lib/DJabberd/RosterStorage/SQLite.pm',
+              ABSTRACT_FROM => 'lib/DJabberd/RosterStorage/SQLite.pm',
+              PREREQ_PM    => {
+                  'DJabberd'                => '0.80',
+                  'DBD::SQLite'             => '1.08',
+                  'DBI',                    => 0,
+              },
+              AUTHOR     => 'Brad Fitzpatrick <brad at danga.com>',
+              );
+

commit 16d19e5aff540f05fb0cfa112c51c72911964af8
Author: Brad Fitzpatrick <brad at danga.com>
Date:   Thu Sep 28 21:59:58 2006 +0000

    prep for release
    
    git-svn-id: http://code.sixapart.com/svn/djabberd/trunk@720 43dd9337-660f-0410-bd32-e7601923a1a1

diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..3139bc1
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,5 @@
+lib/DJabberd/RosterStorage/SQLite.pm
+Makefile.PL
+MANIFEST			This list of files
+t/00-use.t
+META.yml                                 Module meta-data (added by MakeMaker)

commit 18ad1289d81788a1e0de945234cd40986d666c17
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Sep 24 17:35:42 2009 -0400

    Add tool to unify mixed-case records in existing SQLite rosters

diff --git a/fix-jabber-roster-case b/fix-jabber-roster-case
new file mode 100755
index 0000000..385f79e
--- /dev/null
+++ b/fix-jabber-roster-case
@@ -0,0 +1,176 @@
+#!/usr/bin/perl
+
+use DJabberd::JID;
+use DJabberd::Subscription;
+use DBI;
+use strict;
+use warnings;
+
+die "$0 must be run with a case-folding version of DJabberd\n"
+    unless DJabberd::JID->new('TEST at example.com')->eq(DJabberd::JID->new('test at example.com'));
+
+my $roster = shift @ARGV;
+die "Usage: $0 rosterfile\n" unless defined $roster and -f $roster;
+
+# Note that all work is performed in a transaction for consistency,
+# hence AutoCommit => 0
+my $dbh = DBI->connect(
+    "dbi:SQLite:dbname=$roster", "", "",
+    { RaiseError => 1, PrintError => 0, AutoCommit => 0 }
+);
+
+my $all_dups = $dbh->selectcol_arrayref(<<SQL);
+SELECT LOWER(first.jid)
+  FROM jidmap first
+       JOIN jidmap other
+         ON LOWER(first.jid) = LOWER(other.jid)
+       LEFT JOIN jidmap later
+         ON LOWER(later.jid) = LOWER(first.jid)
+        AND later.jidid > first.jidid
+ GROUP BY first.jid
+HAVING COUNT(DISTINCT other.jid) > 1
+   AND later.jid IS NULL
+ ORDER BY COUNT(DISTINCT other.jid), LOWER(first.jid)
+SQL
+
+for my $name (@{$all_dups}) {
+    warn "Looking at $name\n";
+
+    # Grab the set of dups
+    my $dups = $dbh->selectall_arrayref(<<SQL, undef, $name);
+SELECT jidid, jid FROM jidmap WHERE LOWER(jid) = ?
+SQL
+
+    # For later use, build up the set of JIDs ids to coalesce.
+    my $jidset = "(" . join(",", map {$_->[0]} @$dups) .")";
+    warn "  JID equivalence set is $jidset\n";
+
+    # Since this is the output of LOWER(jid), it is _probably_ already
+    # correct, but we run it through canonicalize to get KC
+    # normalization, etc
+    $name = canonicalize($name);
+    next unless defined $name;
+    warn "  Canonical form is $name\n";
+
+    # Blow away all of the duplicate JIDs, and re-insert the new name
+    $dbh->do("DELETE FROM jidmap WHERE jidid IN $jidset");
+    $dbh->do("INSERT INTO jidmap(jid) VALUES(?)", undef, $name);
+    my $newid = $dbh->last_insert_id(undef, undef, "jidmap", "jidid");
+    warn "  New row id is $newid\n";
+
+    # Next, find all of the places in the roster where the old IDs
+    # showed up, either as userids or contactids
+    my %types = (userid => "contactid", contactid => "userid");
+    for my $column (keys %types) {
+        warn "  Looking for occurrances in $column\n";
+        my $other = $types{$column};
+        # We now generate a list of contacts that any of the JIDs as
+        # the user, (or users who have any of the JIDs as a contact)
+        my $otherlist = $dbh->selectcol_arrayref(<<SQL);
+SELECT $other FROM roster WHERE $column IN $jidset GROUP BY $other
+SQL
+        for my $otherval (@$otherlist) {
+            my $merge = $dbh->selectall_arrayref(<<SQL, { Slice => {} });
+SELECT $column, name, subscription FROM roster WHERE $other = $otherval AND $column IN $jidset ORDER BY $column ASC
+SQL
+            # We now have a set of information to merge together, in $merge
+            warn "    $otherval references JIDs @{[map $_->{$column}, @$merge]} in $column\n";
+            $dbh->do(<<SQL, undef, $newid, $otherval, merge(@$merge));
+INSERT INTO roster($column, $other, name, subscription) VALUES(?, ?, ?, ?)
+SQL
+        }
+    }
+
+    # Delete any references to old JIDs in the roster
+    $dbh->do("DELETE FROM roster WHERE contactid IN $jidset OR userid IN $jidset");
+
+    # Merge roster groups
+    my $grouplist = $dbh->selectcol_arrayref(<<SQL);
+SELECT name FROM rostergroup WHERE userid IN $jidset GROUP BY name
+SQL
+    for my $groupname (@$grouplist) {
+        # Make a new group of this name for the new JID's id
+        $dbh->do("INSERT INTO rostergroup(userid, name) VALUES(?, ?)", undef, $newid, $groupname);
+        my $groupid = $dbh->last_insert_id(undef, undef, "rostergroup", "groupid");
+        # Find all of the distinct contacts in groups of this name in
+        # any of the old JIDs, and add them to the new group
+        my $merge = $dbh->do(<<SQL, undef, $groupid, $groupname);
+INSERT INTO groupitem(groupid, contactid)
+SELECT ?, groupitem.contactid
+  FROM rostergroup
+       JOIN groupitem
+         ON rostergroup.groupid = groupitem.groupid
+ WHERE rostergroup.name = ?
+   AND rostergroup.userid IN $jidset
+ GROUP BY groupitem.contactid
+SQL
+    }
+    # Remove the old groups
+    $dbh->do("DELETE FROM rostergroup WHERE userid IN $jidset");
+
+    # Look for places the any of the old JIDs appeared in other roster
+    # groups, and replace them with the new JID's id
+    $dbh->do(<<SQL, undef, $newid);
+INSERT INTO groupitem(groupid, contactid)
+SELECT groupid, ?
+  FROM groupitem
+ WHERE contactid IN $jidset
+ GROUP BY groupid
+SQL
+    # Remove the old contacts from the groups
+    $dbh->do("DELETE FROM groupitem WHERE contactid IN $jidset");
+
+}
+
+# The above merely handles cases where one JID appears twice; now we
+# iterate though _all_ names, and lower-case them, to catch JIDs which
+# are incorrectly cased but only appear once.
+my $all = $dbh->selectall_arrayref("SELECT jid, jidid FROM jidmap", { Slice => {} });
+for my $row (@{$all}) {
+    # Canonicalize the name.  Since this is the output of LOWER(jid),
+    # it is _probably_ already correct, but we round-trip through
+    # DJabberd::JID to ensure we get KC normalization, and the like
+    my $name = canonicalize($row->{jid});
+    next unless defined $name;
+    next if $name eq $row->{jid};
+    warn "Looking at @{[$row->{jid}]}\n";
+    warn "  Canonical form is $name\n";
+    $dbh->do("UPDATE jidmap SET jid = ? WHERE jidid = ?", undef, $name, $row->{jidid});
+}
+
+$dbh->commit or die "Error commiting changes: ".$dbh->errstr;
+
+
+
+
+sub canonicalize {
+    # Canonicalize the name.  We round-trip through DJabberd::JID to
+    # ensure we get KC normalization, case folding, and the like
+    my $name = shift;
+    my $jid = DJabberd::JID->new($name);
+    unless ($jid) {
+        warn "Can't make canonical form of $name!";
+        return undef;
+    }
+    return $jid->as_string;
+}
+
+sub merge {
+    my (@merge) = @_;
+
+    # Trivial case
+    return ($merge[0]->{name}, $merge[0]->{subscription})
+        if @merge == 1;
+
+    # More complex case; name is arbitrarily picked to be first non-null name, or null
+    my($name) = grep {defined} map {$_->{name}} @merge;
+    @merge = map {DJabberd::Subscription->from_bitmask($_->{subscription})} @merge;
+
+    # to overrides pendout, from overrides pendin
+    my $merged = DJabberd::Subscription->new;
+    $merged->{to}      = 1 if grep {$_->{to}} @merge;
+    $merged->{pendout} = 1 if not $merged->{to} and grep {$_->{pendout}} @merge;
+    $merged->{from}    = 1 if grep {$_->{from}} @merge;
+    $merged->{pendin}  = 1 if not $merged->{from} and grep {$_->{pendin}} @merge;
+    return $name, $merged->as_bitmask;
+}

-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list