DEBUGGING DATAWINDOWS

Transcription

DEBUGGING DATAWINDOWS
DEBUGGING DATAWINDOWS
U.S. $14.00 (CANADA $15.00)
PowerBuilderJournal.com
Enterprise Application Studio
February 2000 - Volume: 7 Issue: 2
www.PowerBuilderJournal.com
Announcing...
Coming
June 25–28, 2000
September 24–27, 2000
From the Editor
PowerDesigner: Neck
and Neck with the
Competition
by John Olson pg. 3
Guest Editorial
Fat Client Is Not Dead
by Richard Brooks pg. 5
Focus Story: Java vs PowerScript
Kouros Gorgani
Part 2: How to leverage your knowledge of PowerScript to quickly
learn and build Java programs
DataWindow Magic:
Concepts in Graphing
Richard Brooks
How to create a simple graph
From Sybase
Extending
PowerBuilder
Applications to
the Web
by Rob Veitch pg. 27
PBDJ News
by Bruce Armstrong pg. 33
10
Distributed Technologies: Handling
Aborted Transactions in Jaguar
Michael Barlotta
A technique that works with any Jaguar client or component
SYS-CON
PUBLICATIONS
18
Feature: Beyond Showplan
Brian Davignon
The next step in Sybase query tuning
22
PFC Corner: Debugging
DataWindows–Live!
Vince Fabro
Useful enhancements to the DW properties dialog
Slick Tricks
by Bernie Metzger pg. 38
6
Introduction to PowerBuilder:
Using the PFC Multitable
Update Service
28
USER 1
USER 2
Update orders
Set ship_name =’Foy’
Where order_id = 100 and
ship_name = ’Bates’
Update orders
Set ship_city
ship_city =’Wayne’
Where order_id = 100 and
ship_city = ’Boogers Holler’
ship_city
Foy
Wayne
order_id
100
How one of the least used PFC services works
ship_name
Bates
Bob Hendry
ship_city
ship_cit
Boogers Holler
34
Dutton
Software
www.duttonsoftware.com
2
PBDJ volume7 issue2
www.PowerBuilderJournal.com
FROM THE EDITOR
JOHN OLSON, EDITOR-IN-CHIEF
PowerDesigner:Neck and Neck with the Competition
hough PowerDesigner 7 was released a few months ago, not many
people are aware that it’s much more feature-rich than past versions. Sybase has been pushing hard to get the word out that PD7 is
a great product, but it’s getting only mediocre media coverage. We
haven’t even mentioned it yet in this magazine, so I’m going to focus on
it now. First, a little history…
Back in 1994, Powersoft was looking to broaden its toolset by adding
relational design capabilities, a database and various other components needed for full-cycle development. Rather than taking years to
build them from the ground up, they chose to find and purchase existing products and integrate them with PowerBuilder. A well-advertised
acquisition was Watcom of Waterloo, Ontario. The company had one of
the best stables of C/C++ developers in the world and some products,
Watcom C and Watcom SQL, that were best in their fields. Of course,
their Watcom SQL database has evolved and is now called Adaptive
Server Anywhere, and it’s still the best departmental-size and mobile
computing database available. The relational design tool that met their
expectations for quality and features was SDesignor by SDP, a French
company. They purchased SDP and quickly started offering SDesignor
in a bundle with PowerBuilder and Watcom SQL. Soon after, Sybase
purchased Powersoft and all the Powersoft products became part of a
much larger collection of front-end and back-end tools. As the Sybase
toolset evolved and became more integrated, SDesignor was renamed
PowerDesigner.
Though SDesignor was a good product, by the time Powersoft started promoting it several other design tools, led by Computer Associates’
ERWin, had already won the hearts (and market) of PowerBuilder
developers. Initially, SDesignor was lagging a little in features but has
slowly caught up in both features and acceptance. However, Sybase
hasn’t promoted it much outside of its customer base. As a result, it’s
virtually unknown to developers who don’t use Sybase development
tools.
Today, PowerDesigner is an entirely different tool than it was in version 6. The French development team rewrote the entire program and
greatly enhanced its feature set. It’s no longer just a very good relational design tool, but is now also a very good object-design tool. The huge
changes resulted in a bit of a rocky start to the PD7 release. However,
version 7.01 is now available and the bugs common to first-version
products have been taken care of. The Sybase development and support teams are proud of this product, and for good reason. It can now
compete head to head with ERwin for relational design and Rational
Software’s Rose for object design. Hopefully it won’t be marketed just to
the PowerBuilder industry but will also find acceptance in the general
software development industry.
In addition to features you’d expect to find in every relational and
object modeling tool, here are just a few of the exceptional features
PowerDesigner 7 has.
T
Relational Design (many of these were also in PD6):
• Conceptual modeling: Physical modeling is a staple of relational
tools but not all offer conceptual modeling. It allows for designs to be
database independent and leads to the next great feature.
• Support for multiple databases from a single model: From a single
conceptual model PD can build DDL specific to several targets
including Oracle, MS-SQL Server, ASE, ASA, DB2 and more.
• Powerful reengineering capabilities: The tool not only builds databases for many targets but it can also reengineer physical models
from many database types. From the physical model it can create a
conceptual model, then build a physical model for a new target,
automatically performing data type conversions. It effectively
removes most of the work of porting a database from one type to
another.
• Embedded entities: With PB7 you can embed an entity from an
entirely different project (CDM or PDM file) into your current project. The link is persistent so changes to the entity in the original
model are promulgated to the linked model.
• Schema difference reporting: Databases, schema exports and physical models can be compared for differences – a feature ERwin has
had for several years. It’s nice to finally have it in PowerDesigner.
Object Design:
• Java support: Not only does PD7 support PowerBuilder, and more
specifically PFC, for code generation and reengineering, but it
also supports Java. PD7 will generate Java classes and reengineer
Java.
• Code reengineering: PD7 can read Java and PowerBuilder code, then
create object models from it. If you’ve already begun coding, it’s not
too late to start using PD7 for object modeling.
• Code generation: Once you’ve modeled your objects, PD7 will actually generate your code for you. This gives you a good head start on
coding Java and PowerBuilder applications and components.
• UML: PD follows the industry standard by supporting the Unified
Modeling Language.
Obviously, that’s a very brief list of standout features. For more information on the features that PD7 offers and to download an evaluation
version, go to www.sybase.com/products/powerdesigner. In the next
few months we’ll have some in-depth articles on the features of PD7, as
well as provide some tips on its use.
Sybase Success
Sybase released their fourth-quarter earnings on January 21. They
blew away financial analysts’ expectations for profits by beating the
per-share estimate by almost 50%. That’s a huge surprise for a quarter in which earnings were expected to be down due to Y2K and a
cyclical revenue downturn. After the earnings announcement,
Sybase stock immediately gained 25% and suddenly Sybase is the
talk of Wall Street. The media frenzy will blow over but hopefully the
increased consumer confidence will not. With over $300,000,000 in
the bank, it’s clear that Sybase isn’t going out of business any time
soon. On the downside, the service revenues are increasing while the
licensing revenues are flat. For a company whose success has been
based on its products rather than services, that could signal a change
in direction. ▼
john@sys-con.com
AUTHOR BIO
John Olson is a principal of Developower, Inc., a consulting company specializing in software solutions using Sybase development tools.
He’s a CPD professional, charter member of TeamSybase and contributing author to SYS-CON’s Secrets of the PowerBuilder Masters books.
www.PowerBuilderJournal.com
PBDJ volume7 issue2
3
Starbase
Corporation
www.starbase.com
4
PBDJ volume7 issue2
www.PowerBuilderJournal.com
GUEST EDITORIAL
WRITTEN BY RICHARD BROOKS
EDITORIAL ADVISORY BOARD
MICHAEL BARLOTTA, BILL BARTOW, ANDY BLUM,
RICHARD BROOKS, JOE CELKO, KOUROS GORGANI,
BAHADIR KARUV, PH.D., DAVID MCCLANAHAN,
BERNIE METZGER, JOHN OLSON, SEAN RHODY
EDITOR-IN-CHIEF:
EXECUTIVE EDITOR:
ART DIRECTOR:
PRODUCTION EDITOR:
ASSOCIATE EDITOR:
TECHNICAL EDITOR:
WATCOM SQL EDITOR:
DATA WINDOWS EDITOR:
WRITERS IN THIS ISSUE
BRUCE ARMSTRONG, MICHAEL BARLOTTA, RICHARD BROOKS,
BRIAN DAVIGNON, VINCE FABRO, KOUROS GORGANI, BOB HENDRY
BERNIE METZGER, , JOHN OLSON, ROB VEITCH
SUBSCRIPTIONS
FOR SUBSCRIPTIONS AND REQUESTS FOR BULK ORDERS,
PLEASE SEND YOUR LETTERS TO SUBSCRIPTION DEPARTMENT
SUBSCRIPTION HOTLINE:800 513-7111
COVER PRICE: $14/ISSUE
DOMESTIC: $149/YR. (12 ISSUES) CANADA/MEXICO: $169/YR.
OVERSEAS: BASIC SUBSCRIPTION PRICE PLUS AIRMAIL POSTAGE
(U.S. BANKS OR MONEY ORDERS). BACK ISSUES: $12 EACH
PUBLISHER,PRESIDENT AND CEO:
VICE PRESIDENT,PRODUCTION:
VICE PRESIDENT,MARKETING:
ACCOUNTING MANAGER:
ADVERTISING ACCOUNT MANAGERS:
ADVERTISING ASSISTANT:
GRAPHIC DESIGNER:
GRAPHIC DESIGN INTERN:
WEBMASTER:
WEB EDITOR:
WEB SERVICES CONSULTANT:
WEB SERVICES INTERN:
CUSTOMER SERVICE:
JDJSTORE.COM:
ONLINE CUSTOMER SERVICE:
Fat Client Is Not Dead
JOHN OLSON
M’LOU PINKHAM
ALEX BOTERO
CHERYL VAN SISE
NANCY VALENTINE
BERNIE METZGER
JOE CELKO
RICHARD BROOKS
FUAT A. KIRCAALI
JIM MORGAN
CARMEN GONZALEZ
ELI HOROWITZ
ROBYN FORMA
MEGAN RING
CHRISTINE RUSSELL
JASON KREMKAU
AARATHI VENKATARAMAN
ROBERT DIAMOND
BARD DEMA
BRUNO Y. DECAUDIN
DIGANT B. DAVE
ANN MARIE MILILLO
JACLYN REDMOND
AMANDA MOSKOWITZ
EDITORIAL OFFICES
SYS-CON PUBLICATIONS, INC.
39 E. CENTRAL AVE., PEARL RIVER, NY 10965
TELEPHONE: 914 735-7300
FAX: 914 735-6547
SUBSCRIBE@SYS-CON.COM
POWERBUILDER DEVELOPER’S JOURNAL (ISSN#1078-1889)
is published monthly (12 times a year) for $149.00 by
SYS-CON Publications, Inc., 39 E. Central Ave., Pearl River, NY 10965-2306.
Periodicals Postage rates are paid at
Pearl River, NY 10965 and additional mailing offices.
POSTMASTER: Send address changes to:
POWERBUILDER DEVELOPER’S JOURNAL, SYS-CON Publications, Inc.,
39 E. Central Ave., Pearl River, NY 10965-2306.
©COPYRIGHT
Copyright © 2000 by SYS-CON Publications, Inc. All rights reserved.
No part of this publication may be reproduced or transmitted in any form or by any
means, electronic or mechanical, including photocopy or any information storage and
retrieval system, without written permission. For promotional reprints, contact reprint
coordinator. SYS-CON Publications, Inc., reserves the right to revise, republish and
authorize its readers to use the articles submitted for publication.
WORLDWIDE DISTRIBUTION BY
CURTIS CIRCULATION COMPANY
739 RIVER ROAD, NEW MILFORD NJ 07646-3048 PHONE: 201 634-7400
All brand and product names used on these pages are trade names,
service marks or trademarks of their respective companies.
SYS-CON Publications, Inc., is not affiliated with the companies
or products covered in PowerBuilder Developer’s Journal.
SYS-CON
PUBLICATIONS
www.PowerBuilderJournal.com
s programmers we tend to live in the fast
lane, especially where technology is concerned. If you’re anything like me, any
mention of the “wave of the future” gets
your immediate attention. The last thing we
want is to be left behind, floundering in a
dead or dying technology.
I know that’s what drives me. That’s why, in
the wee hours of the morning, you can often
find me scanning hundreds of messages in
the usenet groups looking for those little
hints – “How is the new technology being
used? Who is using it? Are the big companies
getting involved? What problems are the programmers facing with this new technology
and will I face those problems soon?”
I have the Sybase knowledge base bookmarked. I’ve set up “My Support” on Sybase.
I’ve read every faq there and every white
paper. I routinely scan Amazon.com for
books with words like PowerBuilder, Jaguar,
PowerJ, Distributed, COM and CORBA. I buy
and read these books without a second
thought.
By the time this emerging technology
becomes mainstream, I’ll already be there
with the experience and knowledge, perfectly poised to offer my clients the solutions
they need.
This brings me to my point. This morning
my client told me she was afraid that I might
be driving my current project too far toward
this technology. She wanted to remind me of
her priorities. Actually she has only one – get
this application up and running and make
sure it’s airtight. Then move on to those other
applications that need to be done.
It was an epiphany for me, one of those
moments when reality comes shining in,
throws you back in your chair and makes you
reconsider your motivations and actions. I
stumbled my way through the rest of that
meeting, trying to allay her very justifiable
fears while at the same time asking myself if
she was right, at least on some level.
A
My mind swirled with a sudden realization
of this client’s world. Sure, she’d like to use
the most modern technology. It would be
nice to be able to use all the buzzwords and
to tell her supervisors she had delivered the
very best that could be found. Given the
choice though, she would rather say that she
delivered on time and under budget using
old technology rather than late and over budget using new. The single biggest nightmare,
though, would be to admit that in her efforts
to bring the very best, she didn’t have anything at all.
I could explain to her how this technology
works and the flexibility she would gain. I
could explain instance pooling and wax
poetic about components and how she could
get her Java programmers working on the
same team as her PowerBuilder programmers, all of them striving for the same goal.
She doesn’t know these words; she hasn’t
seen them working. She hasn’t seen any real
proof that this technology is more than piein-the-sky dreams and hopes. What she
knows is PowerBuilder, client/server, PFC
and MDI. These are proven and safe. She
knows that they work and she has faith in
them. She knows how much effort is required
with these tools. She knows that when I leave,
her programmers can maintain and extend
what I give her – as long as I stick with their
skill set. For her these things are real. They
are today. Let’s talk about tomorrow when it
gets here. For now, she needs her application.
So yes, tomorrow morning in the wee
hours, you’ll find me at my desk in my home
office once again scouring the usenet groups
for hints, tidbits and head starts. I’ll be one of
those programmers out there talking about
how to implement this new technology and
I’ll be, believe me, one of the greatest supporters of it. But for now I need to keep her
needs in mind and focus.
Let’s see, how many arguments did OpenSheet have again? ▼
dwmaster@1313mockingbirdlane.com
AUTHOR BIO
Richard (Rik) Brooks is the owner of Brooks & Young, a Sybase Code Partner. He’s been using PowerBuilder since 1990
and has worked as an independent consultant for major corporations in the U.S. for the last five years. He has authored
several books on PowerBuilder including PFC Programmer’s Reference Manual and The Definitive DataWindow.
PBDJ volume7 issue2
5
P B D J
P A R T
F O C U S
V
Leverage your knowledge of PowerScript
WRITTEN BY KOUROS GORGANI
n Part 1 of this article (PBDJ,Vol. 7, issue 1) I compared Java to
PowerScript.We covered some of the fundamentals of Java language – identifiers, primitive data types and primitive data type
wrappers – and PowerBuilder’s any data type, and we began to
discuss relational operators, so let’s continue.
I
Boolean Logical Operators
expression is not evaluated. To illustrate this point, consider
the following example using the && operator:
In Java, the boolean logical operators as seen in Table 1
may look different; however, they perform the same as the
AND, OR and NOT in PowerBuilder. The operator not present
in PowerBuilder is the exclusive OR, XOR that returns TRUE
if and only if one of its two operands is TRUE.
boolean IsPersonEligibleForDriversLicense( String name,
int age ) {
int minimumAge = 16;
boolean ok;
Conditional Logical Operators
The conditional logical operators shown in Table 2
work the same as the Boolean logical operators with one
difference: conditional operators are known to short-circuit.
Short circuiting is a performance optimization common
to many languages. When one condition can determine the
outcome of the entire expression, the remainder of the
Operator
Name
Example
&
AND
if ((age >= 9) & (age <= 9))
System.out.println("Age is 9.");
|
OR
if ((age == 9) | (age == 10))
System.out.println("Age is 9 or
10.");
!
NOT
if ( !(age == 9) ) System.out.print
ln("Age is not 9");
^
XOR
if ((age == 9) ^ (age == 10)) Sys
tem.out.println("Age is 9 or 10");
TABLE 1
Operator
Name
Example
&&
AND
if ((age > 0) && (age <= 9))
System.out.println("Age is 9".);
||
OR
if ((age == 9) || (age == 10))
System.out.println( "Age is 9 or
10." );
if (( age >= minimumAge ) &&
(HasNoCriminalRecord(name)) {
ok = true;
} else {
ok = false;
}
return ok
}
In order for a && expression to evaluate to true, both sides
must evaluate to true. In the above example, for a person to be
eligible for a driver’s license, he or she must be older than 16
and not have a criminal record. For this to occur the left-hand
side should be (age >= minimumAge) and the right-hand side
– (HasNoCriminalRecord(name)) – must evaluate to true.
Since the conditional and && are used, Java can skip
the criminal record check if the person’s age is less than
16. Thus, if the expression (age >= minimumAge) evaluates to false, then the overall expression can’t be true and
the criminal record check isn’t performed. In this scenario, the method (HasNoCriminalRecord name) isn’t
even called, which could result in a significant performance increase.
If our expression had been coded with the logical and &,
then the criminal record check would always be performed,
regardless of the person’s age.
if ( age >= minimumAge ) & (HasNoCriminalRecord(name)) {
TABLE 2
6
PBDJ volume7 issue2
www.PowerBuilderJournal.com
I I
S
to quickly learn and build Java programs
Bitwise Logical Operators
–-a; // prefix decrement
The third class of operators is the bitwise operators that
operate on individual bits in an integral value. Collectively,
the individual bits are referred to as a bitfield. Bitfields are an
extremely efficient and compact way to use and store properties. Each individual bit in the bitfield could represent a single
property that is either on or off. The bold, italic and underline
properties of fonts could be represented by a single bit in a
bitfield since their values are either on or off. Bits could also
be combined to represent a property with more than two values. The alignment property, such as justify, right, left or center for a paragraph, requires two bits in order to represent the
four ( (2bits)2 = 4properties ) values.
The Java data type byte, whose length is 8 bits, could be used
to store all the properties in the example mentioned above. To
adjust a property’s setting, its corresponding bit must be turned
on or off. If we wanted to change the font to bold, then the first
bit needs to be enabled or set to 1. If we wanted to change the
paragraph alignment to right justification, then the fifth and
sixth bits need to be 1 and 0, respectively.
Java also allows the prefix and postfix operators to exist
within expressions or statements.
Arithmetic Operators
Java’s arithmetic operators are standard among today’s
programming languages. In Table 3 the Java language defines
standard arithmetic operators.
In addition, PowerBuilder defines an exponentiation operator, “^”, and Java does not. In Java, exponentiation is performed by the pow() method in the java.lang.Math package.
// To calculate 23
result = 2^3
// In PowerBuilder
result = java.lang.Math.pow( 2, 3 ); // In Java
In Java the expression A-B is always parsed as A minus B.
PowerBuilder will treat the expression A-B as either an identifier or an expression. PowerBuilder’s interpretation depends
on the value of the DashesInIdentifiers property. If DashesInIdentifiers is set to 1, then A-B is an identifier, and if
DashesInIdentifiers is set to 0, then A-B is the same as Java.
Both PowerBuilder and Java allow the use of postfix increment and decrement operators. In order to use the postfix
decrement operator in PowerBuilder, the DashesInIdentifiers
must be turned off. Java also permits the use of the prefix
increment and decrement operators.
int a;
a++; // postfix increment
a--; // postfix decrement
++a; // prefix increment
// In Java the following is legal.
int a;
a = 0;
System.out.println( "Value of a is " + ++a );
Both PowerBuilder and Java permit the use of the com–
pound assignment operator that, in general, is a statement of
the form: variable operator= expression.
Some examples include the following:
int a;
a += 1;
a *= 2;
// Equivalent to a = a + 1;
// Equivalent to a = a * 1;
Note: The DashesInIdentifiers property is located in the
PB.INI and valid values for the property are:
• 1 = Allows dashes in identifiers and the expression a– is
interpreted as an identifier.
• 0 = Disallows dashes in identifiers and the expression a– is
interpreted as an expression.
Flow Control Statements
Java’s flow or control statements have practically been
copied from C or C++. If you have any C or C++ experience,
these will all look familiar. If your background is using PowerScript, then you’ll have to learn only the new syntax of these
statements. The common flow-control statements to both
languages include if, for and while.
If Statement
Table 4 compares Java’s standard if statement to PowerBuilder’s if statement.
Operator
Meaning
Example
+
Addition
result = a + 5;
+
Concatenation
newString = "Hello " + "World";
-
Subtraction
result = a – 5;
*
Multiplication
result = a * 5;
/
Division
result = a / 5;
%
Remainder
result = a % 5;
TABLE 3
PBDJ volume7 issue2
www.PowerBuilderJournal.com
7
Java
PowerBuilder
if <condition> {
<action>;
}
IF <condition> THEN
<action>
END IF
if <condition> {
<action1>;
} else {
<action2>;
}
IF <condition> THEN
<action1>
ELSE <
action2>
END IF
if <condition1> {
<action1>
} else if <condition2> {
<action2>;
}
IF <condition1> THEN
<action1>;
ELSEIF <condition2> THEN
<action2>
END IF
if <condition1>
<action1>;
} else if <condition2> {
<action2>;
} else {
<action3>;
}
{IF <condition1> THEN
<action1>
ELSEIF <condition2> THEN
<action2>
ELSE
<action3>
END IF
choose. Java behaves the same way with one fundamental difference.
When the <statementblock> finishes, control is not transferred to the
end of the switch statement. Instead, control falls through to the next
label and executes its respective <statementblock>. This “fall through”
continues until either the end of the switch or a break statement is
encountered. If a break statement is encountered, control is transferred
to the end of the switch.
Switch.java
int yearsOfService, numberOfHolidays;
numberOfHolidays = 0;
yearsOfService = 10;
switch (yearsOfService) {
case 10:
numberOfHolidays += 5;
case 5:
numberOfHolidays += 5;
TABLE 4
Java
PowerBuilder
case 2:
numberOfHolidays += 5;
switch <expression>
case <item>:
<statementblock>;
}
{CHOOSE CASE <expression>
CASE <item>
<statementblock>
END CHOOSE
case 1:
numberOfHolidays += 5;
break;
switch <expression>
case <item>:
<statementblock>;
break;
{CHOOSE CASE <expression>
CASE <item>
<statementblock>
CASE ELSE
default:
<statementblock>;
}
<statementblock>
END CHOOSE
TABLE 5
Java
PowerBuilder
for( <varname>=<start>;
FOR <varname>=<start> TO
<varname>=<end>; <end>
<statementblock>
NEXT
<varname>++ )
{
<statementblock>;
}
for( <varname>=<start>;
<end>
<varname>=<end>;
<varname> = <varname> + <increment> )
{
<statementblock>;
}
FOR <varname>=<start> TO
STEP
<increment>
<statementblock>
NEXT
TABLE 6
No magic here! Java defines all standard conditions when dealing with if
statements. Remember that <condition> must be boolean.
Switch Statement
Switches in Java behave slightly different than PowerBuilder’s choose
statement (see Table 5). In Java the <expression> must evaluate to a char,
byte, int or short. If it doesn’t, then compile-time error will be generated.
At runtime, Java’s switch behaves differently than PowerBuilder’s
choose statement. When PowerBuilder finds a case <item> that matches
the <expression>, it immediately executes the next <statementblock>.
After the <statementblock> is executed, control is transferred to the end
8
PBDJ volume7 issue2
default;
numberOfHolidays += 5;
}
In the above example, the number of days allotted for vacation is
based on the number of years in service. The example sets the yearsOfService to 10 years and then calculates numberOfHolidays. The switch
statement will execute case blocks 10, 5, 2 and 1 since all the blocks, except
case 1, don’t have a break statement. When case 1 executes, it transfers
control to the end of the switch block once the break is executed.
For Statement
Table 6 compares Java’s standard for statement to PowerBuilder’s for
statement.
Java’s for loop includes all the functionality of PowerBuilder’s for loop
with a couple of extra features. A standard for loop that outputs integers
1 through 10 is as follows:
int j;
for( j=1; j<=10; j++ ) {
System.out.println( "j = " + j );
}
In the above example the starting condition is j = 1. The loop repeats
while j <= 10, and every time the loop iterates, j is incremented by 1. Also,
there’s one expression to represent the starting condition and one to represent the increment condition. Like C and C++, Java allows multiple starting
and increment expressions. In the following example, there are two start
conditions and two increment conditions that count two variables from 1
to 10.
int j, k;
for( j=1, k=1; j<=10; j++, k++ ) {
www.PowerBuilderJournal.com
System.out.println( "j = " + j + ", k = " + k );
}
Notice that the two start expressions are separated by a comma, as
are the increment expressions. This technique is commonly used when
dealing with data structures that involve “linked lists” to other objects.
The following example counts the number of nodes in a linked list.
LinkedNodes
int nodeCount;
ln;
for( ln=headNode, nodeCount=1; ln!=null; nodeCount++, ln=ln.getNextNode() ) { }
System.out.println( "Number of nodes = " + nodeCount );
Do-While Statement
Table 7 compares Java’s standard do-while statement to PowerBuilder’s do-while statement.
This loop will always execute the <statementblock> once and will continue to execute <statementblock> while the <condition> is true.
int j = 10;
do {
System.out.println( "T-Minus " + j " + seconds until blastoff!" );
} while ( --j >= 0 );
Java
PowerBuilder
do {
DO
<statementblock>
<statementblock>
} while <condition>;
LOOP WHILE <condition>
TABLE 7
Java
PowerBuilder
while <condition> {
DO WHILE <condition>
<statementblock>;
<statementblock>
}
LOOP
Jdj Store
Ad
www.jdjstore.com
TABLE 8
While Statement
Table 8 compares Java’s standard while statement to PowerBuilder’s
loop while statement.
Unlike the do-while loop, the while loop may or may not execute the
<statementblock> once depending on the value of <condition>. While
<condition> is true the while loop continues to iterate.
int j = 11;
while ( j-- > 0 ) {
System.out.println( "T-Minus " + j " + seconds until blastoff!" );
}
Now that we’ve covered the basics, we’ll discuss object orientation in
PowerBuilder versus Java in the third and final part of this series. ▼
ABOUT THE AUTHOR
Kouros Gorgani is a software engineering manager focusing on Enterprise Application Server and the mobile
and embedded computing platform. He is a CPD and the author of several technical books.
kouros@romotechnology.com
www.PowerBuilderJournal.com
PBDJ volume7 issue2
9
D A T A W I N D O W
M A G I C
Concepts in Graphing
Creating a simple graph
hen I sit down to write this column I always ask myself, “What can I write that will be the most help
to DataWindow programmers?” At the moment I’m also in the final production stages of my new
book, The Definitive DataWindow.This morning, while glancing at my chapter on “Graphing,” I remembered that this is an area not well understood by many people. In addition, an awful lot can be said
about graphing, maybe enough for a series of articles.
W
WRITTEN BY
RICHARD BROOKS
My goal right now is to make sure
you’re an expert at creating graphs. I’m
going to show you how to create them,
how to use computed columns to make
the data “fit” into your graph and how to
make them interactive. I probably won’t
be able to fit it all into just one article –
but I’ll try.
We’re going to develop an application
that holds examples of graphs. Actually,
this is my typical test application. I write
one for every application I work on. In
fact, I’ve done this since I developed my
first one decades ago. I have an empty
application I call “tester”; it’s sitting in
its own folder, empty except for the
skeleton. Every time I start a new application, I copy tester.pbl to the appropriate folder and begin to build on it.
First we’ll create our tester.pbl, which
I’ll explain how to use, then we’ll get to
our first simple graph.
For our test application we’ll have two
windows, one to log in and the other as
a jumping-off point for our test windows. The idea is that we create a window for each individual test, then go
back to the main window and add the
test in there.
I’ve created the test application in my
own folder under “personal apps”
called, quite appropriately, tester.pbl. In
fact, the only things in that folder are
tester.pbl and connection.ini. That way,
when I start an application, I just copy
both of those files into my new application folder and I’m ready to go.
Figure 1 is the login window and Figure 2 is the main window, both from
tester.pbl. When I create a new tester
application I have to set some of the
properties to customize them.
Let’s take a look at the login window. I
call mine w_login. There’s one checkbox
for the autoCommit, a series of single-line
edits for the values, some static texts and a
couple of buttons – one for canceling the
login and one for executing the connect.
10
PBDJ volume7 issue2
You can find the code for the open
event of the window in Listing 1. It simply loads the connection parameters
from the connection.ini file into the
controls on the login window. The only
other code required in this window is in
the clicked event for the Connect button. You’ll find it in Listing 2.
The next step is the main window. I
call mine w_main. You can see from the
figure that it just has a listbox, a couple
of staticText objects and a button. In this
case I’ve named the staticText objects
st_help and st_window. The former
holds text that will describe the test
when the user clicks on the listbox; the
latter holds the name of the window so I
don’t forget where to go to see the code.
The code for the open event is found
in Listing 3.
The only other code in this window is
found in the listbox control. There’s
code in the selectionChanged event, in
which we’ll change the values in the StaticTexts, and there’s code in the DoubleClicked events, in which we fire off
our example window. These are found in
Listings 4 and 5, respectively.
That’s all there is to the template test
application. As I said before, when I
start an application I copy the pbl and
the connection.ini file into my new
application folder, and whenever I need
to test something I put it in there.
Our First Graph Exercise
Before we get started on graphs we
need to understand a few terms. When
you’re using graphs in a DataWindow,
you’ve moved away from the traditional
row-and-column metaphor. You’re now
dealing with categories and values.
What’s worse is that these terms are so
flexible, they can be just about anything.
I think one of the hardest things about
graphs, at least for me, is we’re essentially dealing with the data in a different
way. Now we talk about directions and
dimensions rather than rows and
columns. It gets even worse – we’re often
talking about multiple lines or planes.
To put it frankly, once you’re dealing
with graphs you’re no longer in Kansas,
Dorothy.
Let’s define our terms.
• A category is normally the span of
your graph. It’s the opposite of a
FIGURE 1 Standard login window
www.PowerBuilderJournal.com
Ecrane Computing
www.ecrane.com
www.PowerBuilderJournal.com
PBDJ volume7 issue2
11
D
A
T
A
W
I
N
D
FIGURE 2 Main test application window
value. For instance, if you graph sales
between a set of dates, the category
would be the dates.
• A value is the data that you’re graphing. In our case of sales, it would be
the sales amount or perhaps the
number of sales.
• A series is the collection of graphing
lines. For example, you might want to
compare the sales of one product with
another and the series would be the
product.
With these definitions in mind let’s do
our first little graph. In our example
we’ve been asked to create a graph that
allows a manager to view shipments that
occurred between a user-supplied pair
of dates. The manager doesn’t care
which product is shipped; his only concern is that there have been shipments.
First create the DataWindow, a graph
with a SQL source. The SQL is shown in
Figure 3, but just in case you prefer text,
here’s the SQL statement:
SELECT "sales_order_items".
"quantity",
"sales_order_items"."ship_date",
"product"."name",
"product"."description"
FIGURE 4 Specifying category and value
12
PBDJ volume7 issue2
FROM "sales_order_items",
"product"
WHERE (
"sales_order_items"."prod_id" =
"product"."id" ) and
( (
"sales_order_items"."ship_date"
between :adate_start and
:adate_end ) )
ORDER BY "sales_order_items".
"ship_date" ASC
O
W
M
A
G
I
C
Figure 4 shows the next dialog we get.
In this case we aren’t concerned with
series. Our manager told us he didn’t
care about which products, just all of
them grouped together. So the category
(the span) is going to be the dates. The
value then would be the sum of the shipments for that date.
The next dialog is shown in Figure 5. All
we need here is a title for our graph and to
specify which style it is. Since we’re going
to do a line graph, that’s what we select.
This dialog is followed by a summary
of the options we’ve chosen. Click past
that one and you’ll be in a DataWindow
painter for your DataWindow.
It’s time to put our test program to
work. We need a window to hold this
DataWindow and a couple of controls to
provide us with the dates. Think about it
– we’re about to create a window. Should
we have a superclass for this (an ancestor)? Do you suppose you’ll ever need a
similar window again? I think you might.
In fact, since I know what I’ll be writing
in the coming months, I can tell you for
certain that you will.
We should pause right here and create
an ancestor window – a new one (or
inherit from an appropriate existing
FIGURE 3 SQL for your line graph
As you can see, we’re selecting from two
tables: thesales_order_items and the
product. The reason we want the product is so we can display the name and
description of the products.
We also have two retrieval arguments
– the start and end dates. Thus we have
quite a simple little DataWindow here.
one) – and put in the two edit masks for
the start and end dates.
Wait a minute! Two edit masks for the
start and end dates? Will we ever need
that combination of edit masks again?
Just how many times have you created
them? Okay, just leave that window
painter on your screen. We’ll come back
FIGURE 5 Selecting the graph style
www.PowerBuilderJournal.com
Java Con 2000
www.javacon2000.com
www.PowerBuilderJournal.com
PBDJ volume7 issue2
13
D
A
T
A
W
I
N
dw_1.setTransObject(sqlca)
In the OK button you need to close
only your parent (the parent of the button is the window).
The only real code in this window is in
the clicked event of the Run button. You
can find that in Listing 7. Note: I made the
Run button default so the user can enter
the dates, then hit Enter to run the report.
Close and save this window. I call mine
w_super_report_daterange because I like
to keep all my ancestor (superclass) windows together in my libraries.
Now that our infrastructure is set up,
we need to use it, which is the point of
W_login::open()
sle_serverName.text = profileString("connection.ini", "database", "servername", "")
sle_logpass.text = profileString("connection.ini", "database", "logpass", "")
sle_logid.text = profileString("connection.ini", "database",
"logid", "")
sle_dbpass.text = profileString("connection.ini", "database",
"dbpass", "")
sle_dbparm.text = profileString("connection.ini", "database",
"dbparm", "")
sle_dbms.text = profileString("connection.ini", "database",
PBDJ volume7 issue2
W
M
A
G
I
C
FIGURE 7 Your graph windows
Listing 1
14
O
this article. You might note that in the
future, if you ever have to do another
similar task, you can skip most of the
preceding steps.
Inherit from your superclass window,
set the title for the window (mine is
“Simple Date Range Graph Test”), then
click on the DataWindow and assign it
the dataObject of the graph you created
earlier. Now close and save your window. I called mine w_product_shipments. That’s all there is to it. No code at
all! In the future you can create any date
range report you want by simply creating the DataWindow, inheriting from
w_super_report_daterange, and setting
the title and the dataObject.
The final step is to add this to our test
application. Open w_main again and
add “Product Shipments” to your listbox.
Now go to your DoubleClicked event
FIGURE 6 Creating your custom “from-to” control
to it soon enough. For now, click on
New, then the Object tabpage, and finally the Custom Visual (see Figure 6).
The next step is to put two static texts
on the object for the From and To, and
then two edit masks.
We need to give the programmer
some way to get the values of those edit
masks. This means one function for getting the From Date, another for getting
the To Date. Those functions are found
in Listing 6.
We’re finished with this object. Close
it and save it as u_dateRange and you’ll
be back at the new window you just left,
ready to place your object on it.
Put your date range object on your
window, a couple of buttons for running your report and another for closing the window, then put a DataWindow control on the window to run. You
don’t have to set the dataObject property of the DataWindow control yet; we’re
still in the ancestor. However, you
should set the control name for your
date range object to uo_dates (see Figure 7).
In the open event you have only one
line of code:
D
for that listbox and add your case statement for this new item. You can find
mine in Listing 8.
Finally, go to the selectionChanged
event for the same listbox and add your
case statement for the item again. You’ll
find my code for this in Listing 9.
This gives us a start, which we’ll
enhance in my next column. We’ll clean
up a few things in the graph to make it
look better, then we’ll make another
similar, but more complex, graph. ▼
dwmaster@1313mockingbirdlane.com
"dbms", "")
sle_database.text = profileString("connection.ini", "database", "database", "")
if upper(left(profileString("connection.ini", "database",
"autocommit", "F"), 1)) = "F" then
cbx_autocommit.checked = FALSE
else
cbx_autocommit.checked = TRUE
end if
Listing 2
W_login.cb_connect::clicked()
if not fileExists("connection.ini") then
int li_fileHandle
www.PowerBuilderJournal.com
Lecco
www.leccotech.com
www.PowerBuilderJournal.com
PBDJ volume7 issue2
15
li_fileHandle = fileOpen("connection.ini", lineMode!,
write!, shared!, replace!)
if li_fileHandle < 1 then
messagebox("Error", "Could not create connection.ini file
to store your values as defaults")
else
fileWrite(li_fileHandle, " ")
fileClose(li_fileHandle)
end if
end if
string ls_str
if len(sle_servername.text) > 0 then ls_str = sle_servername.text
setProfileString("connection.ini", "database", "servername",
ls_str)
if len(sle_logpass.text) > 0 then ls_str = sle_logpass.text
setProfileString("connection.ini", "database", "logpass",
ls_str)
if len(sle_logid.text) > 0 then ls_str = sle_logid.text
setProfileString("connection.ini", "database", "logid",ls_str
)
if len(sle_dbpass.text) > 0 then ls_str = sle_dbpass.text
setProfileString("connection.ini", "database", "dbpass",
ls_str)
if len(sle_dbparm.text) > 0 then ls_str = sle_dbparm.text
setProfileString("connection.ini", "database", "dbparm",
ls_str)
if len(sle_dbms.text) > 0 then ls_str = sle_dbms.text
setProfileString("connection.ini", "database", "dbms",
ls_str)
if len(sle_database.text) > 0 then ls_str = sle_database.text
setProfileString("connection.ini", "database", "database",
ls_str)
if cbx_autocommit.checked then
setProfileString("connection.ini", "database", "autocommit",
"TRUE")
else
setProfileString("connection.ini", "database", "autocommit",
"FALSE")
end if
Listing 4
W_main.lb_1::doubleClicked(int index)
string ls_text
ls_text = text(index)
// Add your cases below. Then open the appropriate window.
choose case ls_text
case ""
end choose
Listing 5
W_main.lb_1.selectionChanged(int index)
string ls_text
ls_text = text(index)
// Add your cases below. In them set st_help.text with a
// description of what your test will do. Then set
// st_window.text to be the name of the window that will
// run your test
choose case ls_text
case ""
end choose
Listing 6
U_dateRange.of_From()
date ldate_retVal
int li_status
li_status = em_from.getData(ldate_retVal)
return ldate_retVal
u_dateRange.of_to()
date ldate_retVal
int li_status
li_status = em_to.getData(ldate_retVal)
return ldate_retVal
Listing 7
W_super_report_daterange.cb_run::clicked()
date ldate_from, ldate_to
ldate_from = uo_dates.of_from()
ldate_to = uo_dates.of_to()
if isNull(ldate_from) or isNull(ldate_to) then
messagebox("ERROR", "Please enter a valid date range")
end if
if len(sle_servername.text) > 0 then sqlca.servername =
sle_servername.text
IF LEN(sle_dbms.text) > 0 then sqlca.dbms = sle_dbms.text
if len(sle_logpass.text) > 0 then sqlca.logpass = sle_logpass.text
if len(sle_logid.text) > 0 then sqlca.logid = sle_logid.text
if len(sle_dbpass.text) > 0 then sqlca.dbpass =
sle_dbpass.text
if len(sle_dbparm.text) > 0 then sqlca.dbparm =
sle_dbparm.text
if len(sle_database.text) > 0 then sqlca.database = sle_database.text
sqlca.autocommit = cbx_autocommit.checked
dw_1.retrieve(ldate_from, ldate_to)
connect using sqlca ;
W_main.lb_1.selectionChanged(int index)
string ls_text
ls_text = text(index)
if sqlca.sqlcode <> 0 then
messagebox("Connection Error", "[" + string(sqlca.sqldbcode)
+ "] " + sqlca.sqlerrText)
else
closeWithReturn(parent, "success")
end if
Listing 3
W_main::open()
open(w_login)
string ls_status
ls_status = message.stringParm
if ls_status = "failure" then close(this)
Listing 8
W_main.lb_1::doubleclicked(int index)
string ls_text
ls_text = text(index)
// Add your cases below. Then open the appropriate window.
choose case ls_text
case "Product Shipments"
open(w_product_shipments)
end choose
Listing 9
// Add your cases below. In them set st_help.text with a
// description of what your test will do. Then set
// st_window.text to be the name of the window that will
// run your test
choose case ls_text
case "Product Shipments"
st_help.text = "Demonstrates a simple date range line
graph"
st_window.text = "w_product_shipments"
end choose
!
d the Code
a
o
l
n
Dow
The code listing for this article can also be located at
www.PowerBuilderJournal .com
16
PBDJ volume7 issue1
www.PowerBuilderJournal.com
Sybase Techwave
www.sybase.com/techwave2000
www.PowerBuilderJournal.com
PBDJ volume7 issue2
17
D I S T R I B U T E D
T E C H N O L O G I E S
Handling Aborted Transactions in Jaguar
A technique that works with any Jaguar client or component
n this article we’ll examine how to handle a Jaguar CORBA_TRANSACTION_ROLLEDBACK exception
that is thrown when a Jaguar transaction is aborted. We’ll use the BTFBank sample application developed in an article I wrote for PBDJ in October 1999 (Vol. 6, issue10).This example is written in PowerBuilder, but the technique covered here is relevant for any Jaguar client (Java,Web, etc.) or Jaguar component that’s involved in a transaction.
I
WRITTEN BY
MICHAEL BARLOTTA
AUTHOR BIO
Michael Barlotta, director
of information
technologies at AEGIS
Consulting, has worked
with PowerBuilder
since version 3.0 and is a
CPD associate.The author
of several books, including
Jaguar Development with
PowerBuilder 7
(Manning Publications),
Mike also serves as a
representative of AEGIS
for the Application
Service Provider
Industry Consortium.
Visit his Web site at
www.erols.com/m.barlotta.
18
When developing a Jaguar component that uses Jaguar transactions, a
CORBA_TRANSACTION_ROLLEDBACK
exception is generated each time a
transaction managed by the Jaguar server is aborted and rolled back. A transaction is marked as requiring a ROLLBACK
by a PowerBuilder component’s invoking the SetAbort function on the TransactionServer object.
This exception is thrown when the
method that’s called on a Jaguar component causes the transaction to be rolled
back. In a PowerBuilder client this exception is captured by the connection object
error event. If the exception isn’t handled,
the client application will terminate. As a
result of throwing the exception, the
method return value and any arguments
passed by reference (output parameters)
are unusable, making it difficult to handle such situations gracefully.
This situation is best illustrated with an
example. In our BTFBank example we
were able to deposit, withdraw and transfer money from a bank account. In this
example a withdrawal of money was canceled if the account didn’t have enough
funds or wasn’t found, or if the SQL to
update the database failed. In all cases the
SetAbort function was invoked to terminate the transaction. However, in the last
article we didn’t return a very useful message that would indicate to the client that
called the withdrawal why it had failed.
The code in Listing 1 modifies the function to accept by reference a string argument in which a message can be returned
to be displayed to the user.
On the BTFBank client window we
can make the following changes to the
call to the withdraw method on the
account component so the message
returned by the method call is displayed
with a MessageBox.
// Withdraw command button
int li_rc
int li_acct, li_tellerid
decimal ld_amount
PBDJ volume7 issue2
FIGURE 1
string
CORBA_TRANSACTION_ROLLEDBACK exception
ls_msg
li_tellerid =
Integer(sle_tellerid.text)
li_acct =
Integer(sle_withdraw_account.text)
ld_amount =
Real(sle_withdraw_amount.text)
li_rc=in_account.withdraw(li_acct,
ld_amount, ls_msg)
IF li_rc <> 1 THEN
MessageBox("BTFBank",ls_msg)
END IF
After making these changes we can
run the example. However, you’ll notice
that after attempting to withdraw too
much money from an account, you don’t
get your error message. Instead, you get
the CORBA_TRANSACTION_ROLLEDBACK exception as shown in Figure 1.
Handling the Aborted Transaction
As a result of throwing the exception,
the withdraw method return value and
any arguments passed by reference
(output parameters) aren’t available,
since the values aren’t sent to the client.
In order to allow return parameters to
be marshaled and accessible by the
client, we need to set a component
property on the Jaguar server. The
www.PowerBuilderJournal.com
Xml Dev Con
xmldevcon2000.com
www.PowerBuilderJournal.com
PBDJ volume7 issue2
19
D I S T R I B U T E D
T E C H N O L O G I E S
and type failed into the Property Value
field.
A value of “failed” tells the Jaguar server not to throw a CORBA TRANSACTION_ROLLEDBACK exception when
the component issues a SetAbort. This
allows the component to raise a different
exception or pass back values to the
client.
Once the information is added, click
the OK button. The tx_outcome property will be visible in the Component
Properties dialog on the All Properties
tab. Once the property is added,
refresh the package or server through
the Jaguar Manager and retest the
application. When a withdrawal of too
much money is attempted, the transaction is aborted and the error message is displayed on the client, as
shown in Figure 4.
Conclusion
FIGURE 2
Component Properties for the Account component
FIGURE 3
New Property dialog
property, com.sybase.jaguar.component.tx_outcome, needs to be added to
the account component through the
Component Properties dialog on the All
Properties tab, as shown in Figure 2.
The tx_outcome property isn’t listed in
the All Properties list, but is available to
the component and set to “always” by
20
PBDJ volume7 issue2
A method that results in an aborted
transaction will throw the CORBA_
TRANSACTION_ROLLEDBACK exception.
To avoid throwing the exception and to
access the return value or output parameters on a method that’s part of an aborted
transaction, the Jaguar Component property com.sybase.jaguar.component.tx_outcome must be set to failed for each compo-
FIGURE 4
BTFBank sample application
default, which tells the Jaguar server
to throw a CORBA TRANSACTION_
ROLLEDBACK exception when a transaction is rolled back. In order to add the
property click on the Add button, which
will open the dialog shown in Figure 3,
type com.sybase.jaguar.component.tx_
outcome into the Property Name field
nent on the Jaguar server using the Jaguar
Manager. After setting the property, be sure
to refresh the component. The BTFBank
sample code used in this article is available
on my Web site at www.erols.com/m.barlotta under Code Samples. ▼
mike.barlotta@aegisconsulting.com
www.PowerBuilderJournal.com
Listing 1
//
//
//
//
//
//
//
//
//
//
//
END IF
Withdrawal
Args:
integer ai_accountid
decimal ad_amount
string as_message (BY REF)
Returns:
1 for OK
-1 for a SQL error
-2 for an error (account not found)
-3 for an error (insufficient funds)
Decimal ld_balance
integer li_result
// Clear Message
as_message = ""
SELECT
INTO
FROM
WHERE
balance
:ld_balance
account
id = :ai_accountid;
IF SQLCA.sqlcode <> 0 THEN
// An error occurred or the customer does not exist
IF SQLCA.SQLCode = 100 THEN
as_message = "Withdraw Failed - Account " +
String(ai_accountid)+ " was not found."
iel_jag.log(ClassName() + " SetAbort (withdraw - account
not found)")
li_result = -2
ELSE
as_message = "Withdraw Failed due to SQL Error."
iel_jag.log(ClassName() + " SetAbort (withdraw - SQL
Error)")
li_result = -1
iel_jag.log(SQLCA.SQLErrText)
its_jag.SetAbort()
ELSE
IF ad_amount > ld_balance THEN
// Insufficient funds
as_message = "Withdrawal Canceled due to Insufficient
funds."
iel_jag.log(ClassName() + " SetAbort (withdraw - insufficient funds)")
iel_jag.log(SQLCA.SQLErrText)
its_jag.SetAbort()
li_result = -3
ELSE
UPDATE account
SET balance = balance - :ad_amount
WHERE id = :ai_accountid;
IF SQLCA.sqlcode <> 0 THEN
as_message = "Withdraw Failed due to SQL Error."
iel_jag.log(ClassName() + " SetAbort (withdraw - SQL
Error)")
iel_jag.log(SQLCA.SQLErrText)
its_jag.SetAbort()
li_result = -1
ELSE
iel_jag.log(ClassName() + " SetComplete (withdraw)")
its_jag.SetComplete()
li_result = 1
END IF
END IF
END IF
RETURN li_result
!
d the Code
a
o
l
n
Dow
The code listing for this article can also be located at
www.PowerBuilderJournal .com
Sub Ad Note It All
www.sys-con.com
www.PowerBuilderJournal.com
www.note-it-all.com
PBDJ volume7 issue2
21
P B D J
WRITTEN BY BRIAN DAVIGNON
F E A T U R E
The next step in Sybase query tuning
O
kay, so you’ve used showplan, statistics i/o and statistics time to identify how your query has been optimized and what it’s costing you. That’s a good starting
point. Sometimes all the answers are right there in the
query plan or resource counts. They’ll point out a table
scan, the fact that your query is using a different index than you expected or that most of your I/O relates to one particular table or SQL statement. You know you have an index on the search columns, so why did it
choose a tablescan? You’ve done your homework and identified that one
index is more selective than another, yet the optimizer decided to access
the table with the less selective index. You suspect that one join order is
better than another, based on your knowledge of the data and relationships, but it didn’t choose that join order. Unfortunately, these tools
won’t help you in these situations because they fail to answer the question “Why?” They only answer the questions “How?” and “How much?”
You’ve reached the point in tuning your query where you need to dig
into your other toolbox and break out the dbcc traceflags. This article
will focus on traceflag 302, although it also includes output from 310.
The 302 traceflag provides information on index selection, while the 310
traceflag relates to join order selection. The definitions and commands
used for this article are shown:
create
(a int
b int
c int
d int
table abc
not null,
not null,
not null,
not null)
create index ix1
on abc(b,c)
create
(a int
b int
c int
d int
create index ix3
on xyz(a,b)
create index ix2
22
table xyz
not null,
not null,
not null,
not null)
PBDJ volume7 issue2
on abc(d)
(200,000 rows)
(1300 rows)
dbcc traceon(3604,302,310)
set showplan on
declare @d int
select @d = 2000
select abc.a, abc.b, abc.d, xyz.c, xyz.d
from abc, xyz
where abc.b=xyz.a
and abc.c=xyz.b
and abc.d > @d
The optimizer’s job is to find the least expensive access method given
the tables, indexes and query provided. The following output shows that
the optimizer is entering a scoring routine to determine how to access
table “abc.” Varno=0 simply means that “abc” is the first table listed in the
“from” clause. However “abc” won’t necessarily end up being first in the
chosen join order, because Sybase uses a cost-based optimizer. That cost
is measured in terms of physical and logical I/O, which is really what this
dbcc report is all about.
How much did the optimizer estimate that accessing a table in a certain manner would cost? Was it an accurate estimate? The report starts by
displaying the table name along with page and row counts. These counts
will be very close to actual values, possibly exact if dbcc checktable() has
been run recently. They will form the basis for estimating total I/O.
DBCC execution completed. If DBCC printed error messages, contact a
user with System Administrator (SA) role.
www.PowerBuilderJournal.com
*******************************
Entering q_score_index() for table 'abc' (objectid 1954626552,
varno = 0).
The table has 198060 rows and 1579 pages.
The optimizer will then score each sarg, or search argument, provided
in the query. Here it begins by scoring the “Greater Than” clause, which
references column “d” of table “abc”.
Scoring the SEARCH CLAUSE:
d GT
Regardless of what indexes are available, the optimizer will always
estimate the cost of doing a tablescan. It may sometimes turn out that a
tablescan will actually be faster than using existing indexes. For example,
the table may occupy only two data pages, but every index access
requires at least three or four pages to be read (root, intermediate, leaf
and data). Or your query may be returning a high percentage of the
table’s rows, which would certainly be faster using a tablescan.
In the next few lines of the report you can see that an estimate is being
performed against indid 0, which is always the base table, so this is an
estimate of pages that need to be read for a tablescan. The optimizer
estimates that 1,579 pages will need to be read, but it also shows that
these reads will be done with a 4K I/O size, which is two pages at a time.
Thus the actual cost will be 790 reads, not 1,579 reads. Keep that in mind
as you read through the output.
The optimizer is trying to reduce the number of reads the query must
perform, and using large I/O buffer pools can have a substantial impact
on the final estimate. For instance, if a large index is bound to a cache
that contains a 16K buffer pool, and another index, only half its size, is
not, then a query like “select count(*) from abc” will perform fewer reads
by scanning the leaf level of the larger index. (The reason is that you can
read eight times as many pages into cache with a single read.) Most of
your query’s time is spent waiting in the I/O queue, not actually performing the read, so if you can reduce the number of reads, you will
reduce the amount of wait time. That’s not to say that you should run out
and start adding large I/O buffer pools all over the place; it’s merely that
the optimizer will consider things like that when generating its estimates. (As you look over the following, you can ignore the Relop bits,
which is just a bitmap value representing the logical operator “>”. )
Base cost: indid: 0 rows: 198060 pages: 1579 prefetch: S
I/O size: 4 cacheid: 0 replace: LRU
Relop bits are: 11
This next section states that my sarg is using a subquery, expression or
local variable. In my case it’s a local variable, but in any case the problem is
that the optimizer doesn’t know the value of the sarg until runtime. It can’t
be determined at optimization time, even though I clearly initialize it to
2,000 before it gets to that statement.
TIP: This problem can easily be rectified by moving the “select” into its own
stored procedure, then passing that value into the stored procedure after it
has been initialized. Thus the optimizer can “see” those values before the
stored procedure is optimized. Unfortunately, that is not how the code was
written, so the optimizer can deal only with what’s in front of it.
There are two options for estimating the I/O cost in this situation: the
magicSC method and the densitySC method.
The magicSC scoring method uses default percentages based on the
logical operator. Using an equality operator (=), the optimizer estimates
that 10% of the rows will be returned. The estimate for a closed-end query
(>= and <=, >= and <, > and <=, > and <) will be 25%, and for an open-end
query (>, >=, <, <=) an estimate of 33% will be used. These estimates may
be considerably far from the truth, but the optimizer has no way of knowing this because I used a local variable. It can only “guesstimate.”
www.PowerBuilderJournal.com
The densitySC method can be used only if a valid distribution page
and density table exist. The density table is located on the distribution
page, and contains information on the percentage of duplicates across
various combinations of key columns. If an index contains columns a
and b, then an entry in the density table will exist for the percentages of
duplicates for the combination of columns a and b. This entry can be
used to further determine an index’s usefulness when a query contains a
sarg for both columns.
SARG is a subbed VAR or expr result or local variable (constat =
60)--use magicSC or densitySC
Sybase won’t consider using an index unless the search argument
includes at least the first column of that index. The only index that qualifies in that regard is indid 3, so it is the only other access method evaluated besides the tablescan. The magicSC scoring was used for this query,
since not knowing the value of the local variable rendered the density table
useless. The .33 selectivity of the index is a result of the open-end query
causing an estimate of 33% of the rows. The index height is 3, meaning that
it takes three reads to traverse the index tree, plus a data page read.
The optimizer has estimated that it will have to read a total of 65,653
index and data pages and that it will yield 65,360 rows. That’s probably pretty far off, but it wasn’t the optimizer’s fault – it was the coder’s fault for not
providing a sarg that the optimizer could use to generate a valid estimate.
Estimate: indid 3, selectivity 0.330000, rows 65360 pages 65653
index height 3
This process of evaluating indexes continues for all indexes that
could possibly satisfy the sarg, or that could be used to cover the query
in the case of a nonmatching index scan. The output in all cases would
be similar to the above, but if a valid sarg were provided, the report
would contain even more information about how the estimate was
made. (We will see this later when we look at the dbcc report of the corrected query.)
Once the optimizer has evaluated each index in turn, it will report on
which index was deemed to be cheapest, along with other details of the
access method. In our example below, it chose the only index that was
evaluated, indid 3, and indicates that it will cost 65,653 pages, generate
65,630 rows and use 2K I/O (no prefetch) in the data cache with id=0,
which is the default data cache.
Cheapest index is index 3, costing 65653 pages and
generating 65360 rows per scan, using no data prefetch (size 2)
on dcacheid 0 with LRU replacement
Search argument selectivity is 0.330000.
After all the sargs are evaluated, the optimizer turns to estimating the
cost of accessing each table on the join columns. Once those numbers
are in hand, there will be enough information to estimate the cost of performing the join in various orders. Note below that it’s estimating the
cost of accessing table “abc,” and has indicated which join clause it is
performing the estimate on. Once again it will evaluate the cost of doing
a tablescan, which is considered the “base cost.”
Entering q_score_index() for table 'abc' (objectid 1954626552,
varno = 0).
The table has 198060 rows and 1579 pages.
Scoring the JOIN CLAUSE:
b EQ a
c EQ b
Base cost: indid: 0 rows: 198060 pages: 1579 prefetch: S
I/O size: 4 cacheid: 0 replace: LRU
Relop bits are: 5
PBDJ volume7 issue2
23
The index selectivity is derived by dividing the estimate of rows by the
total rows in the table. (The estimate of rows will be discussed later when
we cover the distribution page and density table.) The optimizer has estimated that with an index height of 3, it’ll require 245 index and data page
reads to perform a single iteration of the join. This means that if 10 rows
qualify in the outer table, and table “abc” is chosen as the inner table, it
will require 2,450 reads against table “abc” and indid 2 to complete the
join. The join selectivity is determined by multiplying the index selectivity by the total rows, and then dividing total rows by the result (198060 /
(198060 * 0.001221)).
Estimate: indid 2, selectivity 0.001221, rows 242 pages 245 index
height 3
Cheapest index is index 2, costing 245 pages and
generating 242 rows per scan, using no data prefetch (size 2)
on dcacheid 0 with LRU replacement
Join selectivity is 819.187500.
Because the join can be performed with either table as the inner table,
the optimizer will estimate pages, rows and join selectivity for table “xyz”
as well. Based on a row total of 1,309 and an index selectivity of 0.000763,
the optimizer has estimated that one row would be returned for each
iteration of the join if “xyz” were the inner table chosen in the join. The
cost would be two index pages and one data page for each row returned.
Note: The join selectivity is a higher number for this table.
Entering q_score_index() for table 'xyz' (objectid 1922626438,
varno = 1).
The table has 1309 rows and 24 pages.
a EQ b
b EQ c
Base cost: indid: 0 rows: 1309 pages: 24 prefetch: S
I/O size: 4 cacheid: 0 replace: LRU
Relop bits are: 4
Estimate: indid 2, selectivity 0.000763, rows 1 pages 3 index
height 2
Cheapest index is index 2, costing 3 pages and
generating 1 rows per scan, using no data prefetch (size 2)
on dcacheid 0 with LRU replacement
Join selectivity is 1310.700000.
The fact that the row and page costs are significantly lower for table
“xyz” than they were for table “abc” does not guarantee it will be chosen
as the inner table. Those numbers refer only to a single iteration of the
join. The sargs will determine how many iterations will be performed. If
only five rows qualify for table “xyz” based on sarg estimates, and it costs
245 page reads for each iteration of the join against table “abc,” the total
cost of joining to table "abc" is only 1,225 pages. If 5,000 rows qualify for
table “abc” based on sarg estimates, and it costs one page read for each
iteration of the join against table “xyz,” the total cost of joining to table
“xyz” is 5,000 pages. The optimizer must consider the cost of accessing
each table based on sarg estimates, determine how many rows will be
returned and calculate the cost of joining the qualifying rows to the
inner table. The combination of these three things will determine the
final cost of processing the query.
You can imagine how much the optimizer must consider when there
are multiple sargs and several tables involved in joins. The output generated by these traceflags can be enormous, which is why I used a simple example so that we could focus on the individual components.
The 310 traceflag output below will have to be covered in detail another day, but for now I wanted to point out what type of information is contained in it.
“Query Is [Not] Connected” indicates whether or not the proper number of join clauses have been supplied to avoid a Cartesian product. The
output shows that two join orders were evaluated, “0-1” and “1-0”, indi-
24
PBDJ volume7 issue2
cating the varno (table) order. The lp and pp values are logical and physical I/O estimates based on the estimated rows returned for the sarg and
the join selectivity for the join columns on the inner table. The report
shows a second NEW PLAN cost, based on doing a tablescan on table
“abc” instead of using index ix2, but both estimates are based on the
same join order, with table “abc” as the outer table. The total cost is in
milliseconds, and is based on reads necessary to access all qualifying
rows in the outer table, reads necessary to perform the join, and includes
both data and index page reads. “Total Permutations” indicates how
many join orders were considered, and “Total Plans” includes all table
access methods and join orders considered.
“
Once the optimizer has evaluated
each index in turn, it will report on
which index was deemed to be
cheapest, along with other details
of the access method
”
QUERY IS CONNECTED
0 - 1 NEW PLAN (total cost = 552319):
varno=0 (abc) indexid=3 (ix2)
path=0xe44f0128 pathtype=sclause method=NESTED ITERATION
outerrows=1 rows=65360 joinsel=1.000000 cpages=65653 prefetch=N
iosize=2 replace=LRU lp=65653 pp=1579 corder=4
varno=1 (xyz) indexid=2 (ix3)
path=0xe44f05d0 pathtype=join method=NESTED ITERATION
outerrows=65360 rows=65275 joinsel=1310.700000 cpages=3 prefetch=N
iosize=2 replace=LRU lp=196079 pp=24 corder=1
jnvar=0 refcost=0 refpages=0 reftotpages=0 ordercol[0]=1 ordercol[1]=2
NEW PLAN (total cost = 413513):
varno=0 (abc) indexid=0 ()
path=0xe44f0128 pathtype=sclause method=NESTED ITERATION
outerrows=1 rows=65360 joinsel=1.000000 cpages=1579 prefetch=S
iosize=4 replace=LRU lp=1579 pp=1579 corder=0
varno=1 (xyz) indexid=2 (ix3)
path=0xe44f05d0 pathtype=join method=NESTED ITERATION
outerrows=65360 rows=65275 joinsel=1310.700000 cpages=3 prefetch=N
iosize=2 replace=LRU lp=196079 pp=24 corder=1
jnvar=0 refcost=0 refpages=0 reftotpages=0 ordercol[0]=1 ordercol[1]=2
1 - 0 TOTAL # PERMUTATIONS: 2
TOTAL # PLANS CONSIDERED: 7
CACHE USED BY THIS PLAN:
CacheID = 0:
(2K) 24 (4K) 1579 (8K) 0
(16K) 0
The “Final Plan” is the plan that will be executed. It details the join
order, page and row estimates, and the tablescan or index used for each
table’s access. It’s followed by the showplan output, which indicates a
tablescan was chosen for accessing table “abc” first, followed by a join to
table “xyz” using index 3.
www.PowerBuilderJournal.com
FINAL PLAN (total cost = 424170):
varno=0 (abc) indexid=0 ()
path=0xe44f0128 pathtype=sclause method=NESTED ITERATION
outerrows=1 rows=65360 joinsel=1.000000 cpages=1579 prefetch=S
iosize=4 replace=LRU lp=1579 pp=1579 corder=0
varno=1 (xyz) indexid=2 (ix3)
path=0xe44f05d0 pathtype=join method=NESTED ITERATION
outerrows=65360 rows=65275 joinsel=1310.700000 cpages=3 prefetch=N
iosize=2 replace=LRU lp=196079 pp=24 corder=1
jnvar=0 refcost=0 refpages=0 reftotpages=0 ordercol[0]=1 ordercol[1]=2
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is DECLARE.
QUERY PLAN FOR STATEMENT 2 (at line 2).
STEP 1
The type of query is SELECT.
QUERY PLAN FOR STATEMENT 3 (at line 3).
STEP 1
The type of query is SELECT.
FROM TABLE
abc
Nested iteration.
Table Scan.
Ascending scan.
Positioning at start of table.
Using I/O Size 4 Kbytes.
With LRU Buffer Replacement Strategy.
FROM TABLE
xyz
Nested iteration.
Index : ix3
Ascending scan.
Positioning by key.
Keys are:
a
b
Using I/O Size 2 Kbytes.
With LRU Buffer Replacement Strategy.
The corrected query is shown below, followed by some new information that’s now found in the dbcc output. This information shows
that index ix2, with indid=3, is now being evaluated based on statistics
that exist on the distribution page for that index, instead of using
default percentages based on the logical operator. In the real world I
would have broken the query into a separate proc, but the use of a
constant in place of a local variable will suffice for demonstration purposes.
select abc.a, abc.b, abc.d, abc.e, xyz.c, xyz.g
from abc, xyz
where abc.b=xyz.a
and abc.c=xyz.b
and abc.d > 2000
The excerpt below looks similar to the estimate of the cost of a
tablescan that we’ve examined already. However, in this case, since a
valid sarg was provided, the optimizer is now able to more accurately
estimate the number of pages and rows that match the sarg. This is
something that we haven’t seen yet.
Entering q_score_index() for table 'abc' (objectid 1954626552,
varno = 0).
The table has 198060 rows and 1579 pages.
Scoring the SEARCH CLAUSE:
d GT
www.PowerBuilderJournal.com
Base cost: indid: 0 rows: 198060 pages: 1579 prefetch: S
Relop bits are: 11
A distribution page (“stat page”) is the place where Sybase stores index
key value samples. The optimizer will try to use those sample values to
estimate how many rows will be returned. The output first shows us that
a qualifying distribution page was found. Its page number, 619,713, is
located in the column “distribution” of the sysindexes table. (The word
“qualifying” should be taken lightly, since the information contained on
the distribution page will be accurate only if the index was rebuilt or
Update Statistics was run recently.)
A step is merely a sample interval. The number of steps that can fit on a
2K distribution page is determined by the size of the index key. The output
indicates that this index was able to store 334 steps on its distribution
page.
Qualifying stat page; pgno: 619713 steps: 334
Search value: 2000
Let’s talk about steps a little bit before we move on. Smaller index keys
result in more steps, and therefore an estimate involving them will generally be more accurate. If a table has 100,000 rows and the distribution
‘‘
Depending on the logical
operator used, the optimizer
should therefore be no
more than 2,000 rows
off on its estimate
”
page can hold 100 steps, then every 1,000th key value will be stored,
including the first and last key values. Depending on the logical operator
used, the optimizer should therefore be no more than 2,000 rows off on
its estimate. If a smaller key is defined so that the same table’s distribution page can hold 1,000 steps, then every 100th key value will be stored
and the optimizer’s estimate should be no more than 200 rows off.
For instance, if a query searches for a value of 5,000, and the optimizer finds sequential steps contain values of 4,500 and 5,200, it knows that
all rows with a key value of 5,000 are contained within this one step
range. If it also knows that it has stored every 1000th key value as a step,
then it can estimate that it will return no more than 999 rows. The reason
it’s 999 and not 1,000 is that if row 1,000 contains key value 4,500 and row
2,000 contains key value 5,200, there could be 999 values in between.
Again, this is only an estimate, and the estimate is only as good as the
number of rows divided by the number of steps (roughly), and also
depends on how recently these statistics were updated. In this example
there are 198,060 rows and 334 steps, so roughly every 592nd key value
will be stored as a step.
Below we can see that an exact match was found on the distribution
page. In fact, the search value was found on two steps. In this case the
optimizer used the midseveralSC scoring method. In simple terms this
means that the optimizer takes into consideration that there may be
more rows with the target value prior to the first step where it was found,
PBDJ volume7 issue2
25
and there may also be more rows with the target value after the last step
where it was found. Based on that information, it has estimated 1,480
rows will be returned at a cost of 1,490 index and data pages. It estimates
that this index is the cheapest. However, since the estimate is more accurate this time, it’s not only the cheapest index, but it’s also less expensive
than the tablescan. Therefore, index 3 will be chosen as the access
method for table “abc” instead of a tablescan.
Match found on statistics page
equal to 2 rows on the statistics page in middle of page--use midseveralSC
Estimate: indid 3, selectivity 0.007473, rows 1480 pages 1490
index height 3
Cheapest index is index 3, costing 1490 pages and
generating 1480 rows per scan, using no data prefetch (size 2)
on dcacheid 0 with LRU replacement
Search argument selectivity is 0.007473.
I should also mention that prior to ASE 11.9, the steps contain values
only for the leading columns of the index. If you have a composite key
made up of two columns and your sarg references both columns, the
optimizer can use the density table mentioned earlier to more accurately estimate resulting rows. Remember that the density table contains a
percentage of duplicates found in combinations of composite key
columns. If the optimizer determines that 500 rows fall between two
steps for your search argument, and it also knows that the composite key
contains 10% duplicates, it can estimate 50 rows instead of 500. Of
course this estimate could be off as well, because it’s possible that all of
your duplicates for this index fall outside the values contained in these
steps.
I’ve omitted most of the remaining output since it was fairly redundant, but you can see below the new Final Plan, along with the more
English-like showplan. This plan was chosen because the total cost is
30,936, compared with the previous plan’s total cost of 424,170, so we
can expect a significant savings on response time as well. This cost estimate is milliseconds, not reads, but is based on 18ms physical and 2ms
logical I/O estimates. Whether those times are valid is trivial, since those
same weights are used when evaluating each access method.
FINAL PLAN (total cost = 30936):
varno=0 (abc) indexid=3 (ix2)
path=0xe38cb928 pathtype=sclause method=NESTED ITERATION
outerrows=1 rows=1480 joinsel=1.000000 cpages=1490 prefetch=N
iosize=2
replace=LRU lp=1490 pp=1046 corder=4
varno=1 (xyz) indexid=2 (ix3)
path=0xe38cbdd0 pathtype=join method=NESTED ITERATION
outerrows=1480 rows=1473 joinsel=1310.700000 cpages=3 prefetch=N
iosize=2
replace=LRU lp=4438 pp=14 corder=1
jnvar=0 refcost=0 refpages=0 reftotpages=0 ordercol[0]=1 ordercol[1]=2
QUERY PLAN FOR STATEMENT 1 (at line 1).
STEP 1
The type of query is SELECT.
FROM TABLE
abc
Nested iteration.
Index : ix2
Ascending scan.
Positioning by key.
Keys are:
d
Using I/O Size 2 Kbytes.
With LRU Buffer Replacement Strategy.
FROM TABLE
26
PBDJ volume7 issue2
xyz
Nested iteration.
Index : ix3
Ascending scan.
Positioning by key.
Keys are:
a
b
Using I/O Size 2 Kbytes.
With LRU Buffer Replacement Strategy.
There is a wealth of information that can be derived from the traceflags, too much to cover in this space. Here are a few of the things that
the traceflags can help point out, along with some possible reasons for
their occurrence.
• Optimizer did not find a qualifying stat (distribution) page.
1. Is there no valid index for the sarg?
2. Are your index statistics missing?
3. Was a large temp table not indexed?
• An erroneous estimate was made for an index.
1. Large number of rows causing huge range of keys between steps?
2. Large key causing too few steps to be stored?
3. Too many columns or too large a composite key size? (This can produce a large density table, which reduces amount of step information
that can be stored on distribution page.)
4. Erratic key distribution in your table (e.g., five rows for one key value,
100,000 for another key value)?
• A subquery, expression or local variable was used as a sarg.
1. As in our example, optimizer must rely on default percentages.
‘‘
Effective use of the
traceflags requires that
you have a firm grasp of
index and table design
”
I hope this introduction has motivated you to add the traceflags to
your tuning toolbox. As with any tool, they are only as good as their user.
Using state-of-the-art automotive diagnostic tools won’t turn you into a
mechanic, but if you’re already an accomplished mechanic, having better tools can certainly make you a more successful one and save you
time and energy. Effective use of the traceflags requires that you have a
firm grasp of index and table design and a basic understanding of distribution pages and density tables, as well as knowledge and experience in
various tuning methods. It won’t always be as easy as adding an index,
updating statistics or removing a local variable. But I think with these
tools you will find it easier to get to the root of the problem, and that’s the
first step in solving it. ▼
briand@soaringeagleltd.com
www.PowerBuilderJournal.com
F
R
O
M
S
Y
B
A
S
E
Extending PowerBuilder Applications to the Web
Use application server technology to make e-business a reality
ver the past year many companies have been leveraging their PowerBuilder skills and investments to take their businesses to the Web. Sybase’s Enterprise Application Server (EAServer)
has enabled these companies to quickly and easily make e-business a reality.
O
WRITTEN BY
ROB VEITCH
Blue Cross and Blue Shield of Rhode
Island (BCBSRI) is one customer who’s
leveraged PowerBuilder and EAServer to
develop an intranet solution. This application has provided BCBSRI with
tremendous cost savings and enhanced
performance.
Using Data to Everyone’s Advantage
BCBSRI is the state’s largest and only
locally based health plan. Founded in
1939, BCBSRI provides health coverage
for one out of every two Rhode
Islanders. With more than 1,500 employees, it’s also one of the 20 largest
employers in the area.
Customer-Driven Solutions
AUTHOR BIO
Rob Veitch is the director
of business development
at Sybase Internet
Applications Division.
BCBSRI is driven by the needs of its
customers. Toward this end, the company relies on its customer service system
to monitor and record communication
with its policyholders. BCBSRI’s mission
is to be the local leader in coordinating
and financing health-related benefits
and providing quality health care and
service in Rhode Island.
www.PowerBuilderJournal.com
To help achieve the company’s
mission, BCBSRI’s IT department was
tasked with replacing its existing customer service system, which was proprietary and non-Y2K compliant. In
an effort to serve its customers better
and improve operational efficiency,
the company decided to take its
existing customer service application
and deploy it on a three-tier architecture.
Moving to Three Tiers
BCBSRI originally explored the possibility of implementing an off-the-shelf
CRM application but soon realized that
this option would cost in excess of $1 million. Moreover, by integrating the new
application, BCBSRI would have incurred
additional costs and resources. The company’s IT department convinced management that the best approach would
be to use the company’s existing PowerBuilder skill sets and investments and
rebuild the customer service system
instead.
Built with Sybase’s EAServer and
PowerBuilder, the newly developed
intranet application allows a variety of
lightweight clients to use business logic
that sits in the middle tier on EAServer.
The company chose EAServer and
PowerBuilder because of their rapid
development capabilities, scalability
and tight integration across BCBSRI’s
infrastructure.
EAServer combines the capabilities of
a component transaction server and a
dynamic Web page server to provide a
single point of integration for heterogeneous back-office systems. This has
allowed BCBSRI to extend its existing
PowerBuilder-based system and data
safely and easily to a three-tier environment. With EAServer, BCBSRI could
integrate its legacy and new-customer
service data with the company’s existing
logic, which was located on an IBM
mainframe. EAServer now acts as the
focal point for access to several IBM
legacy applications within the company,
including IMS and DB2.
Significant Savings
By using its existing resources and skill
sets, BCBSRI was able to implement a
customer service system with a cost savings of more than $500,000. Because the
new system borrowed from the previous
system’s user interface, the company
also saved time and money by not having to retrain users on a new application.
The new EAServer-based system supports BCBSRI’s 120-user customer service department, managing 1,800 calls
per day and generating approximately
20,000 transactions – a significant performance gain over the previous customer service solution.
Application server technology is relatively new to the IT market, and organizations are still trying to grasp its true
purpose in the enterprise. Those who
can recognize the value of integrating
existing data and applications stand to
gain significant competitive advantage
as they move to an e-business model.
As an innovative e-business implementation, BCBSRI’s newly enhanced customer service system offers a clear
example of how application server
technology can be used to an organization’s fullest advantage – with a beneficial impact on customers as well.
Are you considering the advantages of
an application server in designing your
business’s Web architecture? More and
more PowerBuilder shops are discovering
that EAServer is uniquely suited for
deploying business logic via PowerBuilder
NVOs in intranet- and Internet-based
applications, as well as JavaBeans, COM
and CORBA objects created by other
development teams. You can find more
success stories at www.sybase.com/success. Do you have an EAServer success
story you’d like to share with us? If so, send
an e-mail to ndavis@sybase.com. ▼
veitch@sybase.com
PBDJ volume7 issue2
27
P
F
C
C
O
R
N
E
R
Debugging DataWindows – Live!
Useful PFC extensions
ou stumble on a bug in your application. In order to troubleshoot it you need to see the raw data
inside your DataWindow but the columns you need aren’t visible. ••• You open a window in your
application, and even though you don’t change any of its data it still prompts you to save when closing. What has changed? ••• A drop-down DataWindow isn’t displaying the right values at runtime,
but it retrieves just fine when you preview it at design time.Where’s the data? ••• You’re pulling your
hair out trying to get some modify/describe syntax to work, and it’s taking forever between changing
your code, saving and rerunning your application. Can you possibly get the syntax right and still get
home on time?
Y
WRITTEN BY
VINCE FABRO
The solution to all of these problems
can be found in the PFC DataWindow
properties dialog – after we make some
enhancements, that is.
The DataWindow Properties Dialog
The PFC originally included a
DataWindow debug window, which I
found very helpful. With the major
changes that came in PFC 6.0, the debug
window was replaced by the DataWindow properties dialog, which both added
and removed functionality. (I needn’t
mention PFC 7.0 because of the paucity
of changes.) One thing I like about the
PFC 6.0 dialog is that it’s easy to add new
debugging capabilities simply by extending existing tabs and adding new tabs. In
this article I’ll describe a useful enhancement to some existing DataWindow
properties tabs, which essentially
restores a PFC 5.0 feature that was
removed in PFC 6.0. In addition, we’ll add
two new DataWindow properties tabs: a
drop-down DataWindow tab and a Modify/Describe tab.
But first, if you aren’t familiar with the
DataWindow properties dialog, then
obviously you don’t know what you’re
missing! In order to access the dialog you
must enable the “property” service. You
can do this either by calling of_SetProper-
FIGURE 1 The DataWindow popup menu
28
PBDJ volume7 issue2
ty or of_SetSharedProperty. I call of_SetSharedProperty because with one call I
instantiate a shared service (referenced
with the shared variable snv_Property),
which makes the service accessible to all
DataWindow controls in the entire application. (The function of_SetProperty, on
the other hand, instantiates the same service but stores the reference in an
instance variable so that it’s accessible
only to the current DataWindow control.)
After the service is instantiated at runtime, you can right-click on your
DataWindow control and the “DataWindow Properties…” option will be visible in
the popup menu (see Figure 1).
Selecting the “DataWindow Properties…” option will open the DataWindow Properties dialog and its Services
tab page, as in Figure 2. (Note: These figures show the DataWindow properties
dialog after making the enhancements
in this article.) I rarely use the Services
tab, but I frequently use the “Buffers”
and “StatusFlags” tabs (see Figures 3
and 4, respectively). Try them out.
They’re both pretty self-explanatory,
and there’s even on-line PFC help!
FIGURE 3 The DataWindow Properties
dialog buffers tab page
FIGURE 2 The DataWindow Properties
dialog services tab page
FIGURE 4 The DataWindow Properties
dialog StatusFlags tab page
www.PowerBuilderJournal.com
FIGURE 5 The u_dw KeyDown Event
Now that I’ve finished explaining the
“standard” way to open the DataWindow properties dialog, I can wink and
nod and tell you how I really open it.
The problem I have with calling of_SetSharedProperty or even of_SetProperty
is that they both enable a menu option
in a popup menu that users have access
to. I don’t want my users saying, “I wonder what that ‘DataWindow Properties’
thingy does?” So I coded a secret key
sequence that opens the DataWindow
properties dialog. To maintain secrecy,
I’ll include a simplified version here.
Open u_dw and insert a new event
called “KeyDown,” mapped to the
pbm_dwnkey event ID (see Figure 5).
Then add the following code to that
event:
// Event KeyDown:
// If you press Shift-Ctrl-Alt-D, the
//DataWindow property window will
// display
if Key = KeyD! and keyflags = 3 and
& KeyDown (KeyAlt!) then
// Turn on the property service for
// all DWs.
this.of_SetSharedProperty // (true)
this.Event pfc_Properties ()
end if
// End if the User hit the // right
key combo.
return 0
With this code in u_dw you won’t
have to write any additional code to
access the DataWindow properties dialog, and your users won’t have easy
access to it. All you have to do is set
focus on your DataWindow control, hit
Shift-Ctrl-Alt-D and the DataWindow
properties dialog will open!
Now that we’re all at least vaguely
familiar with the DataWindow properties dialog, let’s add some cool debugging capabilities.
quently was the ability to view all of the
raw data in any DataWindow. Notice in
see Figures 3 and 4 that there are radio
buttons labeled “Original” and “All
Columns” – those options aren’t available in the base PFC. These figures show
the original view, which uses the same
DataWindow object that your user sees.
Notice in Figures 6 and 7, however, that
every column’s data is visible and
editable. That’s what allows me to peer
inside any DataWindow’s data, and it’s
all at runtime!
Let’s start by extending the Buffers
tab. The GUI is quite simple, but the
code is fairly involved.
1. Open u_tabpg_dwproperty_buffers.
2. Add two radio buttons(rb_original
and rb_all) at the bottom of the
DataWidow.
3. Set the radio buttons’ text and default
rb_original to checked.
4. Shrink the DataWindows to make
room for the radio buttons.
5. Place an invisible group box around
the radio buttons. (This step is
optional, but it’s good technique.)
6. Write the following code for rb_original.
parent.Event pfc_PropertyInitialize
(inv_attrib)
7. Write the code for rb_all (see Listing
1).
8. Save and close the tab page. (Note: I
had to regenerate pfc_w_dwproperty
and w_dwproperty because otherwise
I got a runtime error.)
Extending the Buffers and
StatusFlags Tabs
The steps are the same for the StatusFlags tab, but the code for rb_all is
slightly different. One difference is that
this tab page has only one DataWindow
(dw_requestorview), and the second
difference is that this tab page has no
radio buttons for selecting which buffer
to display. Basically, the code for rb_all
is identical to what is on the Buffers tab,
except that the following two sections
need to be removed.
1. The code that refers to dw_requestorduplicate:
Some useful features were lost when
the PFC 5.0 DataWindow debugger
became the PFC 6.0 DataWindow properties dialog. One that I used quite fre-
dw_requestorduplicate.Create(ls_dw_sy
ntax,ls_error)
if len(ls_error) > 0 then
www.PowerBuilderJournal.com
FIGURE 6 The “All Columns” View on
the Buffers Tab
FIGURE 7 The “All Columns” View on
the StatusFlags Tab
MessageBox("Create:",ls_error)
return
end if
idw_Requestor.ShareData(dw_requestorv
iew)
2. The code that refers to the buffer
radio buttons:
if rb_primary.checked then
Parent.Event pfc_PropertyBufferChanged(Primary!)
elseif rb_deleted.checked then
Parent.Event pfc_PropertyBufferChanged(Delete!)
else
Parent.Event pfc_PropertyBufferChanged(Filter!)
end if
Finally, some limitations to this implementation of the “All Columns” radio
button are worth noting.
1. The code for rb_all assumes that the
DataWindow uses SQLCA as its trans-
PBDJ volume7 issue2
29
P
action object, which may not be the
case. If needed, you can extend u_dw
and add an of_GetTransObject.
2. As noted in the code, the section of
code that replaces retrieval arguments doesn’t work with all DBMSs.
Different DBMS vendors have different formats for some data types, and
datetime values are what I’ve had
problems with in the past. In my own
framework (the NewMedia PFC) I use
different formats depending on the
DBMS.
3. Even though the Buffers tab displays
data from the Filter! and Delete!
buffers, editing that data won’t actually change data in the main
DataWindow.
4. The “Original” and “All Columns”don’t
view display drop-down DataWindows.
Regardless of these limitations, the
Buffers and StatusFlags tabs are my
most frequent reason for using the
DataWindow properties dialog. The
ability to see the raw data inside a
DataWindow at runtime is awesome!
FIGURE 8 The drop-down DataWindow tab page
The Drop-Down DataWindow Tab
I can safely say that half of the DataWindows in my applications include dropdown DataWindows. That’s probably pretty common. For the most part, using dropdown DataWindows is quite straightforward. But we spend very little time debugging straightforward things. The reason I
created a drop-down DataWindow properties tab is because there are many occasions in my applications where drop-
30
PBDJ volume7 issue1
F
C
downs are filtered for one reason or another. Unfortunately, there have also been
occasions where those filtered dropdowns don’t display the expected values.
The drop-down DataWindow tab in Figure
8 permits me to see inside my dropdowns. The tab page’s GUI is not too difficult. It’s based on the Buffers tab page,
which is the second tab in the DataWindow Properties dialog. The master
DataWindow lists all the drop-downs in
the DataWindow being debugged. The
detail DataWindow displays the contents
of the selected drop-down DataWindow.
Then there are radio buttons for selecting
which buffer to display, and buttons for
sorting and filtering. Because it’s based on
the Buffers tab, you can start by saving the
Buffers tab under a different name. Open
pfc_u_tabpg_dw-property_buffers
in
PFCUTIL.PBL
and
save
it
as
u_tabpg_dwproperty_childdws in PFEUTIL.PBL. (Actually, mine lives in an extension layer between the PFC and the PFE.)
Then make the following changes to
the GUI:
1. Change the GUI by deleting the extraneous controls in the group box.
2. Add the “Child DataWindow:” text
above the detail DataWindow.
3. Resize and rearrange the controls to
make room for the master DataWindow (note that there are actually two
detail DataWindows – one is for displaying the Primary! buffer, and the
other is for the Filter! and Delete!
buffers.).
4. Add the “Drop-down DataWindow
Columns:” text at the top of the tab
page.
5. Add the master DataWindow
(dw_children, based on u_dw).
6. Create an external grid DataWindow
object named d_dwproperty_childdws that has two char(40) columns
(“colname” and “dataobject”).
7. Set the DataObject property of
dw_children to d_dwproperty_childdws.
8. Delete the Undelete button.
And now for the code. Declare an
instance variable of type dwBuffer called
idwb_CurrentBuffer. This will keep track
of the selected buffer (Primary!, Filter! or
Delete!).
Insert a new function called of_LoadChild, which takes a row (al_row) as its
argument. This function will load the
selected child DataWindow object into
the detail DataWindows and refresh the
view (see Listing 2).
The tab page will already have code in
the following events: pfc_PropertyBufferChanged, pfc_PropertyInitialize, pfc_PropertyOpen, pfc_PropertyStats and pfc_PropertyUndelete. You can delete the code from
C
O
R
N
E
R
the pfc_PropertyInitialize and pfc_PropertyUndelete events. Then make the following changes:
1. Change pfc_PropertyBufferChanged
by deleting all references to
cb_undelete.
2. Change pfc_PropertyStats to the code
in Listing 3).
3. Use the script in Listing 4 for
pfc_PropertyPopulate.
4. Add the following code to the master
DataWindow’s RowFocusChanged
event:
// Event RowFocusChanged:
// Load the newly selected child
DataWindow.
return parent.of_LoadChild (currentrow)
5. Change the code for dw_requestorduplicate by deleting all scripts except
for the constructor event and set the
tab page’s Text property to “DDDWs”
on the General tab. And that’s it for the
tab page! But in order to use it you’ll
have to add it to the DataWindow
properties tab control.
Close and save the tab page, then
open u_tab_dwproperties in PFEUTIL.PBL. Right-click near the top of
the tab and select “Insert User Object”
(see Figure 9). Select u_tabpg_
dwproperties_childdws and click OK.
On the General tab of the Properties
pane, name the tab page “tabpage_
dddws.” Close and save the tab control. Done! Now you can run your
application and test it out. (Note:
Once again I had to regenerate
pfc_w_dwproperty and w_dwproperty. Otherwise I got a runtime error.)
Don’t forget to set focus on your
DataWindow and type the Shift-CtrlAlt-D shortcut to display the DataWindow Properties window.
FIGURE 9 Adding the DDDWs tab
page
www.PowerBuilderJournal.com
FIGURE 10 The DataWindow Properties
modify tab
The Modify/Describe Tab
The power of Modify and Describe
never ceases to amaze me. But occasionally I have a lot of trouble getting the syntax to work as I’d like. (And I suspect I’m
not alone in that experience!) In these situations the PowerBuilder debugger is
rarely helpful, and the DataWindow
painter isn’t always helpful either. Then
one day a light went on and I realized
how easy it would be to build a Modi-
fy/Describe debugging tab. It took very
little time to build, as you’ll see, and it can
be quite helpful.
Create a new tab page by inheriting
from u_tabpg_dwproperty_base in PFEUTIL.PBL. The most difficult part about
constructing this tab page is the GUI (see
Figure 10). Make it the same size as the
other tabs (1198x1200). Add some text
controls, a couple of multiline edits
(mle_syntax and mle_result), few buttons
and an edit mask (em_position). Don’t forget to inherit from base PFC controls!
Finally, set the Text property on the tab
page’s General tab to “Modify.”
The code couldn’t get any easier.
Here’s the code for the Modify button:
(em_position.text), 0)
mle_syntax.SetFocus ()
mle_result.text = idw_requestor.Modify ( &
mle_syntax.text)
Probably the biggest benefit from
using a framework such as the PFC is
productivity gains from code reuse.
But why shouldn’t your framework
also help you debug and troubleshoot
problems? So I hope you find these
enhancements to the DataWindow
properties dialog a useful addition to
your PFC. And to take PFC debugging
one step further, I’d suggest that you
check into the debug service and SQL
Spy. (How about Shift-Ctrl-Alt-S?) ▼
Here’s the code for the Describe button:
mle_result.text =
idw_requestor.Describe ( &
mle_syntax.text)
And here’s the code for the Go to button:
mle_syntax.SelectText (Integer
Listing 1
// Clicked for rb_all// Clicked for rb_original.
int li_Arg, li_NumArgs, li_Pos, li_Col, li_Cols
string
ls_sql_syntax, ls_dw_syntax, ls_error, ls_Arg
string
ls_ArgNames[], ls_ArgTypes[], ls_TmpSyntax
u_dwldw_remote
n_cst_string
lnv_string
if IsNull(idw_Requestor) then return
if not IsValid(idw_Requestor) then return
SetPointer(hourglass!)
ls_sql_syntax = idw_Requestor.Describe( &
'DataWindow.Table.Select')
// Make sure we have a database connection.
if SQLCA.DBHandle () = 0 then
MessageBox ("All Columns:", &
"Transaction not connected!", StopSign!)
rb_original.checked = true
return
end if
// Make sure we have a valid SQL statement.
if Pos (ls_sql_syntax, "PBSELECT") > 0 then
idw_Requestor.SetTransObject (SQLCA)
ls_sql_syntax = idw_Requestor.Describe( &
'DataWindow.Table.Select')
end if
// Replace any args so that we don't get prompted.
ldw_remote = idw_Requestor
ldw_remote.of_SetBase (true)
ldw_remote.inv_base.of_dwArguments (ls_ArgNames, &
ls_ArgTypes)
www.PowerBuilderJournal.com
The purpose of the Go to button is to
help in those situations when you get an
error message from your Modify/Describe
call such as “Line 1 Column 14: incorrect
syntax.” The line and column numbers are
sometimes useful, but a more advanced
“Go to” that accepted a line and column
number would be more useful.
Save the tab page as u_tabpg_dwproperties_modify, close the painter and add it
to u_tab_dwproperties just as before.
(Don’t forget to regenerate pfc_w_dwproperty and w_dwproperty!)
Closing Thoughts
fabro@nmedia.com
ABOUT THE AUTHOR
Vince Fabro is a practice
leader at the Columbus
branch of NewMedia, a
consulting firm
headquartered in
Cleveland, Ohio. He’s a
CPD professional and
certified PowerBuilder
instructor who has been
using PowerBuilder since
version 2.
// Loop through the arguments.
li_NumArgs = UpperBound (ls_ArgNames)
ls_sql_syntax = lnv_string.of_GlobalReplace ( &
ls_sql_syntax, "~~~"", "~"")
for li_Arg = 1 to li_NumArgs
// Copy the SQL so we can search it in lower case.
ls_TmpSyntax = Lower (ls_sql_syntax)
li_Pos = Pos (ls_TmpSyntax, ":" + ls_ArgNames[li_Arg])
if li_Pos > 0 then
choose case ls_ArgTypes[li_Arg]
// CHARACTER DATATYPE
CASE "string"
ls_Arg = "''"
// Empty string.
// DATE DATATYPE
CASE "date"
ls_Arg = "'1900-01-01'"
// Empty date.
// DATETIME DATATYPE
CASE "datetime"
// NOTE: This is a simplification,
// and won't work with all DBMSs.
ls_Arg = "'1900-01-01'"
// Numeric datatypes
CASE "decimal", "number", "long", "ulong"
ls_Arg = "0"
// Default to zero.
// TIME DATATYPE
CASE "time", "times"
ls_Arg = "'12:00:00'" // Default to noon.
end choose
// Change the SQL syntax.
ls_sql_syntax = &
PBDJ volume7 issue2
31
Left (ls_sql_syntax, li_Pos - 1) + &
ls_arg + &
Mid (ls_sql_syntax, li_Pos + &
Len (ls_ArgNames[li_Arg]) + 1)
end if
next
// Get the DataWindow syntax for this
ls_dw_syntax = SQLCA.SyntaxFromSQL( &
ls_sql_syntax, "Style(Type= Grid )",ls_error)
if Len(ls_error) > 0 then
MessageBox("SyntaxFromSQL:",ls_error)
return
end if
// Put this DataWindow in the two DW controls.
dw_requestorview.ShareDataOff()
dw_requestorview.Create(ls_dw_syntax,ls_error)
if len(ls_error) > 0 then
MessageBox("Create:",ls_error)
return
end if
dw_requestorduplicate.Create(ls_dw_syntax,ls_error)
if len(ls_error) > 0 then
MessageBox("Create:",ls_error)
return
end if
idw_Requestor.ShareData(dw_requestorview)
li_rc = ldwc_temp.ShareData (dw_requestorview)
this.Event pfc_PropertyBufferChanged( &
idwb_CurrentBuffer)
// Update the onscreen stats.
this.Event pfc_PropertyStats()
return li_rc
Listing 3
// Se// Event pfc_PropertyStats:
t the text for the radio buttons to keep
// track of row counts.
if dw_requestorview.DataObject = "" then return 0
rb_primary.Text = &
string(dw_requestorview.RowCount()) + &
" &Primary"
rb_filtered.Text = &
string(dw_requestorview.FilteredCount()) + &
" &Filtered"
rb_deleted.Text = &
string(dw_requestorview.DeletedCount()) + &
" &Deleted"
Return 1
Listing 4
// Make sure the data is editable.
// NOTE: Only the primary buffer will be editable.
ls_Arg = dw_requestorview.Describe("#1.TabSequence")
if ls_Arg <> "10" then
// Loop thru the cols and assign a tab order.
li_Cols = Integer (dw_requestorview.Describe ( &
"DataWindow.Column.Count"))
for li_Col = 1 to li_Cols
dw_requestorview.Modify ( &
"#" + String (li_Col) + ".TabSequence = " + &
String (li_Col))
next
end if
// Event pfc_PropertyPopulate:
// Load the child DW objects into the master DW.
int
li_Col, li_Cols
long
ll_row
string ls_Columns[], ls_Describe, ls_DDDWName
// Update the statistics and the buffer.
parent.Event pfc_PropertyStats ()
if rb_primary.checked then
Parent.Event pfc_PropertyBufferChanged(Primary!)
elseif rb_deleted.checked then
Parent.Event pfc_PropertyBufferChanged(Delete!)
else
Parent.Event pfc_PropertyBufferChanged(Filter!)
end if
// Loop through the columns.
for li_Col = 1 to li_Cols
// Get the edit style.
ls_Describe = idw_requestor.Describe ( &
ls_Columns[li_Col] + ".Edit.Style")
Listing 2
// Function of_LoadChild (long al_row):
// Load a child DW object into the detail DWs.
Datawindowchild
ldwc_temp
int
li_rc
string
ls_Column, ls_DataObject
if IsNull (al_row) or al_row <= 0 or &
al_row > dw_children.RowCount () then
return –1
end if
// Get the column and datawindow object.
ls_Column = dw_children.GetItemString (al_row, &
"colname")
ls_DataObject = dw_children.GetItemString ( &
al_row, "dataobject")
// Get a reference to the child datawindow.
Li_rc = idw_requestor.GetChild (ls_Column, ldwc_temp)
if li_rc < 0 then return -1
// Set the DataObject of the bottom datawindow.
dw_requestorview.ShareDataOff ()
dw_requestorview.DataObject = ls_DataObject
dw_requestorduplicate.DataObject = ls_DataObject
32
PBDJ volume7 issue2
// Use the base DataWindow service to get a list
// of the columns in the DataWindow.
if not IsValid (idw_requestor.inv_base) then
idw_requestor.of_SetBase (true)
end if
li_Cols = idw_requestor.inv_base.of_GetObjects ( &
ls_Columns, "column", "*", false)
// Only dddws need apply.
if ls_Describe <> "dddw" then continue
// Get the name of the dddw.
ls_DDDWName = idw_requestor.Describe ( &
ls_Columns[li_Col] + ".dddw.Name")
if ls_DDDWName = "!" or ls_DDDWName = "?" or &
Trim (ls_DDDWName) = "" then continue
// Insert this dddw into the list.
ll_row = dw_children.InsertRow(0)
dw_children.SetItem (ll_row, "colname", &
ls_Columns[li_Col])
dw_children.SetItem (ll_row, "dataobject", &
ls_DDDWName)
next
if dw_children.RowCount() > 0 then
this.of_LoadChild (1)
end if
!
d the Code
a
o
l
n
Dow
The code listing for this article can also be located at
www.PowerBuilderJournal .com
www.PowerBuilderJournal.com
PowerBuilder News
All things of interest to the PB community
BY
BRUCE
ARMSTRONG
bruce.armstrong@eudoramail.com
12/07/1999
11/29/1999
Sybase released Enterprise Application Server (EAServer) 3.5,
which adds support for the Java 2 Platform Enterprise Edition
(J2EE) standard. Sybase also introduced new EAServer pricing.
Certicom Corp. announced that Sybase licensed security
technology from them for use in future product enhancements.
Certicom is a leading provider of next-generation encryption
technology used to build strong, fast and efficient security solutions. Their technology is used in electronic commerce software, wireless messaging applications and smart cards.
12/06/1999
Sybase announced the Early Adopter release of an Enterprise
Portal (EP) solution, code-named Sybase OpenDoor.
12/01/1999
Sybase announced the signing of a definitive agreement to
acquire Home Financial Network (HFN) from InteliData Technologies Corp. HFN will be established as an independent,
wholly owned subsidiary of Sybase. Sybase will acquire HFN
for a combination of cash and an agreed number of shares of
Sybase stock with a total value of approximately $130 million.
11/30/1999
Sybase joined the Wireless Application Protocol (WAP) forum,
the industry association focused on developing information and
telephony services for digital mobile phones and other wireless
terminals. As a part of the WAP forum, Sybase will work with
other forum members to define the standards for wireless ebusiness applications.
11/24/1999
MicroStrategy Incorporated announced a multiyear licensing agreement with Sybase that allows MicroStrategy to offer
Sybase’s Industry Warehouse Studio as part of its Intelligent EBusiness Platform.
11/22/99
Sybase released the results of a significant performance test
with Adaptive Server IQ 12. In the test a 100 gigabyte benchmark
of IQ(12)-32 bit ran on a Sun 3500 with eight CPUs and 4 gigabytes of memory versus an Informix Dynamic Server AD 8.2, 64
bit running on a Sun 4500 with 12 CPUs and 12 gigabytes of
memory. Sybase obtained superior performance and throughput
using 60% less memory and 30% less CPU than Informix, while
still only using the 32-bit version of IQ(12).
ADVERTISINGINDEX
Cendant
www.cmjobs.com
www.PowerBuilderJournal.com
ADVERTISER
URL
PH
PG
DUTTON SOFTWARE
WWW.DUTTONSOFTWARE.COM
805.375.0789
2
E. CRANE COMPUTING
WWW.ECRANE.COM
603.226.4041
11
JAVACON 2000
WWW.JAVACON2000.COM
JDJ STORE
WWW.JDJSTORE.COM
888.303.JAVA
9
LECCO TECHNOLOGY
WWW.LECCOTECH.COM
852.2527.0330
15
RIVERTON SOFTWARE CORPORATION
WWW.RIVERTON.COM
781.229.0070
39
STARBASE CORPORATION
WWW.STARBASE.COM
888.STAR700
4
SYBASE
WWW.SYBASE.COM/PRODUCTS/POWERBUILDER
978.287.1871
40
SYBASE TECHWAVE
WWW.SYBASE.COM/TECHWAVE2000
781.278.2607
17
SYS-CON PUBLICATIONS,INC.
WWW.SYS-CON.COM
800.513.7111
21
XML DEV. CON
WWW.XMLDEVCON2000.COM
13
PBDJ volume7 issue2
19
33
I N T R O D U C T I O N T O P O W E R B U I L D E R
Using the PFC MultitableUpdate Service
The PFC multitable service made simple
I
n this month’s column I’ll show you how to use the PFC multitable update service.This service, as you
might expect, allows the DataWindow to update more than one table. Multitable update, one of the
least used PFC services, has been around since version 5.0. I’ll demonstrate how you can use it, how
it works and pitfalls you may find along the way.
WRITTEN BY
BOB HENDRY
How the DataWindow Updates
Before I introduce the DataWindow
multitable update service, it’s important to understand the default way that
DataWindows handle updates. When a
an update is attempted, a PowerBuilder
system message box will be displayed,
informing the user that updates can’t be
made to this DataWindow. Also, if this
button is left unchecked, the DataWindow won’t maintain a delete buffer.
Where Clause for Update/Delete
You can indicate here how the
DataWindow will determine if an update
was successful. This groupbox is used to
specify which of three techniques should
be used to construct the Where clause on
the Update statement to implement
optimistic locking. These techniques
vary in the degree of data integrity they
ensure. (See Table 1 for a more detailed
description.)
Where Clause Criteria – Key Columns
FIGURE 1
Specify Update Properties
DataWindow is created, it contains a
list of items called Update Properties.
These can be viewed via the Rows >>
Update Properties menu item within
the DataWindow painter (see Figure 1).
Simply put, this is where you, the programmer, tell this DataWindow how the
updates are going to work. A more
descriptive list of the Update Properties
is given below.
Where Clause Criteria Meaning
Meaning
Key
Only the key column(s) have to match their original
Allow Updates
Key and Updatable Columns
This checkbox specifies whether or
not this DataWindow will be updatable.
Obviously, if you want the user to be able
to update the DataWindow, the box must
be checked. If the box isn’t checked and
34
The Key criterion specifies that only the
key column(s) must match their original
value(s) for the update to be performed.
The major concern with this technique is
the potential for lost updates.
In Figure 2, Users 1 and 2 retrieve a
row at the same time and both intend to
modify the last name in the orders table.
If User 1 makes the change, and User 2 is
unaware that the data has already been
updated, any changes from User 2 will
overwrite the change made by User 1 – in
other words, the database update will be
successful…and never mind that the
changes made by User 1 are overwritten.
If it so happened that Users 1 and 2
PBDJ volume7 issue2
values. If the key column(s) don’t match their original
database values, the update will fail (see Figure 2).
Key and Modified Columns
The key column(s) and the columns whose values are
being modified must match their original values. If the
above-mentioned columns don’t match their original
database values, the update will fail (see Figure 3).
The key column(s) and all updatable columns must
match their original values. If the above-mentioned
columns don’t match their original database values,
the update will fail (see Figure 4).
TABLE 1
www.PowerBuilderJournal.com
USER 1
USER 2
Set
ship_name
Update
orders =’Hendry’
Set ship_name =’Hendry’
=’Hendry’
Where order_id = 100
Set
ship_name
Update
orders=’Piekos’
Set ship_name =’Piekos’
Where order_id = 100
Hendry
order_id
100
FIGURE 2
Piekos
ship_name
Bates
ship_city
Boogers Holler
Key Columns Update
USER 1
USER 2
Update orders
Set ship_name =’Foy’
Where order_id = 100 and
ship_name = ’Bates’
Update orders
Set ship_city =’Hendry’
=’Hendry’
Where order_id = 100 and
ship_city = ’Bates’
Set ship_name =’Foy’
y =’Hendry’
ship_name = ’Bates’
y = ’Bates’
Update
Fails
Foy
order_id
100
FIGURE 3
ship_name
Bates
ship_city
Boogers Holler
Key and Modified Update
retrieved the data at the same time and
one of them updated the key column (in
this case the order number), the next
attempt to update would fail because the
key column has changed.
This technique is commonly used in
single-user applications or when dealing
with many-to-many tables where all
columns make up the primary key.
Where Clause Criteria – Key and
Modified Columns
The Key and Modified Columns criterion specifies that the key column(s) and
the columns for which the values are
being modified must match their original
values for the update to be performed.
Users 1 and 2 retrieve a row at the
same time, both intending to modify the
ship name. User 1 changes the ship
name from Bates to Foy. The update is
successful because both the key and the
modified columns match their original
values. However, when User 2 attempts
www.PowerBuilderJournal.com
longer matches its original value.
This update technique has a distinct
advantage. If a user has updated a value
in the database, this value can’t be written over by another user. However, the
technique also has a drawback (doesn’t
everything?). Consider the following scenario. Users 1 and 2 retrieve the same
row at the same time. User 1 changes the
ship name from Bates to Foy, while User
2 changes the ship city from Boogers
Holler to Wayne. Both updates would be
successful, but neither user would be
aware that the other had made changes.
The result would be inconsistent data
(see Figure 3A).
Where Clause Criteria – Key and
Updatable Columns
The Key and Updatable Columns criterion specifies that the key column(s)
and all updatable columns must match
their original values for the update to be
performed. In Figure 4, User 1 changes
the last name from Bates to Foy. The
change is successful. However, when
User 2 attempts to change the ship city
from Boogers Holler to Wayne, the
update fails. Even though the particular
change that User 2 made wouldn’t write
over the change made by User 1, the
update fails because User 1 changed an
updatable column (ship_name). While
this technique offers greater consistency
in the database, it’s achieved at the cost
of a greater potential for failed updates.
Optimistic Locking Errors
to change the ship name to Hendry, the
update will fail (see Figure 3). Since the
ship name is now Foy, while on User 2’s
end it was expected to be Bates, User 2’s
entry won’t overwrite the change by User
1. The database column ship_name no
If any changes to the database fail
because of how the programmer defined
the DataWindow Update Properties, the
DataWindow DBError event is fired and
a PowerBuilder-generated message is
displayed to the user (see Figure 5).
USER 2
USER 1
Update orders
Set ship_name =’Foy’
Set ship_name =’Foy’
Where
order_id = 100 and
ship_name = ’Bates’
ship_name = ’Bates’
Foy
order_id
100
FIGURE 3A
Update orders
Set ship_city =’Wayne’
Where order_id y==’Wayne’
100 and
ship_city = ’Boogers Holler’
y = ’Boogers Holler’
Wayne
ship_name
Bates
ship_city
Boogers Holler
Updates are successful, but data is inconsistent.
PBDJ volume7 issue2
35
I N T R O D U C T I O N
USER 1
USER 2
Update orders
Set ship_name =’Foy’
Set ship_name
Where
order_id =’Foy’
= 100 and
Where order_id
= 100
and
ship_name
= ’Bates’
and
ship_name
= ’Bates’ Holler’
and
ship_city
= ’Boogers
y = ’Boogers Holler’
Update orders
Set ship_city =’Wayne’
y =’Wayne’
Where order_id
= 100 and
Where order_id
= 100
ship_name
= ’Bates’
andand
ship_name
= ’Bates’ Holler’
and
ship_city
= ’Boogers’
y = ’Boogers’ Holler’
Foy
Update
Fails
order_id
100
FIGURE 4
ship_name
Bates
T O
ship_city
Boogers Holler
Key and Updatable Update
P O W E R B U I L D E R
dow modify functions to change the
DataWindow.Table.properties of the
DataWindow. For those who actually
want to try this, look up the UpdateTable,
UpdateWhere and UpdateKeyInPlace
properties. But believe me, trying to programmatically change the Update Properties of a DataWindow will make you
want to throw your PC down the stairs.
Multitable Update Service
We can now move on to the focus of
this month’s column: How can you use
the PFC multitable service to update
more than one database table? Let’s take
the following scenario. A DataWindow
contains columns from three tables. The
user is allowed to update at least one column from each of the three (see Figure
6). The updatable columns are as follows:
Column
Table
order date
quantity
ship date
product description
sales order
sales order items
sales order items
product
Adding the Code
Just like any of the DataWindow services, the multitable service must be
turned on. In the DataWindow constructor event, This.of_setMultiTable(TRUE)
will turn on the service for the DataWindow.
The next step is to determine what
tables we need to update, what identifies
a record in a table as a primary key, what
columns we’ll allow the user to update,
and what Update Properties (as discussed above) the DataWindow will use.
When we’ve gathered all of that information, we’ll put the code behind the
Update command button.
The first table we want to update is the
sales order table. The primary key of the
sales order table is the sales_order_id
column. Believe it or not, this is all you
need to start coding. Put the following
code behind the command button
Clicked event:
FIGURE 5 Update Error Message Box
String
String
ls_table
ls_key_cols[]
FIGURE 6 The Multitable Update: Demo Window
Table to Update
This portion of the Update Properties
dialog box specifies which table is updatable. Notice that only one table can be
updatable. What if you want more than
one table to be updated? Well, unless you
want to write some pretty complicated
36
PBDJ volume7 issue2
PowerScript to modify the DataWindow’s
Update Properties, you’re out of luck.
Nowhere within the DataWindow painter
can you specify that more than one table
should be affected.
Before the DataWindow multitable
update service was available, programmers were forced to write many DataWin-
// Update the sales_order table
ls_table = "sales_order"
ls_key_cols[1] = "sales_order_id"
If IsValid(dw_1.inv_multitable) then
dw_1.inv_multitable.of_register(ls_tab
le,ls_key_cols[])
End If
dw_1.Event pfc_update(TRUE,TRUE)
www.PowerBuilderJournal.com
The most important part of this
block of code is the of_register function. This function informs the multitable service that any update properties
specified in the DataWindow painter
will be overridden with those supplied
in our PowerScript. In the example
above, we’re saying that we want to
update the sales order table and the key
is sales_order_id.
The of_register function has three signatures.
Type
Value
as_table
String
Table to be updated.
as_key_cols
String array
Key columns.
as_updatable_cols
String array
Columns that updates will be
allowed on. Default is all columns.
ab_key_inplace
Boolean
TRUE - use a SQL UPDATE.
FALSE (default) - use a SQL
DELETE then an INSERT.
Signature 1:
ai_where_option
of_register(string as_table, string
as_key_cols[]).
In this signature, the programmer supplies the table to be updated and the key
column(s) for the table. By default, all
columns for that table are considered
updatable. Also, the service assumes that
you want to use Key and Updatable for the
DataWindow Update Properties when
building the Where clause. Finally, the service assumes that when performing the
update, the row will first be deleted with a
DELETE SQL statement, then reinserted
with an INSERT SQL statement. In our
examples we’ll use this signature.
Argument Name
Integer
TABLE 2
will be updated. Table 2 provides a more
detailed look at the syntax.
The Rest of the Code
Signature 2:
Now that we’ve specified the code to
update the sales_order table, we can
add the code to update product and
sales_order_items. See Listing 1 for
the complete code.
of_register(string as_table, string
as_key_cols[], string
as_updatable_cols[]).
About the Code
This signature is similar to the first.
The only difference is that specific
updatable columns are being passed to
the service. Use this signature when you
don’t want all of the columns in a table to
be updatable.
Signature 3:
of_register(string as_table, string
as_key_cols[], string
as_updatable_cols[],boolean
ab_key_inplace, integer
ai_where_option).
Use this signature when you want to
supply every detail about how the table
There are a couple of things worth noting in Listing 1. First of all, we have two
key columns from the sales_order_
items table. This is because the
sales_order_items_id and the sales_order_items_line_id uniquely identify
each row. You may have noticed that the
ID column in the sales_order_items_id
table isn’t visible in the DataWindow. Even
though we don’t display it to the user, it’s
part of the DataWindow selection criteria
because we need to use it as a key.
When we’re updating the product table,
the array that holds the key columns needs
to be reset. That’s because the product
table has only one key while the previous
table has two.
Listing 1
String
String
String
ls_table
ls_key_cols[]
ls_reset_array[]
// Update the sales_order table.
ls_table = "sales_order"
ls_key_cols[1] = "sales_order_id"
If IsValid(dw_1.inv_multitable) then
dw_1.inv_multitable.of_register(ls_table,ls_key_cols[])
End If
// Update the sales_order_items table.
ls_table = "sales_order_items"
www.PowerBuilderJournal.com
0 – Key columns only.
1 – Key and updatable columns
(default).
2 – Key and modified columns.
Just before the event is over, changes
are saved to the database and the pfc_
update event is used. When this event is
fired, the update logic is redirected to the
multitable update service. A regular
DataWindow update function simply
won’t work.
While we’re talking about the PFC…
remember, the use of most PFC services requires the entire application to
be PFC-based. Mixing PFC components with non-PFC components, with
few exceptions, is a recipe for disaster.
To use the DataWindow multitable service, your entire application should be
built on PFC components.
Final Note
I hope you’ve found this month’s column useful. Please remember that PowerBuilder Developer’s Journal is your magazine. Readers have requested every topic
that has been presented thus far. If you’d
like to suggest a topic, please e-mail me.
Jim Burbank, who works at Texaco in
Houston, first suggested this month’s topic.
Hope this helped you, Jim! ▼
bobh@envisionsoft.com
AUTHOR BIO
Bob Hendry is a
PowerBuilder instructor for
Envision Software Systems
and a frequent speaker
at national/international
PowerBuilder conferences.
He specializes in PFC
development and has
written two books on the
subject.
ls_key_cols[1] = "sales_order_items_id"
ls_key_cols[2] = "sales_order_items_line_id"
dw_1.inv_multitable.of_register(ls_table,ls_key_cols[])
// Update the product table.
ls_key_cols[] = ls_reset_array[]
ls_table = "product"
ls_key_cols[1] = "product_id"
dw_1.inv_multitable.of_register(ls_table,ls_key_cols[])
dw_1.Event pfc_update(TRUE,TRUE)
!
d the Code
a
o
l
n
Dow
The code listing for this article can also be located at
www.PowerBuilderJournal .com
PBDJ volume7 issue2
37
slick
tricks
submit your slick tricks to Bernie at slick@kevsys.com
Setting Column Attributes at Runtime
edited by
Bernie Metzger
AUTHOR BIO
Bernhard Metzger is a
CPD professional and
president of KEV Systems
Inc., a Powersoft
consulting partner in
Newton, Massachusetts.
Bernie can be reached at
bmetzger@kevsys.com, or
by voice at
800 376-5755.
If you want your DataWindows to look
consistent, you have two basic choices.
The first one – always code the DataWindow expressions the same in all your
DataWindows. However, there are drawbacks to this: you have to make sure your
entire development team is aware of the
standards, and you have to have methods
to ensure their use and document the
standards for future developers. In spite
of such precautions, it’s likely your
DataWindows will stray from the standards you’ve set up. The second and better approach is to put the standards in
your base DataWindow. Randy Howie of
the American Skiing Company has done
this with a function to set the protection
and background mode on DataWindow
columns at runtime. His function takes
two arguments, as_columnName and
as_expressions. The expression is the
same kind you would use in the
DataWindow column. For example, if
you wanted to prevent users from modifying the last name of an employee in
department 100, you would put the following expression on the emp_lname
column for protection: if (dept_id = 100 ,
0, 1). Since it’s a good idea to give the user
visual clues, you might also want to set
the background.color to something like
“silver” if the field isn’t editable. You’d
then need an additional statement for
the color attribute such as: if (dept_id =
100, 12632256,rgb[255,255,255]) which
would make the column silver or white.
The good thing about doing this in a
function, of course, is that if you decide
to change the color scheme later on, you
have to change the code in only one
place. Randy’s code sets the protection
of the column and the background.mode
Listing 1
/*
FUNCTION:
of_setProtectExpression
Called From:
constructor of any dw.
Description:
This function can be used as an alternative
to
setting conditional expressions in a dw for the
protect and background.color properties.
Arguments:as_columnName, as_expression
Returns: 1 =Successful
-1 =Unsuccessful
*/
string ls_modify, ls_modifyError
integer li_rc=-1
ls_modify = as_columnName + &
".background.mode=~"0~t" + as_expression + '~"'
ls_modifyError = this.modify (ls_modify)
IF ls_modifyError = '' THEN
ls_modify = as_columnName + &
".protect=~"0~t" + as_expression + '~"'
ls_modifyError = this.modify (ls_modify)
IF ls_modifyError = '' THEN
li_rc = 1
END IF
END IF
38
PBDJ volume7 issue2
(transparency) property. The function
(see Listing 1) first attempts to set the
background mode. If it succeeds, it then
proceeds to set the protection property.
Clearly you can modify this function to
set other properties to create a consistent “object-oriented” look to your
DataWindows.
Listing 2 shows some sample calls to
the function.▼
Randy Howie is a technical manager at
American Skiing Company. He can be
reached at rhowie@steamboat-ski.com
// If an error occurred on
//either modify, display message
IF li_rc = -1 THEN
messageBox ("of_setProtectExpression", &
"Developer: Invalid expression sent to"+&
uo_dw.of_setProtectExpression.~r~n" + &
ls_modify + "~r~nResponse: " +&
ls_modifyError)
END IF
RETURN (li_rc)
Listing 2
this.of_setProtectExpression ('emp_id', "if (isRowNew(), 0,
1)")
this.of_setProtectExpression ('emp_lname', "if (dept_id = 100
or isNull(dept_id), 0, 1)")
!
d the Code
a
o
l
n
Dow
The code listing for this article can also be located at
www.PowerBuilderJournal .com
www.PowerBuilderJournal.com
Riverton
www.riverton.com
www.PowerBuilderJournal.com
PBDJ volume7 issue2
39
SYBASE
www.sybase.com/products/powerbuilder
40
PBDJ volume7 issue2
www.PowerBuilderJournal.com