move core profile fields into users table

this change will fix the problems with the userProfileData table being
way too big. it also simplifies many basic user search tasks, not
needing to join the userProfileData table
This commit is contained in:
Doug Bell 2010-12-03 22:36:30 -06:00
parent f43541c5c9
commit 2c51e6d4fd
15 changed files with 129 additions and 53 deletions

View file

@ -1533,7 +1533,7 @@ sub www_view {
my $userSql = $inbox->getMessageSql(undef, { 'select' => <<EOSQL, });
ibox.sentBy,
(IF(userProfileData.firstName != '' and userProfileData.firstName is not null and userProfileData.lastName !='' and userProfileData.lastName is not null, concat(userProfileData.firstName,' ',userProfileData.lastName),users.username)) as fullName
(IF(users.firstName != '' and users.firstName is not null and users.lastName !='' and users.lastName is not null, concat(users.firstName,' ',users.lastName),users.username)) as fullName
EOSQL
tie my %userHash, 'Tie::IxHash';
my $i18n = WebGUI::International->new($session, 'Account_Inbox');

View file

@ -467,7 +467,7 @@ sub www_findUser {
}
my $sql = 'SELECT userId, CONCAT(firstName,lastName) AS name, username, alias, avatar
FROM users JOIN userProfileData USING (userId) WHERE ' . join( ' || ', @places );
FROM users WHERE ' . join( ' || ', @places );
my $params = [ ( $query ) x scalar @places ];
my $sth = $db->read( $sql, $params );

View file

@ -89,7 +89,6 @@ sub _fetchNames {
my %nameHash;
my $sql = "SELECT users.username, users.userId, firstName, lastName
FROM users
LEFT JOIN userProfileData ON users.userId=userProfileData.userId
WHERE users.userId=?";
my $sth = $self->session->db->prepare($sql);
foreach my $userId (@userIds) {
@ -337,7 +336,6 @@ sub www_selectDelegates {
from users
left join groupings on users.userId=groupings.userId
left join InOutBoard on groupings.groupId=InOutBoard.inOutGroup
left join userProfileData on users.userId=userProfileData.userId
left join InOutBoard_status on users.userId=InOutBoard_status.userId and InOutBoard_status.assetId=?
where
users.userId<>'1'

View file

@ -385,7 +385,6 @@ sub _userSearchQuery {
my $query = <<"SQL";
SELECT 'user' AS resourceKind, users.userId AS resourceId
FROM users
LEFT JOIN userProfileData ON users.userId = userProfileData.userId
WHERE (LOWER(lastName) LIKE ? OR LOWER(firstName) LIKE ?
OR LOWER(users.username) LIKE ?) AND (users.userId NOT IN $excludePlaceholders)
ORDER BY lastName, firstName

View file

@ -179,7 +179,7 @@ sub getAlphabetSearchLoop {
my $htmlEncodedLetter = encode_entities($letter);
my $searchURL = "?searchExact_".$fieldName."=".$letter."%25";
my $hasResults;
my $users = $self->session->db->read("select userId from userProfileData where `$fieldName` like '".$letter."%'");
my $users = $self->session->db->read("select userId from users join userProfileData using (userId) where `$fieldName` like '".$letter."%'");
while (my $user = $users->hashRef){
my $showGroupId = $self->showGroupId;
if ($showGroupId eq '0' || ($showGroupId && $self->isInGroup($showGroupId,$user->{userId}))){
@ -430,10 +430,10 @@ sub view {
}
# Query user profile data. Exclude the visitor account and users that have been deactivated.
$sql = "select distinct users.userId, users.userName, userProfileData.publicProfile ";
$sql = "select distinct users.userId, users.userName, users.publicProfile ";
# Include remaining profile fields in the query
foreach my $profileField (@profileFields){
$sql .= ", userProfileData." . $dbh->quote_identifier($profileField->{fieldName});
$sql .= ", " . $dbh->quote_identifier($profileField->{fieldName});
}
$sql .= " from users";
$sql .= " left join userProfileData using(userId) where users.userId != '1' and users.status = 'active'";
@ -447,14 +447,14 @@ sub view {
# Normal search with one keyword in a limited number of fields
foreach my $profileField (@profileFields){
if ($form->process('includeInSearch_'.$profileField->{fieldName})){
push(@profileSearchFields, 'userProfileData.'.$dbh->quote_identifier($profileField->{fieldName})
push(@profileSearchFields, $dbh->quote_identifier($profileField->{fieldName})
.' like '. $dbh->quote('%'.$form->process('search').'%'));
}
}
}
else{
# Normal search with one keyword in all fields
$constraint = "(".join(' or ', map {'userProfileData.'.$dbh->quote_identifier($_->{fieldName})
$constraint = "(".join(' or ', map {$dbh->quote_identifier($_->{fieldName})
.' like '.$dbh->quote('%'.$form->process('search').'%')} @profileFields).")";
}
}
@ -464,14 +464,14 @@ sub view {
# Exact search with one keyword in a limited number of fields
foreach my $profileField (@profileFields){
if ($form->process('includeInSearch_'.$profileField->{fieldName})){
push(@profileSearchFields,'userProfileData.'.$dbh->quote_identifier($profileField->{fieldName})
push(@profileSearchFields,$dbh->quote_identifier($profileField->{fieldName})
.' like '.$dbh->quote($form->process('search')));
}
}
}
else{
# Exact search with one keyword in all fields
$constraint = "(".join(' or ', map {'userProfileData.'.$dbh->quote_identifier($_->{fieldName})
$constraint = "(".join(' or ', map {$dbh->quote_identifier($_->{fieldName})
.' like ' . $dbh->quote($form->process('searchExact'))} @profileFields).")";
}
}
@ -480,11 +480,11 @@ sub view {
foreach my $profileField (@profileFields){
# Exact search has precedence over normal search
if ($form->process('searchExact_'.$profileField->{fieldName})){
push(@profileSearchFields,'userProfileData.'.$dbh->quote_identifier($profileField->{fieldName})
push(@profileSearchFields,$dbh->quote_identifier($profileField->{fieldName})
.' like '. $dbh->quote($form->process('searchExact_'.$profileField->{fieldName})));
}
elsif ($form->process('search_'.$profileField->{fieldName})){
push(@profileSearchFields,'userProfileData.'.$dbh->quote_identifier($profileField->{fieldName})
push(@profileSearchFields,$dbh->quote_identifier($profileField->{fieldName})
.' like '. $dbh->quote('%'.$form->process('search_'.$profileField->{fieldName})));
}
}

View file

@ -997,7 +997,7 @@ sub www_profileRecoverPasswordFinish {
my @fieldNames = keys %fieldValues;
my @fieldValues = values %fieldValues;
my $wheres = join(' ', map{"AND upd.$fieldNames[$_] = ?"} (0..$#fieldNames));
my $wheres = join(' ', map{"AND $fieldNames[$_] = ?"} (0..$#fieldNames));
$wheres .= ' AND u.username = ?' if defined $username;
my $sql = "SELECT u.userId FROM users AS u JOIN userProfileData AS upd ON u.userId=upd.userId WHERE u.authMethod = ? $wheres";
my @userIds = $self->session->db->buildArray($sql, [$self->authMethod, @fieldValues, (defined($username)? ($username) : ())]);

View file

@ -482,7 +482,7 @@ sub getMessageSql {
$select =<<SELECT;
ibox.messageId, ibox.subject, ibox.sentBy, ibox.dateStamp,
(IF(ibox.status = 'completed' or ibox.status = 'pending',ibox.status,IF(inbox_messageState.repliedTo,'replied',IF(inbox_messageState.isRead,'read','unread')))) as messageStatus,
(IF(userProfileData.firstName != '' and userProfileData.firstName is not null and userProfileData.lastName !='' and userProfileData.lastName is not null, concat(userProfileData.firstName,' ',userProfileData.lastName),users.username)) as fullName
(IF(users.firstName != '' and users.firstName is not null and users.lastName !='' and users.lastName is not null, concat(users.firstName,' ',users.lastName),users.username)) as fullName
SELECT
}

View file

@ -735,7 +735,7 @@ sub www_saveSettings {
# Reset login message seen numbers
if ( $session->form->get( 'showMessageOnLoginReset' ) ) {
$session->db->write(
"UPDATE userProfileData SET showMessageOnLoginSeen=0"
"UPDATE users SET showMessageOnLoginSeen=0"
);
$session->cache->clear;
}

View file

@ -227,10 +227,9 @@ sub doUserSearch {
$keyword = "%".$keyword;
}
my $sql = "select users.userId, users.username, users.status, users.dateCreated, users.lastUpdated,
userProfileData.email from users
left join userProfileData on users.userId=userProfileData.userId
users.email from users
where $selectedStatus and (users.username like ? or alias like ? or email like ?
or firstName like ? or lastName like ?)
or firstName like ? or lastName like ? or CONCAT(firstName, ' ', lastName) LIKE ? )
and users.userId not in (".$session->db->quoteAndJoin($userFilter).") order by users.username";
if ($returnPaginator) {
my $p = WebGUI::Paginator->new($session,$session->url->page("op=".$op));

View file

@ -607,7 +607,7 @@ sub isDuplicate {
my $value = shift;
my $userId = shift || $session->user->userId;
my $sql = qq{select count(*) from userProfileData where $fieldId = ? and userId <> ?};
my $sql = qq{select count(*) from users join userProfileData using( userId ) where $fieldId = ? and userId <> ?};
my $duplicate = $session->db->quickScalar($sql,[$value, $userId]);
return ($duplicate > 0);
}
@ -918,7 +918,7 @@ sub set {
}
# If the fieldType has changed, modify the userProfileData column
if ($properties->{fieldType} ne $originalFieldType) {
if ($properties->{fieldType} ne $originalFieldType && !$self->isProtected) {
# Create a copy of the new properties so we don't mess them up
my $fieldClass = $self->getFormControlClass;
eval { WebGUI::Pluggable::load($fieldClass) };

View file

@ -98,7 +98,7 @@ sub _create {
$privacy->{$field->get('fieldName')} = $privacySetting;
}
my $json = JSON->new->encode($privacy);
$session->db->write("update userProfileData set wg_privacySettings=? where userId=?",[$json,$userId]);
$session->db->write("update users set privacyFields=? where userId=?",[$json,$userId]);
WebGUI::Group->new($session,2)->addUsers([$userId]);
WebGUI::Group->new($session,7)->addUsers([$userId]);
@ -578,7 +578,13 @@ sub get {
my $session = $self->session;
if ( $field ) {
return if $field eq 'privacyFields';
if ( exists $self->{_user}->{$field} ) {
if ( !defined $self->{_user}->{$field} ) {
my $default = $session->db->quickScalar("SELECT dataDefault FROM userProfileField WHERE fieldName=?", [$field]);
$self->{_user}{$field}
= WebGUI::Operation::Shared::secureEval($session, $default);
}
return $self->{_user}->{$field};
}
else {
@ -606,7 +612,11 @@ sub get {
"SELECT fieldName, dataDefault FROM userProfileField",
);
for my $key ( keys %default ) {
if ( !exists $self->{_profile}{$key} ) {
if ( exists $self->{_user}->{$key} && !defined $self->{_user}->{$key} ) {
$self->{_user}{$key}
= WebGUI::Operation::Shared::secureEval($session, $default{$key});
}
elsif ( !exists $self->{_user}->{$key} && !exists $self->{_profile}{$key} ) {
$self->{_profile}{$key}
= WebGUI::Operation::Shared::secureEval($session, $default{$key});
}
@ -788,7 +798,7 @@ sub getProfileFieldPrivacySetting {
unless ($self->{_privacySettings}) {
#Look it up manually because we want to cache this separately.
my $privacySettings = $session->db->quickScalar(
q{select wg_privacySettings from userProfileData where userId=?},
q{select privacyFields from users where userId=?},
[$self->userId]
);
$privacySettings = "{}" unless $privacySettings;
@ -798,7 +808,7 @@ sub getProfileFieldPrivacySetting {
return $self->{_privacySettings} unless ($field);
#No privacy settings returned the privacy setting field
return "none" if($field eq "wg_privacySettings");
return "none" if($field eq "privacyFields");
return $self->{_privacySettings}->{$field};
}
@ -1092,7 +1102,6 @@ sub new {
[$user{userId}]
);
delete $profile{userId};
delete $profile{wg_privacySettings};
# Fill in dataDefault
my $default = $session->db->buildHashRef(
@ -1105,8 +1114,8 @@ sub new {
}
}
if (($profile{alias} =~ /^\W+$/ || $profile{alias} eq "") and $user{username}) {
$profile{alias} = $user{username};
if (($user{alias} =~ /^\W+$/ || $user{alias} eq "") and $user{username}) {
$user{alias} = $user{username};
}
$self->{_userId} = $userId;
$self->{_user} = \%user,
@ -1138,7 +1147,7 @@ sub newByEmail {
my $class = shift;
my $session = shift;
my $email = shift;
my ($id) = $session->dbSlave->quickArray("select userId from userProfileData where email=?",[$email]);
my ($id) = $session->dbSlave->quickArray("select userId from users where email=?",[$email]);
my $user = $class->new($session, $id);
return undef if ($user->isVisitor); # visitor is never valid for this method
return undef unless $user->username;
@ -1304,7 +1313,7 @@ sub setProfileFieldPrivacySetting {
#Store the data in the database
my $json = JSON->new->encode($currentSettings);
$session->db->write("update userProfileData set wg_privacySettings=? where userId=?",[$json,$self->userId]);
$session->db->write("update users set privacyFields=? where userId=?",[$json,$self->userId]);
#Recache the current settings
$self->{_privacySettings} = $currentSettings;
@ -1365,14 +1374,38 @@ name => value pairs of user properties and/or profile fields.
Valid user properties:
authMethod
dateCreated
friendsGroup
authMethod - The default auth method for the user. DEPRECATED, all users
can be authed by all auth methods
dateCreated - The unix timestamp when the user was created
friendsGroup - The WebGUI::Group containing the user's friends
karma - NOTE: To add karma, use the karma() method
lastUpdated
referringAffiliate
lastUpdated - The unix timestamp when the user was last updated
referringAffiliate - A WebGUI::User who referred the user
status - One of "Activated", "Deactivated", or "Selfdestructed"
username
username - The username
ableToBeFriend - Whether the user can be added as a friend
alias - Show this instead of the username
allowPrivateMessages - Whether this user can receive private messages
avatar - A WebGUI::Storage containing an avatar image
cellPhone - The user's cell number. Used for the SMS gateway
dateFormat - The desired date format. See WebGUI::DateTime for fields
email - The user's email
firstDayOfWeek - The preferred first day of the week
firstName - The first name
language - The user's i18n language
lastName - The last name
publicProfile - Whether the profile is visible to the public
receiveInboxEmailNotifications - Should inbox messages be sent to the user's email
receiveInboxSmsNotifications - Should inbox messages be sent to user's SMS
showMessageOnLoginSeen - How many times they've seen the login message
showOnline - Should we reveal if the user is online?
signature - The signature for private messages and forum posts
timeFormat - The desired time format. See WebGUI::DateTime for fields
timeZone - The user's time zone. All datetimes will appear local to this
toolbar - Customize the toolbar for an i18n language
uiLevel - The UI Level. Allow users to see more based on their comprehension
versionTagMode - Their version tag mode
Anything else is a profile field.
@ -1401,7 +1434,8 @@ sub update {
delete $properties->{userId};
# This is an internal field with its own api to set it
delete $properties->{wg_privacySettings};
### CHANGE THIS to be settable by update
delete $properties->{privacyFields};
# $self->{_user} contains all fields in `users` table
my @userFields = ();

View file

@ -163,11 +163,11 @@ sub getSql {
return <<END_SQL;
select
r.Survey_responseId, r.username, r.userId, r.startDate,
upd.email, upd.firstName, upd.lastName,
u.email, u.firstName, u.lastName,
s.timeLimit, s.doAfterTimeLimit,
ad.title, ad.url
from
Survey_response r left outer join userProfileData upd on r.userId = upd.userId, Survey s, assetData ad
Survey_response r left outer join users u on r.userId = u.userId, Survey s, assetData ad
where
r.isComplete = 0
and s.timeLimit > 0

View file

@ -86,7 +86,7 @@ sub execute {
SELECT email, count(distinct(tagId)) AS count
FROM assetVersionTag
JOIN assetData USING (tagId)
JOIN userProfileData ON assetVersionTag.createdBy = userProfileData.userId
JOIN users ON assetVersionTag.createdBy = users.userId
WHERE isCommitted = 0
AND DATE_ADD(FROM_UNIXTIME(creationDate), INTERVAL $daysLeftOpen DAY) < NOW()
GROUP BY userId
@ -108,7 +108,7 @@ sub _notify {
my $i18n = shift;
my $hostname = $self->session->config->get('sitename')->[0];
my($from) = $self->session->db->quickScalar(" SELECT email FROM userProfileData WHERE userId = 3 ");
my($from) = $self->session->db->quickScalar(" SELECT email FROM users WHERE userId = 3 ");
my $s = $dataHashRef->{count} > 1 ? 's' : '';
my $subject = sprintf($i18n->get('email subject'), $s, $hostname);

View file

@ -0,0 +1,40 @@
use WebGUI::Upgrade::Script;
use WebGUI::Pluggable;
use WebGUI::ProfileField;
start_step "Move core profile fields to users table...";
my @fields = qw( ableToBeFriend alias allowPrivateMessages avatar cellPhone dateFormat
email firstDayOfWeek firstName language lastName publicProfile receiveInboxEmailNotifications
receiveInboxSmsNotifications showMessageOnLoginSeen showOnline signature timeFormat timeZone
toolbar uiLevel versionTagMode );
# Create the new columns
for my $fieldName ( @fields ) {
my $field = WebGUI::ProfileField->new( session, $fieldName );
my $fieldClass = $field->getFormControlClass;
eval { WebGUI::Pluggable::load( $fieldClass ) };
my $dbType = $fieldClass->getDatabaseFieldType;
session->db->write( sprintf q{ ALTER TABLE users ADD COLUMN `%s` %s }, $fieldName, $dbType );
}
# Update the table
my @pairs = map { q{`users`.`} . $_ . q{`=`userProfileData`.`} . $_ . q{`} } @fields;
session->db->write(
q{ UPDATE `users`,`userProfileData` SET } . join( ", ", @pairs ) .
q{ WHERE `users`.`userId` = `userProfileData`.`userId` }
);
# Drop the old tables
for my $fieldName ( @fields ) {
session->db->write( qq{ ALTER TABLE userProfileData DROP COLUMN `$fieldName` } );
}
# Move not-profile fields in userProfileData
session->db->write( qq{ ALTER TABLE users ADD privacyFields LONGTEXT } );
session->db->write( qq{ UPDATE users,userProfileData SET users.privacyFields = userProfileData.wg_privacySettings } );
session->db->write( qq{ ALTER TABLE userProfileData DROP COLUMN wg_privacySettings } );
done;

View file

@ -13,6 +13,7 @@ use strict;
use WebGUI::Test;
use WebGUI::Session;
#use Exception::Class;
use List::MoreUtils qw( uniq );
use WebGUI::User;
use WebGUI::ProfileField;
@ -150,7 +151,7 @@ is(
);
is(
$session->db->quickScalar("SELECT firstName FROM userProfileData WHERE userId=?",[$user->getId]),
$session->db->quickScalar("SELECT firstName FROM users WHERE userId=?",[$user->getId]),
"John",
"update() updates profile firstName",
);
@ -161,7 +162,7 @@ is(
);
is(
$session->db->quickScalar("SELECT lastName FROM userProfileData WHERE userId=?",[$user->getId]),
$session->db->quickScalar("SELECT lastName FROM users WHERE userId=?",[$user->getId]),
"Lumbergh",
"update() updates profile lastName",
);
@ -188,7 +189,7 @@ ok(
$user->update({ lastName => "Lumberg" }),
is(
$session->db->quickScalar("SELECT lastName FROM userProfileData WHERE userId=?",[$user->getId]),
$session->db->quickScalar("SELECT lastName FROM users WHERE userId=?",[$user->getId]),
"Lumberg",
"update() updates lastName again",
);
@ -210,7 +211,7 @@ my $expectValues = {
};
# expects all user properties and all profile fields
my @expectFields = (
my @expectFields = uniq(
$session->db->buildArray('DESCRIBE users'),
$session->db->buildArray('SELECT fieldName FROM userProfileField'),
);
@ -244,9 +245,9 @@ is($user->profileField('notAProfileField'), undef, 'getting non-existant profile
my $newProfileField = WebGUI::ProfileField->create($session, 'testField', {dataDefault => 'this is a test', fieldType => 'Text'});
is($user->profileField('testField'), 'this is a test', 'getting profile fields not cached in the user object returns the profile field default');
ok(!$user->profileField('wg_privacySettings'), '... wg_privacySettings may not be retrieved');
$user->profileField('wg_privacySettings', '{"email"=>"all"}');
ok(!$user->profileField('wg_privacySettings'), '... wg_privacySettings may not be set');
ok(!$user->profileField('privacyFields'), '... privacyFields may not be retrieved');
$user->profileField('privacyFields', '{"email"=>"all"}');
ok(!$user->profileField('privacyFields'), '... privacyFields may not be set');
################################################################
#
@ -562,6 +563,8 @@ is( $buster->profileField('timeZone'), 'America/Chicago', 'buster received origi
my $profileField = WebGUI::ProfileField->new($session, 'timeZone');
my %originalFieldData = %{ $profileField->get() };
use Data::Dumper;
note( Dumper \%originalFieldData );
my %copiedFieldData = %originalFieldData;
$copiedFieldData{'dataDefault'} = "'America/Hillsboro'";
$profileField->set(\%copiedFieldData);
@ -569,9 +572,12 @@ $profileField->set(\%copiedFieldData);
is($profileField->get('dataDefault'), "'America/Hillsboro'", 'default timeZone set to America/Hillsboro');
# now let's make sure it has an extras field, and that we can get/set it.
$profileField->set( { extras => '<!-- hello world -->' } );
# DID YOU KNOW? you have to set everything otherwise things get messed up!
$copiedFieldData{ 'extras' } = '<!-- hello world -->';
$profileField->set( \%copiedFieldData );
is($profileField->getExtras, '<!-- hello world -->', 'extras field for profileField');
$profileField->set( { extras => '' } );
$copiedFieldData{ 'extras' } = '';
$profileField->set( \%copiedFieldData );
my $busterCopy = WebGUI::User->new($session, $buster->userId);
@ -930,8 +936,8 @@ is($neighbor->getProfileFieldPrivacySetting('email'), 'none', '...get and set 1
$neighbor->setProfileFieldPrivacySetting({email => 'only Tony'});
is($neighbor->getProfileFieldPrivacySetting('email'), 'none', '...set will not set invalid profile settings');
is($admin->getProfileFieldPrivacySetting('publicEmail'), 'all', '...get on a user with existing settings');
is($neighbor->getProfileFieldPrivacySetting('wg_privacySettings'), 'none', '...the privacy field always returns "none"');
is($admin->getProfileFieldPrivacySetting('email'), 'all', '...get on a user with existing settings');
is($neighbor->getProfileFieldPrivacySetting('privacyFields'), 'none', '...the privacy field always returns "none"');
################################################################
#