3 =head1 NAME $RCSfile: Clearquest.pm,v $
5 Object oriented interface to Clearquest.
13 Andrew DeFaria <Andrew@ClearSCM.com>
21 Fri Sep 22 09:21:18 CDT 2006
25 $Date: 2013/03/28 22:48:07 $
31 Provides access to Clearquest database in an object oriented manner.
33 # Create Clearquest object
34 my $cq = Clearquest->new;
36 # Connect to database (using all the defaults in cq.conf)
39 # Connect as non standard user;
41 $cq->connect (CQ_USERNAME => 'me', CQ_PASSWORD => 'mypassword');
43 # Get record (Default: all fields)
44 my %record = $cq->get ($recordName, $key);
46 # Get record with specific field list
47 my %record =$cq->get ($recordName, $key, qw(field1 field2))
51 Description => 'This is a new description',
54 $cq->modify ($recordName, $key, 'Modify', \%update);
56 # Change state using modify with an alternate action. Note the use of @ordering
57 my %fieldsToUpdate = (
59 Category => 'New Functionality',
60 Groups => [ 'Group1', 'Group2' ],
63 my @ordering qw(Project Category);
65 $cq->modify ($recordName, $key, 'Open', \%fieldsToUpdate, @ordering);
68 error "Unable to update $key to Opened state\n"
74 This module provides a simple interface to Clearquest in a Perl like fashion.
75 There are three modes of talking to Clearquest using this module - api, rest
78 With module = 'api' you must have Clearquest installed locally and you must use
79 cqperl to execute your script. This mode of operation has the benefit of speed -
80 note that initial connection to the Clearquest database is not very speedy, but
81 all subsequent calls will operate at full speed. The 'api' module is free to
82 use. For the other modules contact ClearSCM, Inc.
84 With module = 'rest' you can access Clearquest by using a RESTFull interface.
85 You can use any Perl which has the required CPAN modules (REST, XML::Simple -
86 see Clearquest::REST for a list of required CPAN modules). The REST interface is
87 a slower than the native api and requires the setup of Clearquest Web (cqweb) on
88 your network. To use the REST interface set CQ_MODULE to 'rest'.
90 With module = 'client' you access Clearquest through the companion
91 Clearquest::Server module and the cqd.pl server script. The server process is
92 started on a machine that has Clearquest installed locally. It uses the api
93 interface for speed and can operate in a multithreaded manner, spawning
94 processes which open and handle requests from Clearquest::Client requests. To
95 use the Client interface set CQ_MODULE to 'client'.
97 Other than setting CQ_MODULE to one of the three modes described above, the rest
98 of your script's usage of the Clearquest module should be exactly the same.
102 This module uses GetConfig to read in a configuration file (../etc/cq.conf)
103 which sets default values described below. Or you can export the option name to
104 the env(1) to override the defaults in cq.conf. Finally you can programmatically
105 set the options when you call new by passing in a %parms hash. To specify the
106 %parms hash key remove the CQ_ portion and lc the rest.
108 =for html <blockquote>
114 Clearquest server to talk to. Also used for rest server (Default: From cq.conf)
118 Port to connect to (Default: From cq.conf)
122 The web host to contact with leading http:// (Default: From cq.conf)
126 Name of database to connect to (Default: From cq.conf)
130 User name to connect as (Default: From cq.conf)
134 Password for CQREST_USERNAME (Default: From cq.conf)
138 Database Set name (Default: From cq.conf)
142 One of 'api', 'rest' or 'client' (Default: From cq.conf)
148 The following methods are available:
163 # Seed options from config file
164 my $config = $ENV{CQ_CONF} || dirname (__FILE__) . '/../etc/cq.conf';
166 croak "Unable to find config file $config" unless -r $config;
168 our %OPTS = GetConfig $config;
170 my $DEFAULT_DBSET = $OPTS{CQ_DBSET};
172 our $VERSION = '$Revision: 2.23 $';
173 ($VERSION) = ($VERSION =~ /\$Revision: (.*) /);
175 # Override options if in the environment
176 $OPTS{CQ_DATABASE} = $ENV{CQ_DATABASE} if $ENV{CQ_DATABASE};
177 $OPTS{CQ_DBSET} = $ENV{CQ_DBSET} if $ENV{CQ_DBSET};
178 $OPTS{CQ_MODULE} = $ENV{CQ_MODULE} if $ENV{CQ_MODULE};
179 $OPTS{CQ_PASSWORD} = $ENV{CQ_PASSWORD} if $ENV{CQ_PASSWORD};
180 $OPTS{CQ_PORT} = $ENV{CQ_PORT} if $ENV{CQ_PORT};
181 $OPTS{CQ_SERVER} = $ENV{CQ_SERVER} if $ENV{CQ_SERVER};
182 $OPTS{CQ_USERNAME} = $ENV{CQ_USERNAME} if $ENV{CQ_USERNAME};
187 our $MULTILINE_STRING = 2;
191 our $REFERENCE_LIST = 6;
192 our $ATTACHMENT_LIST = 7;
198 our $RECORD_TYPE = 13;
204 my $SECS_IN_MIN = 60;
205 my $SECS_IN_HOUR = $SECS_IN_MIN * 60;
206 my $SECS_IN_DAY = $SECS_IN_HOUR * 24;
215 |<> # the other not equal
216 |<= # less than or equal
217 |>= # greater than or equal
221 |not\s+like # not like
223 |not\s*between # not between
225 |is\s+not\s+null # is not null
234 # Insure all instaniated objects have been destroyed
235 $_->DESTROY for (@objects);
239 sub _commitRecord($) {
240 my ($self, $entity) = @_;
242 $self->{errmsg} = $entity->Validate;
244 if ($self->{errmsg} eq '') {
245 $self->{errmsg} = $entity->Commit;
246 $self->{error} = $self->{errmsg} eq '' ? 0 : 1;
248 return $self->{errmsg};
254 return $self->{errmsg};
258 sub _is_leap_year($) {
261 return 0 if $year % 4;
262 return 1 if $year % 100;
263 return 0 if $year % 400;
268 sub _dateToEpoch($) {
271 my $year = substr $date, 0, 4;
272 my $month = substr $date, 5, 2;
273 my $day = substr $date, 8, 2;
274 my $hour = substr $date, 11, 2;
275 my $minute = substr $date, 14, 2;
276 my $seconds = substr $date, 17, 2;
280 for (my $i = 1970; $i < $year; $i++) {
281 $days += _is_leap_year ($i) ? 366 : 365;
299 $days += $monthDays[$month - 1];
302 if _is_leap_year ($year) and $month > 2;
306 return ($days * $SECS_IN_DAY)
307 + ($hour * $SECS_IN_HOUR)
308 + ($minute * $SECS_IN_MIN)
312 sub _epochToDate($) {
316 my ($month, $day, $hour, $minute, $seconds);
317 my $leapYearSecs = 366 * $SECS_IN_DAY;
318 my $yearSecs = $leapYearSecs - $SECS_IN_DAY;
321 my $amount = _is_leap_year ($year) ? $leapYearSecs : $yearSecs;
330 my $leapYearAdjustment = _is_leap_year ($year) ? 1 : 0;
332 if ($epoch >= (334 + $leapYearAdjustment) * $SECS_IN_DAY) {
334 $epoch -= (334 + $leapYearAdjustment) * $SECS_IN_DAY;
335 } elsif ($epoch >= (304 + $leapYearAdjustment) * $SECS_IN_DAY) {
337 $epoch -= (304 + $leapYearAdjustment) * $SECS_IN_DAY;
338 } elsif ($epoch >= (273 + $leapYearAdjustment) * $SECS_IN_DAY) {
340 $epoch -= (273 + $leapYearAdjustment) * $SECS_IN_DAY;
341 } elsif ($epoch >= (243 + $leapYearAdjustment) * $SECS_IN_DAY) {
343 $epoch -= (243 + $leapYearAdjustment) * $SECS_IN_DAY;
344 } elsif ($epoch >= (212 + $leapYearAdjustment) * $SECS_IN_DAY) {
346 $epoch -= (212 + $leapYearAdjustment) * $SECS_IN_DAY;
347 } elsif ($epoch >= (181 + $leapYearAdjustment) * $SECS_IN_DAY) {
349 $epoch -= (181 + $leapYearAdjustment) * $SECS_IN_DAY;
350 } elsif ($epoch >= (151 + $leapYearAdjustment) * $SECS_IN_DAY) {
352 $epoch -= (151 + $leapYearAdjustment) * $SECS_IN_DAY;
353 } elsif ($epoch >= (120 + $leapYearAdjustment) * $SECS_IN_DAY) {
355 $epoch -= (120 + $leapYearAdjustment) * $SECS_IN_DAY;
356 } elsif ($epoch >= (90 + $leapYearAdjustment) * $SECS_IN_DAY) {
358 $epoch -= (90 + $leapYearAdjustment) * $SECS_IN_DAY;
359 } elsif ($epoch >= (59 + $leapYearAdjustment) * $SECS_IN_DAY) {
361 $epoch -= (59 + $leapYearAdjustment) * $SECS_IN_DAY;
362 } elsif ($epoch >= 31 * $SECS_IN_DAY) {
364 $epoch -= 31 * $SECS_IN_DAY;
369 $day = int (($epoch / $SECS_IN_DAY) + 1);
370 $epoch = $epoch % $SECS_IN_DAY;
371 $hour = int ($epoch / $SECS_IN_HOUR);
372 $epoch = $epoch % $SECS_IN_HOUR;
373 $minute = int ($epoch / $SECS_IN_MIN);
374 $seconds = $epoch % $SECS_IN_MIN;
376 $day = "0$day" if $day < 10;
377 $hour = "0$hour" if $hour < 10;
378 $minute = "0$minute" if $minute < 10;
379 $seconds = "0$seconds" if $seconds < 10;
381 return "$year-$month-$day $hour:$minute:$seconds";
384 sub _parseCondition($) {
385 my ($self, $condition) = @_;
387 # Parse simple conditions only
388 my ($field, $operator, $value);
390 if ($condition =~ $operatorRE) {
395 if ($operator eq '==' or $operator eq '=') {
396 if ($value !~ /^null$/i) {
397 $operator = $CQPerlExt::CQ_COMP_OP_EQ;
399 $operator = $CQPerlExt::CQ_COMP_OP_IS_NULL;
401 } elsif ($operator eq '!=' or $operator eq '<>') {
402 if ($value !~ /^null$/i) {
403 $operator = $CQPerlExt::CQ_COMP_OP_NEQ;
405 $operator = $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL;
407 } elsif ($operator eq '<') {
408 $operator = $CQPerlExt::CQ_COMP_OP_LT;
409 } elsif ($operator eq '>') {
410 $operator = $CQPerlExt::CQ_COMP_OP_GT;
411 } elsif ($operator eq '<=') {
412 $operator = $CQPerlExt::CQ_COMP_OP_LTE;
413 } elsif ($operator eq '>=') {
414 $operator = $CQPerlExt::CQ_COMP_OP_GTE;
415 } elsif ($operator =~ /^like$/i) {
416 $operator = $CQPerlExt::CQ_COMP_OP_LIKE;
417 } elsif ($operator =~ /^not\s+like$/i) {
418 $operator = $CQPerlExt::CQ_COMP_OP_NOT_LIKE;
419 } elsif ($operator =~ /^between$/i) {
420 $operator = $CQPerlExt::CQ_COMP_OP_BETWEEN;
421 } elsif ($operator =~ /^not\s+between$/i) {
422 $operator = $CQPerlExt::CQ_COMP_OP_NOT_BETWEEN;
423 } elsif ($operator =~ /^is\s+null$/i) {
424 $operator = $CQPerlExt::CQ_COMP_OP_IS_NULL;
425 } elsif ($operator =~ /^is\s+not\s+null$/i) {
426 $operator = $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL;
427 } elsif ($operator =~ /^in$/i) {
428 $operator = $CQPerlExt::CQ_COMP_OP_IN;
429 } elsif ($operator =~ /^not\s+in$/) {
430 $operator = $CQPerlExt::CQ_COMP_OP_NOT_IN;
432 $self->_setError ("I can't understand the operator $operator");
439 # TODO: How to handle more complicated $condition....
440 $self->_setError ("I can't understand the conditional expression "
448 # Trim quotes if any:
449 if ($value =~ /^\s*\'/) {
450 $value =~ s/^\s*\'//;
451 $value =~ s/\'\s*$//;
452 } elsif ($value =~ /^\s*\"/) {
453 $value =~ s/^\s*\"//;
454 $value =~ s/\"\s*$//;
457 # Trim leading and trailing whitespace
461 return ($field, $operator, $value);
464 sub _parseConditional($$;$);
465 sub _parseConditional($$;$) {
466 my ($self, $query, $condition, $filterOperator) = @_;
468 return if $condition eq '';
470 my ($field, $operator, $value);
472 if ($condition =~ /(.+?)\s+(and|or)\s+(.+)/i) {
474 my $conjunction = lc $2;
477 if ($conjunction eq 'and') {
478 unless ($filterOperator) {
479 $filterOperator = $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND);
481 $filterOperator = $filterOperator->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND);
483 } elsif ($conjunction eq 'or') {
484 unless ($filterOperator) {
485 $filterOperator = $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_OR);
487 $filterOperator = $filterOperator->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_OR);
491 $self->_setCondition ($self->_parseCondition ($leftSide), $filterOperator);
493 $self->_parseConditional ($query, $rightSide, $filterOperator);
495 unless ($condition =~ $operatorRE) {
496 $self->_setError ("Unable to parse condition \"$condition\"");
501 $filterOperator = $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND)
502 unless $filterOperator;
504 $self->_setCondition ($self->_parseCondition ($condition), $filterOperator);
507 # Actually clear error...
511 } # _parseConditional
513 sub _setCondition($$$) {
514 my ($self, $field, $operator, $value, $filterOperator) = @_;
516 return unless $operator;
518 if ($operator == $CQPerlExt::CQ_COMP_OP_IS_NULL or
519 $operator == $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL) {
520 eval {$filterOperator->BuildFilter ($field, $operator, [()])};
523 $self->_setError ($@);
528 # If the operator is one of the operators that have mulitple values then we
529 # need to make an array of $value
530 if ($operator == $CQPerlExt::CQ_COMP_OP_BETWEEN or
531 $operator == $CQPerlExt::CQ_COMP_OP_NOT_BETWEEN or
532 $operator == $CQPerlExt::CQ_COMP_OP_IN or
533 $operator == $CQPerlExt::CQ_COMP_OP_NOT_IN) {
534 my @values = split /,\s*/, $value;
536 eval {$filterOperator->BuildFilter ($field, $operator, \@values)};
539 $self->_setError ($@);
544 eval {$filterOperator->BuildFilter ($field, $operator, [$value])};
547 $self->_setError ($@);
558 my ($self, $table, @fields) = @_;
562 eval {$entityDef = $self->{session}->GetEntityDef ($table)};
565 $self->_setError ($@, -1);
571 for (@{$entityDef->GetFieldDefNames}) {
572 unless ($self->{returnSystemFields}) {
573 next if $entityDef->IsSystemOwnedFieldDefName ($_);
581 push @fields, 'dbid' unless grep {$_ eq 'dbid'} @fields;
587 my ($self, $errmsg, $error) = @_;
591 if ($errmsg and $errmsg ne '') {
594 $self->{errmsg} = $errmsg;
596 $self->{errmsg} = '';
599 $self->error ($error);
604 sub _setFieldValue($$$$) {
605 my ($self, $entity, $table, $fieldName, $fieldValue) = @_;
609 my $entityDef = $self->{session}->GetEntityDef ($table);
611 return $errmsg if $entityDef->IsSystemOwnedFieldDefName ($fieldName);
613 unless (ref $fieldValue eq 'ARRAY') {
614 # This is one of those rare instances where it is important to surround a
615 # bare variable with double quotes otherwise the CQ API will wrongly
616 # evaluate $fieldValue if $fieldValue is a simple number (e.g. 0, 1, etc.)
617 $errmsg = $entity->SetFieldValue ($fieldName, "$fieldValue") if $fieldValue;
620 $errmsg = $entity->AddFieldValue ($fieldName, $_);
622 return $errmsg unless $errmsg eq '';
629 sub _UTC2Localtime($) {
630 my ($utcdatetime) = @_;
632 return unless $utcdatetime;
634 # If the field does not look like a UTC time then just return it.
635 return $utcdatetime unless $utcdatetime =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/;
637 $utcdatetime =~ s/T/ /;
638 $utcdatetime =~ s/Z//;
640 my @localtime = localtime;
642 return _epochToDate (
643 _dateToEpoch ($utcdatetime) + (timegm (@localtime) - timelocal (@localtime))
648 my ($self, $table, $values, @ordering) = @_;
654 Insert a new record into the database
658 =for html <blockquote>
664 The name of the table to insert into
668 Hash reference of name/value pairs for the insertion
672 Array containing field names that need to be processed in order. Not all fields
673 mentioned in the $values hash need be mentioned here. If you have fields that
674 must be set in a particular order you can mention them here. So, if you're
675 adding the Defect record, but you need Project set before Platform, you need
676 only pass in an @ordering of qw(Project Platform). They will be done first, then
677 all of the rest of the fields in the $values hash. If you have no ordering
678 dependencies then you can simply omit @ordering.
680 Note that the best way to determine if you have an ordering dependency try using
681 a Clearquest client and note the order that you set fields in. If at anytime
682 setting one field negates another field via action hook code then you have just
683 figured out that this field needs to be set before the file that just got
688 =for html </blockquote>
692 =for html <blockquote>
698 The DBID of the newly added record or undef if error.
702 =for html </blockquote>
706 $self->{errmsg} = '';
708 unless ($self->connected) {
709 $self->_setError ('You must connect to Clearquest before you can call add');
714 my %values = %$values;
717 eval {$entity = $self->{session}->BuildEntity ($table)};
720 $self->_setError ("Unable to create new $table record:\n$@");
725 # First process all fields in @ordering, if specified
728 $self->{errmsg} = $self->_setFieldValue ($entity, $table, $_, $values{$_});
730 $self->_setError ("$_ from the ordering array is not present in the value hash", -1);
733 last unless $self->{errmsg} eq '';
736 return unless $self->{errmsg} eq '';
738 # Now process the rest of the values
739 for my $fieldName (keys %values) {
740 next if grep {$fieldName eq $_} @ordering;
742 $self->{errmsg} = $self->_setFieldValue ($entity, $table, $fieldName, $values{$fieldName});
744 last unless $self->{errmsg} eq '';
747 $self->_setError ($self->{errmsg});
749 return unless $self->{errmsg} eq '';
751 $self->{errmsg} = $self->_commitRecord ($entity);
752 $self->{error} = $self->{errmsg} eq '' ? 0 : 1;
754 my $dbid = $entity->GetFieldValue ('dbid')->GetValue;
760 my ($self, $username, $password, $database, $dbset) = @_;
764 =head2 connect (;$$$$)
766 Connect to the Clearquest database. You can supply parameters such as username,
767 password, etc and they will override any passed to Clearquest::new (or those
768 coming from ../etc/cq.conf)
772 =for html <blockquote>
778 Username to use to connect to the database
782 Password to use to connect to the database
786 Clearquest database to connect to
790 Database set to connect to (Default: Connect to the default dbset)
794 =for html </blockquote>
798 =for html <blockquote>
806 =for html </blockquote>
810 return unless $self->{module} eq 'api';
812 eval {require CQPerlExt};
814 croak "Unable to use Rational's CQPerlExt library - "
815 . "You must use cqperl to use the Clearquest API back end\n$@" if $@;
817 $self->{username} = $username if $username;
818 $self->{password} = $password if $password;
819 $self->{database} = $database if $database;
820 $self->{dbset} = $dbset if $dbset;
822 $self->{session} = CQSession::Build ();
824 $self->{loggedin} = 0;
827 $self->{session}->UserLogon ($self->{username},
836 $self->_setError ($@, 1);
838 $self->{loggedin} = 1;
840 $self->_setError ($_, 0);
843 return $self->{loggedin};
853 Returns 1 if we are currently connected to Clearquest
857 =for html <blockquote>
865 =for html </blockquote>
869 =for html <blockquote>
873 =item 1 if logged in - 0 if not
877 =for html </blockquote>
881 return $self->{loggedin};
885 my ($self, $fullyQualify) = @_;
891 Returns a connection string that describes the current connection
895 =for html <blockquote>
901 If true the connection string will be fully qualified
905 =for html </blockquote>
909 =for html <blockquote>
915 A string describing the current connection. Generally
916 <username>@<database>[/<dbset>]. Note that <dbset> is only displayed if it is
917 not the default DBSet as defined in cq.conf.
921 =for html </blockquote>
925 my $connectionStr = $self->username ()
927 . $self->database ();
930 $connectionStr .= '/' . $self->dbset;
932 $connectionStr .= '/' . $self->dbset () unless $self->dbset eq $DEFAULT_DBSET;
935 return $connectionStr;
939 my ($self, $msg, $die, $log) = @_;
943 =head2 checkErr (;$$)
945 Checks for error in the last Clearquest method call and prints error to STDERR.
946 Optionally prints a user message if $msg is specified. Dies if $die is true
950 =for html <blockquote>
960 Causes caller to croak if set to true
964 =for html </blockquote>
968 =for html <blockquote>
974 Returns 0 for no error, non-zero if error.
978 =for html </blockquote>
984 if ($self->{error}) {
986 $msg .= "\n" . $self->errmsg . "\n";
988 $msg = $self->errmsg . "\n";
992 $log->err ($msg) if $log;
998 print STDERR "$msg\n";
1001 return $self->{error};
1015 Returns the current database (or the database that would be used)
1019 =for html <blockquote>
1027 =for html </blockquote>
1031 =for html <blockquote>
1039 =for html </blockquote>
1043 return $self->{database};
1053 Returns the current dbset (or the dbset that would be used)
1057 =for html <blockquote>
1065 =for html </blockquote>
1069 =for html <blockquote>
1077 =for html </blockquote>
1081 return $self->{dbset};
1091 Return the installed DBSets for this schema
1095 =for html <blockquote>
1103 =for html </blockquote>
1107 =for html <blockquote>
1117 =for html </blockquote>
1121 unless ($self->connected) {
1122 $self->_setError ('You must connect to Clearquest before you can call DBSets', '-1');
1127 return @{$self->{session}->GetInstalledDbSets};
1131 my ($self, $table, $key) = @_;
1137 Deletes records from the database
1141 =for html <blockquote>
1147 Table to delete records from
1151 Key of the record to delete
1155 =for html </blockquote>
1159 =for html <blockquote>
1165 Error message or blank if no error
1169 =for html </blockquote>
1175 eval {$entity = $self->{session}->GetEntity ($table, $key)};
1178 $self->_setError ($@, 1);
1183 eval {$self->{session}->DeleteEntity ($entity, 'Delete')};
1186 $self->_setError ($@, 1);
1197 CQSession::Unbuild ($self->{session}) if $self->{session};
1207 =head2 disconnect ()
1209 Disconnect from Clearquest
1213 =for html <blockquote>
1221 =for html </blockquote>
1225 =for html <blockquote>
1233 =for html </blockquote>
1237 CQSession::Unbuild ($self->{session});
1239 undef $self->{session};
1241 $self->{loggedin} = 0;
1247 my ($self, $errmsg) = @_;
1253 Returns the last error message. Optionally sets the error message if specified.
1257 =for html <blockquote>
1265 =for html </blockquote>
1269 =for html <blockquote>
1279 =for html </blockquote>
1283 $self->{errmsg} = $errmsg if $errmsg;
1285 return $self->{errmsg};
1289 my ($self, $error) = @_;
1293 =head2 error ($error)
1295 Returns the last error number. Optional set the error number if specified
1299 =for html <blockquote>
1309 =for html </blockquote>
1313 =for html <blockquote>
1323 =for html </blockquote>
1327 # Watch here as $error can very well be 0 which "if $error" would evaluate
1328 # to false leaving $self->{error} undefined
1329 $self->{error} = $error if defined $error;
1331 return $self->{error};
1335 my ($self, $table, $fieldName) = @_;
1339 =head2 fieldType ($table, $fieldname)
1341 Returns the field type for the $table, $fieldname combination.
1345 =for html <blockquote>
1351 Table to return field type from.
1355 Fieldname to return the field type from.
1359 =for html </blockquote>
1363 =for html <blockquote>
1373 =for html </blockquote>
1377 return $UNKNOWN unless $self->{loggedin};
1379 # If we've already computed the fieldTypes for the fields in this table then
1381 if ($FIELDS{$table}) {
1382 # If we already have this fieldType just return it
1383 if (defined $FIELDS{$table}{$fieldName}) {
1384 return $FIELDS{$table}{$fieldName}
1390 my $entityDef = $self->{session}->GetEntityDef ($table);
1392 for (@{$entityDef->GetFieldDefNames}) {
1393 $FIELDS{$table}{$_} = $entityDef->GetFieldDefType ($_);
1396 if (defined $FIELDS{$table}{$fieldName}) {
1397 return $FIELDS{$table}{$fieldName}
1403 sub fieldTypeName($$) {
1404 my ($self, $table, $fieldName) = @_;
1408 =head2 fieldTypeName ($table, $fieldname)
1410 Returns the field type name for the $table, $fieldname combination.
1414 =for html <blockquote>
1420 Table to return field type from.
1424 Fieldname to return the field type from.
1428 =for html </blockquote>
1432 =for html <blockquote>
1436 =item $fieldTypeName
1442 =for html </blockquote>
1446 my $fieldType = $self->fieldType ($table, $fieldName);
1448 return $UNKNOWN unless $fieldType;
1450 if ($fieldType == $STRING) {
1452 } elsif ($fieldType == $MULTILINE_STRING) {
1453 return "MULTILINE_STRING";
1454 } elsif ($fieldType == $INT) {
1456 } elsif ($fieldType == $DATE_TIME) {
1458 } elsif ($fieldType == $REFERENCE) {
1460 } elsif ($fieldType == $REFERENCE_LIST) {
1461 return "REFERENCE_LIST";
1462 } elsif ($fieldType == $ATTACHMENT_LIST) {
1463 return "ATTACHMENT_LIST";
1464 } elsif ($fieldType == $ID) {
1466 } elsif ($fieldType == $STATE) {
1468 } elsif ($fieldType == $JOURNAL) {
1470 } elsif ($fieldType == $DBID) {
1472 } elsif ($fieldType == $STATETYPE) {
1474 } elsif ($fieldType == $RECORD_TYPE) {
1475 return "RECORD_TYPE";
1476 } elsif ($fieldType == $UNKNOWN) {
1482 my ($self, $table, $condition, @fields) = @_;
1488 Find records in $table. You can specify a $condition and which fields you wish
1489 to retrieve. Specifying a smaller set of fields means less data transfered and
1490 quicker retrieval so only retrieve the fields you really need.
1494 =for html <blockquote>
1500 Name of the table to search
1504 Condition to use. If you want all records then pass in undef. Only simple
1505 conditions are supported. You can specify compound conditions (e.g. field1 ==
1506 'foo' and field1 == 'bar' or field2 is not null). No parenthesizing is
1509 The following conditionals are supported
1515 =item Not Equal (!=|<>)
1519 =item Greater than (>)
1521 =item Less than or equal (<=)
1523 =item Greater than or equal (>=)
1535 Note that "is not null" is currently not working in the REST module (it works
1536 in the api and thus also in the client/server model). This because the
1537 OLSC spec V1.0 does not support it.
1539 As for "Like"", you'll need to specify "<fieldname> like '%var%'" for the
1542 "In" is only available in the REST interface as that's what OLSC supports. It's
1543 syntax would be "<fieldname> In 'value1', 'value2', 'value3'..."
1545 Also conditions can be combined with (and|or) so in the api you could do "in"
1546 as "<fieldname> = 'value1 or <fieldname> = 'value2" or <fieldname> = 'value3'".
1548 Complicated expressions with parenthesis like "(Project = 'Athena' or Project =
1549 'Hawaii') and Category = 'Aspen'" are not supported.
1553 An array of fieldnames to retrieve
1557 =for html </blockquote>
1561 =for html <blockquote>
1565 =item $result or ($result, $nbrRecs)
1567 Internal structure to be used with getNext. If in an array context then $nbrRecs
1572 =for html </blockquote>
1578 unless ($self->connected) {
1579 $self->_setError ('You must connect to Clearquest before you can call find', '-1');
1586 eval {$entityDef = $self->{session}->GetEntityDef ($table)};
1589 $self->_setError ($@, -1);
1594 @fields = $self->_setFields ($table, @fields);
1596 return unless @fields;
1598 my $query = $self->{session}->BuildQuery ($table);
1601 eval {$query->BuildField ($_)};
1604 $self->_setError ($@);
1610 $self->_parseConditional ($query, $condition);
1612 return if $self->error;
1614 my $result = $self->{session}->BuildResultSet ($query);
1615 my $nbrRecs = $result->ExecuteAndCountRecords;
1624 return (\%resultSet, $nbrRecs);
1637 Given a $str or a reference to an array of strings, this function returns a list
1638 of Clearquest IDs found in the $str. If called in a scalar context this function
1639 returns a comma separated string of IDs found. Note that duplicate IDs are
1640 eliminated. Also, the lists of IDs may refer to different Clearquest databases.
1644 =for html <blockquote>
1650 String or reference to an array of strings to search
1654 =for html </blockquote>
1658 =for html <blockquote>
1662 =item @IDs or $strIDs
1664 Either an array of CQ IDs or a comma separated list of CQ IDs.
1668 =for html </blockquote>
1672 $str = join ' ', @$str if ref $str eq 'ARRAY';
1674 my @IDs = $str =~ /([A-Za-z]\w{1,4}\d{8})/gs;
1678 map { $IDs{$_} = 1; } @IDs;
1683 return join ',', keys %IDs;
1688 my ($self, $table, $id, @fields) = @_;
1694 Return a record that you have the id or key of.
1698 =for html <blockquote>
1704 The $table to get the record from
1708 The $id or key to use to retrieve the record
1712 =for html </blockquote>
1716 =for html <blockquote>
1722 Hash of name/value pairs for all the fields in $table
1726 =for html </blockquote>
1730 unless ($self->connected) {
1731 $self->_setError ('You must connect to Clearquest before you can call get', '-1');
1736 @fields = $self->_setFields ($table, @fields);
1738 return unless @fields;
1742 eval {$entity = $self->{session}->GetEntity ($table, $id)};
1745 $self->_setError ($@);
1753 my $fieldType = $entity->GetFieldValue ($_)->GetType;
1755 if ($fieldType == $CQPerlExt::CQ_REFERENCE_LIST) {
1756 $record{$_} = $entity->GetFieldValue ($_)->GetValueAsList;
1758 $record{$_} = $entity->GetFieldValue ($_)->GetValue;
1759 $record{$_} ||= '' if $self->{emptyStringForUndef};
1762 if ($fieldType == $CQPerlExt::CQ_DATE_TIME) {
1763 $record{$_} = _UTC2Localtime ($record{$_});
1774 my ($self, $table, $dbid, @fields) = @_;
1778 =head2 getDBID ($$;@)
1780 Return a record that you have the dbid
1784 =for html <blockquote>
1790 The $table to get the record from
1794 The $dbid to use to retrieve the record
1798 Array of field names to retrieve (Default: All fields)
1800 Note: Avoid getting all fields for large records. It will be slow and bloat your
1801 script's memory usage.
1805 =for html </blockquote>
1809 =for html <blockquote>
1815 Hash of name/value pairs for all the fields in $table
1819 =for html </blockquote>
1823 unless ($self->connected) {
1824 $self->_setError ('You must connect to Clearquest before you can call getDBID', '-1');
1829 @fields = $self->_setFields ($table, @fields);
1833 eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)};
1836 $self->_setError ($@);
1844 my $fieldType = $entity->GetFieldValue ($_)->GetType;
1846 if ($fieldType == $CQPerlExt::CQ_REFERENCE_LIST) {
1847 $record{$_} = $entity->GetFieldValue ($_)->GetValueAsList;
1849 $record{$_} = $entity->GetFieldValue ($_)->GetValue;
1850 $record{$_} ||= '' if $self->{emptyStringForUndef};
1853 if ($fieldType == $CQPerlExt::CQ_DATE_TIME) {
1854 $record{$_} = _UTC2Localtime ($record{$_});
1864 sub getDynamicList($) {
1865 my ($self, $list) = @_;
1869 =head2 getDynamicList ($)
1871 Return the entries of a dynamic list
1875 =for html <blockquote>
1881 The name of the dynamic list
1885 =for html </blockquote>
1889 =for html <blockquote>
1895 An array of entries from the dynamic list
1899 =for html </blockquote>
1903 return () unless $self->connected;
1905 return @{$self->{session}->GetListMembers ($list)};
1909 my ($self, $result) = @_;
1915 Return the next record that qualifies from a preceeding call to the find method.
1919 =for html <blockquote>
1925 The $result returned from find.
1929 =for html </blockquote>
1933 =for html <blockquote>
1939 Hash of name/value pairs for the @fields specified to find.
1943 =for html </blockquote>
1947 unless ($self->connected) {
1948 $self->_setError ('You must connect to Clearquest before you can call getNext', '-1');
1953 # Here we need to do special processing to gather up reference list fields, if
1954 # any. If we have a reference list field in the field list then Clearquest
1955 # returns multiple records - one for each entry in the reference list. Thus if
1956 # you were getting say the key field of a record and a reference list field like
1957 # say Projects, you might see:
1959 # Key Value Projects
1960 # --------- --------
1965 # Things get combinatoric when multiple reference list fields are involved. Our
1966 # strategy here is to keep gathering all fields that change into arrays assuming
1967 # they are reference fields as long as the dbid field has not changed.
1971 unless ($result->{lastDBID}) {
1972 # Move to the first record
1973 last unless $result->{result}->MoveNext == $CQPerlExt::CQ_SUCCESS;
1974 } elsif ($result->{lastDBID} == $result->{thisDBID}) {
1975 # If the dbid is the same then we have at least one reference list field
1976 # in the request so we need to move to the next record
1977 last unless $result->{result}->MoveNext == $CQPerlExt::CQ_SUCCESS;
1979 # If lastDBID != thisDBID then set lastDBID to thisDBID so we can process
1981 $result->{lastDBID} = $result->{thisDBID};
1983 delete $result->{lastRecord};
1986 my $nbrColumns = $result->{result}->GetNumberOfColumns;
1991 while ($column <= $nbrColumns) {
1992 my $name = $result->{result}->GetColumnLabel($column);
1993 my $value = $result->{result}->GetColumnValue($column++);
1995 # Fix any UTC dates - _UTC2Localtime will only modify data if the data
1996 # matches a UTC datetime.
1997 $value = _UTC2Localtime ($value) if $value;
1999 $value ||= '' if $self->{emptyStringForUndef};
2001 $record{$name} = $value;
2004 %{$result->{lastRecord}} = %record unless $result->{lastRecord};
2006 # Store this record's DBID
2007 $result->{thisDBID} = $record{dbid};
2009 if ($result->{lastDBID}) {
2010 if ($result->{thisDBID} == $result->{lastDBID}) {
2011 # Since the dbid's are the same, we have at least one reference list field
2012 # and we need to compare all fields
2013 for my $field (keys %record) {
2014 # If the field is blank then skip it
2015 next if $record{$field} eq '';
2017 # Here we check the field in %lastRecord to see if it was a reference
2018 # list with more than one entry.
2019 if (ref \$result->{lastRecord}{$field} eq 'ARRAY') {
2020 # Check to see if this entry is already in the list of current entries
2021 next if grep {/^$record{$field}$/} @{$result->{lastRecord}{$field}};
2024 # This checks to see if the current field is a scalar and we have a new
2025 # value, then the scalar needs to be changed to an array
2026 if (ref \$result->{lastRecord}{$field} eq 'SCALAR') {
2027 # If the field is the same value then no change, no array. We do next
2028 # to start processing the next field
2029 next if $result->{lastRecord}{$field} eq $record{$field};
2031 # Changed $lastRecord{$_} to a reference to an ARRAY
2032 $result->{lastRecord}{$field} = [$result->{lastRecord}{$field}, $record{$field}];
2034 # Push the value only if it does not already exists in the array
2035 push @{$result->{lastRecord}{$field}}, $record{$field}
2036 unless grep {/^$record{$field}$/} @{$result->{lastRecord}{$field}};
2040 # Transfer %lastRecord -> %record
2041 %record = %{$result->{lastRecord}};
2043 %record = %{$result->{lastRecord}};
2049 # The $lastDBID is now $thisDBID
2050 $result->{lastDBID} = $result->{thisDBID};
2052 # Update %lastRecord
2053 %{$result->{lastRecord}} = %record;
2058 # Never return dbid...
2059 delete $record{dbid};
2071 This function returns the database name given an ID.
2075 =for html <blockquote>
2081 The ID to extract the database name from
2085 =for html </blockquote>
2089 =for html <blockquote>
2095 Returns the name of the database the ID is part of or undef if not found.
2099 =for html </blockquote>
2103 if ($ID =~ /([A-Za-z]\w{1,4})\d{8}/) {
2111 my ($self, $table, $dbid) = @_;
2117 Return the key of the record given a $dbid
2121 =for html <blockquote>
2127 Name of the table to lookup
2131 Database ID of the record to retrieve
2135 =for html </blockquote>
2139 =for html <blockquote>
2147 =for html </blockquote>
2151 unless ($self->connected) {
2152 $self->_setError ('You must connect to Clearquest before you can call key', '-1');
2159 eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)};
2161 return $entity->GetDisplayName;
2164 sub modify($$$$;@) {
2165 my ($self, $table, $key, $action, $values, @ordering) = @_;
2169 =head2 modify ($$$$;@)
2175 =for html <blockquote>
2181 The $table to get the record from
2185 The $key identifying the record to modify
2189 Action to perform the modification under. Default is 'Modify'.
2193 Hash reference containing name/value that have the new values for the fields
2197 Array containing field names that need to be processed in order. Not all fields
2198 mentioned in the $values hash need be mentioned here. If you have fields that
2199 must be set in a particular order you can mention them here. So, if you're
2200 modifying the Defect record, but you need Project set before Platform, you need
2201 only pass in an @ordering of qw(Project Platform). They will be done first, then
2202 all of the rest of the fields in the $values hash. If you have no ordering
2203 dependencies then you can simply omit @ordering.
2205 Note that the best way to determine if you have an ordering dependency try using
2206 a Clearquest client and note the order that you set fields in. If at anytime
2207 setting one field negates another field via action hook code then you have just
2208 figured out that this field needs to be set before the file that just got
2213 =for html </blockquote>
2217 =for html <blockquote>
2223 The $errmsg, if any, when performing the update (empty string for success)
2227 =for html </blockquote>
2231 unless ($self->connected) {
2232 $self->_setError ('You must connect to Clearquest before you can call modify', '-1');
2234 return $self->{errmsg};
2237 my %record = $self->get ($table, $key, qw(dbid));
2239 return $self->modifyDBID ($table, $record{dbid}, $action, $values, @ordering);
2242 sub modifyDBID($$$$;@) {
2243 my ($self, $table, $dbid, $action, $values, @ordering) = @_;
2247 =head2 modifyDBID ($$$%)
2249 Update a unique record (by DBID)
2253 =for html <blockquote>
2259 The $table to get the record from
2263 The $dbid of the record to update. Note that the find method always includes the
2264 dbid of a record in the hash that it returns.
2268 Action to perform the modification under. Default is 'Modify'.
2272 Hash containing name/value that have the new values for the fields
2276 =for html </blockquote>
2280 =for html <blockquote>
2286 The $errmsg, if any, when performing the update (empty string for success)
2290 =for html </blockquote>
2294 $action ||= 'Modify';
2298 %values = %$values if $values;
2302 eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)};
2305 $self->_setError ($@);
2310 eval {$entity->EditEntity ($action)};
2313 $self->_setError ($@);
2318 # First process all fields in @ordering, if specified
2321 $self->{errmsg} = $self->_setFieldValue ($table, $_, $values{$_});
2323 $self->_setError ("$_ from the ordering array is not present in the value hash", -1);
2326 last unless $self->{errmsg} eq '';
2329 return $self->{errmsg} unless $self->{errmsg} eq '';
2331 # Now process the rest of the values
2332 for my $fieldName (keys %values) {
2333 next if grep {$fieldName eq $_} @ordering;
2335 $self->{errmsg} = $self->_setFieldValue ($entity, $table, $fieldName, $values{$fieldName});
2337 last unless $self->{errmsg} eq '';
2340 $self->_setError ($self->{errmsg});
2342 return $self->{errmsg} unless $self->{errmsg} eq '';
2344 $self->{errmsg} = $self->_commitRecord ($entity);
2345 $self->{error} = $self->{errmsg} eq '' ? 0 : 1;
2347 return $self->{errmsg};
2357 Returns the current back end module we are using
2361 =for html <blockquote>
2369 =for html </blockquote>
2373 =for html <blockquote>
2381 =for html </blockquote>
2385 return $self->{module};
2389 my ($class, %parms) = @_;
2395 Construct a new Clearquest object.
2399 Below are the key values for the %parms hash.
2401 =for html <blockquote>
2407 Webhost for REST module
2411 Username to use to connect to the database
2415 Password to use to connect to the database
2419 Clearquest database to connect to
2423 Database set to connect to
2427 One of 'rest', 'api' or 'client' (Default: From cq.conf). This determines which
2428 backend module will be used.
2432 =for html </blockquote>
2436 =for html <blockquote>
2440 =item Clearquest object
2444 =for html </blockquote>
2448 $parms{CQ_DATABASE} ||= $OPTS{CQ_DATABASE};
2449 $parms{CQ_USERNAME} ||= $OPTS{CQ_USERNAME};
2450 $parms{CQ_PASSWORD} ||= $OPTS{CQ_PASSWORD};
2451 $parms{CQ_DBSET} ||= $OPTS{CQ_DBSET};
2454 server => $parms{CQ_SERVER},
2455 port => $parms{CQ_PORT},
2456 database => $parms{CQ_DATABASE},
2457 dbset => $parms{CQ_DBSET},
2458 username => $parms{CQ_USERNAME},
2459 password => $parms{CQ_PASSWORD},
2460 emptyStringForUndef => 0,
2461 returnSystemFields => 0,
2464 my $module = delete $parms{CQ_MODULE};
2466 $module ||= $OPTS{CQ_MODULE};
2468 $module = lc $module;
2470 if ($module eq 'rest') {
2471 require Clearquest::REST;
2473 $self->{webhost} = $parms{CQ_WEBHOST} || $OPTS{CQ_WEBHOST};
2475 $self = Clearquest::REST->new ($self);
2476 } elsif ($module eq 'client') {
2477 require Clearquest::Client;
2479 $self->{server} = $parms{CQ_SERVER} || $OPTS{CQ_SERVER};
2480 $self->{port} = $parms{CQ_PORT} || $OPTS{CQ_PORT};
2482 $self = Clearquest::Client->new ($self);
2483 } elsif ($module ne 'api') {
2484 croak "Unknown interface requested - $module";
2487 $self->{module} = $module;
2489 # Save reference to instaniated instance of this object to insure that global
2490 # variables are properly disposed of
2491 push @objects, $self;
2503 Returns the current server if applicable
2507 =for html <blockquote>
2515 =for html </blockquote>
2519 =for html <blockquote>
2525 For api this will return ''. For REST and client/server this will return the
2526 server name that we are talking to.
2530 =for html </blockquote>
2534 return $self->{server};
2538 my ($self, %opts) = @_;
2544 Set options for operating
2548 =for html <blockquote>
2556 Options to set. The only options currently supported are emptyStringForUndef
2557 and returnSystemFields. If set emptyStringForUndef will return empty strings for
2558 empty fields instead of undef. Default: Empty fields are represented with undef.
2560 System-owned fields are used internally by IBM Rational ClearQuest to maintain
2561 information about the database. You should never modify system fields directly
2562 as it could corrupt the database. If returnSystemFields is set then system
2563 fields will be returned. Default: System fields will not be returned unless
2564 explicitly stated in the @fields parameter. This means that if you do not
2565 specify any fields in @fields, all fields will be returned except system fields,
2566 unless you set returnSystemFields via this method or you explicitly mention the
2567 system field in your @fields parameter.
2569 =for html </blockquote>
2573 =for html <blockquote>
2581 =for html </blockquote>
2585 $self->{emptyStringForUndef} = $opts{emptyStringForUndef}
2586 if $opts{emptyStringForUndef};
2587 $self->{returnSystemFields} = $opts{returnSystemFields}
2588 if $opts{returnSystemFields};
2594 my ($self, $option) = @_;
2604 =for html <blockquote>
2612 Option to retrieve. If non-existant then undef is returned.
2614 =for html </blockquote>
2618 =for html <blockquote>
2622 =item $option or undef if option doesn't exist
2626 =for html </blockquote>
2630 my @validOpts = qw (emptyStringForUndef returnSystemFields);
2632 if (grep {$option eq $_} @validOpts) {
2633 return $self->{$option};
2646 Returns the current username (or the username that would be used)
2650 =for html <blockquote>
2658 =for html </blockquote>
2662 =for html <blockquote>
2670 =for html </blockquote>
2674 return $self->{username};
2680 return $self->{webhost};
2691 L<File::Basename|File::Basename>
2693 =head2 ClearSCM Perl Modules
2695 =for html <p><a href="/php/scm_man.php?file=lib/GetConfig.pm">GetConfig</a></p>
2697 =head1 BUGS AND LIMITATIONS
2699 There are no known bugs in this module
2701 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
2703 =head1 LICENSE AND COPYRIGHT
2705 Copyright (c) 2007, ClearSCM, Inc. All rights reserved.