Sunday, February 26, 2006

PL/sql Experts Determined to Give their Expertise -- ???

I am distributing this idea out into the world of PL/SQL developers. Everything about it, including the name, acronym, structure, etc., is open to discussion. Please let me know what you think and if you would be interested in participating. Thanks!

PLEDGE -PL/sql Experts Determined to Give their Expertise

Let's face it: not all developers are equal. We have widely varied levels of skill, experience and communications abilities. Any such group of professionals has a tiny subset of members who are perceived as the "elders" (aka, gurus, experts, etc.) -- and age has little to do with this status. I feel strongly that those of us in the PL/SQL world who are deeply experienced should come together to make our contributions more visible and widely available to the worldwide PL/SQL community.

Disclosure: I am especially desirous of a group like this, and will benefit greatly from it, because I cannot possibly answer all the questions that come my way (either because my time is limited or the question involves how PL/SQL code interacts with technologies like XML or Java, about which I have little experience). I would like to formalize the group of people I can turn to, to assist me in such matters. But I certainly don't want to limit it just to helping me answer my questions.

What is PLEDGE?

PLEDGE is (would be) a group of highly experienced PL/SQL developers who are committed to sharing their knowledge and code with others, at no cost.

I bet you are already thinking one or more of the following:
  • Should PLEDGE be a part of IOUG?
  • Should PLEDGE be or is it the same as a PL/SQL SIG?
My feeling is that PLEDGE is different from a SIG. It is primarily/fundamentally a close-knit fellowship of experts, not an enormous membership community. It functions to provide high quality, free resources to the worldwide PL/SQL community.

As to whether it should be a part of IOUG, I don't have strong feelings about that right now. See my comments in the section titled Resources below. I am not sure that this would be consistent with an IOUG entity.

How would PLEDGE work?

Here are some initial thoughts/guidelines...

1. PLEDGE has members and guests.

2. A PLEDGE member is a PL/SQL expert who has agreed to contribute her or his time/effort to improve the skills, productivity and code quality of the worldwide PL/SQL community.

3. A PLEDGE guest is a developer who has registered with PLEDGE so as to take advantage of the resources offered by PLEDGE, participate in forums and so on.

4. There is no cost associated with any level of participation in PLEDGE.

5. A PLEDGE member or user can post a challenge/requirement to PLEDGE, and then a member can volunteer to implement/guide/answer that challenge.

6. All code that is posted on the PLEDGE website needs to be documented, tested and testable (preferably with a Qute harness; check out for my latest and greatest product/concept).

Resources needed

This community needs to be supported by commercial vendors who are committed to the Oracle space. I do not believe that we can make it work the way it should with only volunteer time contributed, though it is certainly a possibility....

I do believe that any number of vendors could provide a base of support, including Quest and Oracle.

Areas of expertise

We need general PL/SQL pros, but also people who have specialized in various areas of technology that commonly connect to PL/SQL, including but not limited to:

  • Java-PL/SQL interface
  • .Net-PL/SQL interface
  • VB-PL/SQL interface
  • Using XML in PL/SQL
  • Oracle Applications and PL/SQL
  • Object-Oriented development with PL/SQL
So let me know what you think of PLEDGE!

Monday, February 20, 2006

Gambling addictions

As casinos explode in number across the United States (how else can the most powerful nation in the world, spending over $500 billion on weapons and soldiers each year finance public education), it becomes more and more clear that an addiction to gambling has become a serious problem in the good old US of A.

An article in today's Chicago Tribune drives that point home, based on data gathered by the Center for Responsive Politics:

In 1990, the gambling industry "contributed" $478,000 to federal campaigns (that is, the "war chests" of individuals running for federal office, or more to the point, running to keep their position in Congress or control the Oval Office).

In 2004, that same industry forked over $13 million. Here are the top 5recipients from the 2004 election cycle:


Bush, George W (R)




Reid, Harry (D-NV)




Porter, Jon (R-NV)




Berkley, Shelley (D-NV)




Daschle, Tom (D-SD)



So who is addicted to gambling? I would venture to guess that our elected officials (I hestiate to call them our representatives, because it seems to me that they more closely represent corporate interests and CEOs, not the rest of us) fit the bill very nicely.

Sunday, February 19, 2006

Watch out for sequential Oracle GUIDs!

I ran into a very interesting situation regarding Oracle GUIDs or Globally Unique Indentifiers, the other day. I will tell you about my experience and then after that, you can read all about Oracle GUIDs in an article I originally had published in Oracle Professional. Enjoy!

A GUID is a long sequence of characters that are supposed to be globally unique -- that is, the likelihood of a particular sequence of characters appearing (returned by the GUID generating function) more than once on any computer running on our globe is miniscule. Thus, you can use GUIDs when you need unique values that span, say, database instances or multiple networks.

My latest obsession is a product named Qute - the Quick Unit Test Engine. You can find out more about it at We use GUIDs for primary keys; this makes our work substantially easier since the product and underlying data are installed on a user's computer, and we do not have to worry about possible conflicts with sequence-generated primary key values.

The Delphi frontend in Qute also takes those GUIDs and hashes their values to integers, which are then used to index arrays of data and speed up access to browser data. All fine so far.

Unfortunately, a number of users reported an error involving a "Hash Mismatch" on starting up of Qute. After lots of patient cooperation from those users, we were amazed to find that the Oracle SYS_GUID function (the GUID generator offered by Oracle) was returning sequentially-incremented values, rather than pseudo-random combinations of characters.

We then had a bug in our Delphi code such that the hashing algorithm was set to "quick and not very secure" -- meaning that strings which were not very different were hashing to the same number, which then triggered our error. We adjusted that algorithm, making it more robust, and the problem seems to have disappeared. But I thought I should let the world know about our experience.

To see if you have this issue in your installation of Oracle, run the following script:

FOR indx IN 1 .. 5
DBMS_OUTPUT.put_line ( SYS_GUID );

You should see five rows of wildly different values, like this:


But some Qute users on AIX and Compaq 64bit operating systems were instead seeing results like this:


See the difference? The algorithm that Oracle is using on these operating systems is clearly faulty; yes, the values are unique but they are far from even pseudo-random.

The moral of the story is: if you are going to rely on the Oracle SYS_GUID function to generate GUID values, test the algorithm and make sure you are satisfied and can live with the results.

Going Global with GUIDs

Steven Feuerstein, Copyright 2005

Originally published in Oracle Professional, Ragan Communications

What is a GUID?

You've probably heard about and even seen some funky-looking string that is referred to as a "GUID". This article explains what a GUID is, how they can be very useful, and how you can work with them inside Oracle.

Let's start with a formal definition of a GUID, taken from Wikipedia (

"A Globally Unique Identifier or GUID is a pseudo-random number used in software applications. Each generated GUID is "mathematically guaranteed" to be unique. This is based on the simple principal that the total number of unique keys (264 or 1.8446744073709551616 \times 10^{19}) is so large that the possibility of the same number being generated twice is virtually zero. The GUID is an implementation by Microsoft of a standard called Universally Unique Identifier or UUID, specified by the Open Software Foundation (OSF)."

Ah! So a GUID is actually the Microsoft version of the UUID, about which Wikipedia tells us:

"A Universally Unique Identifier is an identifier standard used in software construction, standardized by the Open Software Foundation (OSF) as part of the Distributed Computing Environment (DCE). The intent of UUIDs is to enable distributed systems to uniquely identify information without significant central coordination. Thus, anyone can create a UUID and use it to identify something with reasonable confidence that that identifier will never be unintentionally used by anyone for anything else. Information labelled with UUIDs can therefore be later combined into a single database without need to resolve name conflicts. The most widespread use of this standard is in Microsoft's Globally Unique Identifiers (GUIDs) which implement this standard.

"A UUID is essentially a 16-byte number and in its canonical form a UUID may look like this:


So, in point of fact and strictly speaking, a GUID is a special (Microsoft-specific) case of a UUID. Perhaps because "GUID" is easier and more fun to pronounce than "UUID," it is the most common of the two terms, even when used in a non-Microsoft context (as we find in Oracle and as I will be doing in this article). I will continue, therefore, to refer to this number as a GUID.

One comment regarding the "mathematical guarantee" mentioned above: as far as I understand, no one has come up with an algorithm that really does quarantee a unique GUID. It is possible, though very, very improbable, that you could generate a GUID that conflicts with an existing value.

GUIDs are GOOD for...

The core value of a GUID is expressed in a single line of the UUID definition above, namely: "The intent of UUIDs is to enable distributed systems to uniquely identify information without significant central coordination." This statement should get Oracle database programmers thinking about two things:

  • Distributed databases: they are certainly a kind of "distributed system." So GUIDs should help us identify unique objects not just in a single instance but across different database instances.
  • Primary keys and unique indexes: we use these constraints to "uniquely identify" rows of information within a given relational table. Perhaps a GUID could be used for these situations?

If I am writing code for an application that resides in a single database instance, then GUIDs are probably not very compelling. Suppose I face a different scenario: my application runs on many different instances. Each instance has its own users inserting their own data. On a regular basis, however, data from one instance must be merged into another instance.

In this scenario, reliance on sequence-generated primary keys gives me serious heartburn. Since users are inserting rows independently in multiple instances, sequence values are being used up at different rates. When I move data from one instance to another, I have to assume and program for primary key conflicts of various kinds.

This is exactly the situation I encountered as I built Qnxo ( This product provides access to a large and ever growing/changing set of PL/SQL scripts (reusable code and generation templates). Any changes to my central PL/SQL repository must be distributed to users. In my first phase of design and construction of Qnxo, I relied on sequence-generated primary keys. As soon as I realized the error of my ways, I converted as much as possible over to GUIDs.

Let's take a closer look at some of the lessons I learned about applying GUIDs in the PL/SQL environment.

Oracle and GUIDs

To help us work with GUIDs, Oracle provides a SQL function named SYS_GUID, documented as follows:

"SYS_GUID generates and returns a globally unique identifier (RAW value) made up of 16 bytes. On most platforms the generated identifier consists of a host identifier, a process or thread identifier of the process or thread invoking the function, and a non-repeating value (sequence of bytes) for that process or thread."

The difference between a RAW and a VARCHAR2 is that RAW string is not explicitly converted to different character sets when moved between systems by Oracle. This point is critical for GUIDs, since we wouldn't want our GUIDs to be changed as they move from one system to another. Conversely, it is also a non-issue for GUIDS, because the characters used in GUIDs are drawn from a restricted subset of ASCII characters, which are static across all character sets.

This function is also exposed in PL/SQL through the STANDARD package as follows:



c RAW (16);


I can therefore immediately obtain a GUID from within my PL/SQL program by writing code like this:


l_guid_raw RAW (16);

l_guid_vc2 VARCHAR2 (32);



l_guid_raw := SYS_GUID;

l_guid_vc2 := SYS_GUID;

DBMS_OUTPUT.put_line (l_guid_raw);

DBMS_OUTPUT.put_line (LENGTH (l_guid_raw));

DBMS_OUTPUT.put_line (l_guid_vc2);

DBMS_OUTPUT.put_line (LENGTH (l_guid_vc2));



And here is the output from running this code:






Formatting for GUIDs

As Wikipedia specified earlier in this article, ""a UUID is essentially a 16-byte number and in its canonical form a UUID may look like this: 550E8400-E29B-11D4-A716-446655440000."

In addition, in many situations, the common presentation format for a GUID is:


So as you decide to incorporate GUIDs in your code and underlying database, you should decide how you want to store the GUIDs. You could stick with the unvarnished RAW values, or you could store a formatted version, if that is how the GUIDs are going to be used in your application.

We decided that in the Qnxo, we decided that the GUIDs would be stored with the full formatting, including dashes and the open and close brackets.

Using GUIDs in Oracle tables

Let's look at how we can use GUIDs inside a relational table.

Below I create a table using a GUID as the primary key for a table.

CREATE TABLE guid_table (


, NAME VARCHAR2(100));

When I insert a row into this table, I can use the SYS_GUID function to generate my unique GUID. I try to do it this way:

INSERT INTO guid_table

VALUES (SYS_GUID, 'Steven');

but I get the following error:

ORA-00984: column not allowed here

As with .nextval, I am unable to call this function directly in SQL. So if I want to insert rows I need to write and run code like this:


l_pky guid_table.pky%TYPE;


l_pky := SYS_GUID;

INSERT INTO guid_table

VALUES (l_pky, 'Steven');

l_pky := SYS_GUID;

INSERT INTO guid_table

VALUES (l_pky, 'Sandra');



Now I can select the data from this table:

SQL> SELECT * FROM guid_table;


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

DF832F19657645A2866E7AD15E9F59AF Steven

5FBAB1A92157434EBDBDB5880556D151 Sandra

That's certainly easy enough! Of course, since we want to make certain that a valid GUID is placed is in this column, it would be best to encode that logic into a BEFORE INSERT trigger on this table, as in:



ON guid_table





:NEW.pky := SYS_GUID;




And I can now insert into this table as simply as this:

INSERT INTO guid_table





In the case of Qnxo, since we decided to store the formatted version of the GUID, we created tables with primary keys defined as follows:

CREATE TABLE sg_favorite (

universal_id VARCHAR2(38) PRIMARY KEY



Encapsulating Oracle's GUID function

If you decide that you want to work with Oracle GUIDs in the RAW format and never need to deal with the formatted version, then you are pretty much ready to go. If, on the other hand, you want to able to work with GUIDs in a "standard" format, you may want to take advantage of the guid_pkg I wrote to assist in Qnxo development.

The elements in the package are:


A SUBTYPE that encapsulates the RAW(16) declaration of a GUID and gives it a name. With this subtype in place, you can declare GUIDs in your code as follows:

my_guid guid_pkg.guid_t;


Another SUBTYPE, this time designed to allow you to easily and readably declare variables that hold a formatted GUID, used as follows:

my_guid_with_brackets guid_pkg.formatted_guid_t;


A mask that specifies the valid format for a formatted GUID. It also serves as a wildcarded string that I can use within a LIKE statement to determine if a string matches the standard GUID format.


A Boolean function that returns TRUE if the string passed to it has a format consistent with the standard GUID format.

formatted_guid (2)

Two overloadings of functions that return a formatted GUID. In the first overloading, you pass a string, which could be a RAW GUID value or a VARCHAR2 string. It will return a string containing the GUID in the standard format (as specified by c_mask).

Here is a script that exercises this package:


l_oracle_guid guid_pkg.guid_t;

l_formatted_guid guid_pkg.formatted_guid_t;




IF val


DBMS_OUTPUT.put_line ('TRUE');



DBMS_OUTPUT.put_line ('FALSE');


DBMS_OUTPUT.put_line ('NULL');




DBMS_OUTPUT.put_line (guid_pkg.formatted_guid);

bpl (guid_pkg.is_formatted_guid (guid_pkg.formatted_guid));

bpl (guid_pkg.is_formatted_guid (SYS_GUID));

bpl (guid_pkg.is_formatted_guid ('Steven'));

DBMS_OUTPUT.put_line (guid_pkg.formatted_guid (SYS_GUID));


(guid_pkg.formatted_guid ('B16079A0567148608C171FA89E7187B3')




With this output after execution:







Listing 1. The specification of the guid_pkg package



SUBTYPE guid_t IS RAW (16);

SUBTYPE formatted_guid_t IS VARCHAR2 (38);

-- Example: {34DC3EA7-21E4-4C8A-BAA1-7C2F21911524}

c_mask CONSTANT formatted_guid_t

:= '{________-____-____-____-____________}';

FUNCTION is_formatted_guid (string_in IN VARCHAR2)


FUNCTION formatted_guid (guid_in IN VARCHAR2)

RETURN formatted_guid_t;

FUNCTION formatted_guid

RETURN formatted_guid_t;

END guid_pkg;


The implementation of this package is shown in Listing 2. It relies on the SYS_GUID function to generate a new GUID value, but otherwise is devoted to applying the format mask and converting un-formatted GUIDs to strings that contain the correctly-placed hyphens.

Listing 2. The body of the guid_pkg package



FUNCTION is_formatted_guid (string_in IN VARCHAR2)




RETURN string_in LIKE c_mask;

END is_formatted_guid;

FUNCTION formatted_guid (guid_in IN VARCHAR2)

RETURN formatted_guid_t



-- If not already in the 8-4-4-4-rest format, then make it so.

IF is_formatted_guid (guid_in)


RETURN guid_in;

-- Is it only missing those squiggly brackets?

ELSIF is_formatted_guid ('{' || guid_in || '}')


RETURN formatted_guid ('{' || guid_in || '}');



|| SUBSTR (guid_in, 1, 8)

|| '-'

|| SUBSTR (guid_in, 9, 4)

|| '-'

|| SUBSTR (guid_in, 13, 4)

|| '-'

|| SUBSTR (guid_in, 17, 4)

|| '-'

|| SUBSTR (guid_in, 21)

|| '}';


END formatted_guid;

FUNCTION formatted_guid

RETURN formatted_guid_t



RETURN formatted_guid (SYS_GUID);

END formatted_guid;

END guid_pkg;


GUIDs: a nice addition to the toolbox

I'd heard of GUIDs, but never had reason to work with them before developing Qnxo. I started with standard, sequence-generated primary keys in my script table, but found that this approach was sorely lacking when it came time to distribute that content (reusable scripts and templates) to my user base.

By relying on GUIDs, I was able to greatly simplify the logic needed to upgrade distributed content from a central source. Not only did I avoid the problem of conflicting sequence-generated primary keys, but I was also able to keep intact foreign key references, since they were based on unchanging GUIDs.

Saturday, February 11, 2006

Poorly tested software strikes again....

I am on occasion honored with a request to do a keynote presentation at Oracle User Group meetings. I could do the usual "top ten" this or that for PL/SQL, but that has always seemed a bit too narrow for a keynote (even if PL/SQL is just about the only reason anyone would want me to keynote). So I put together a presentation called "Software programmers: Heroes and Heroines of the 21st Century", which focuses on the incredibly central and important role we developers play (for better or worse!) in our society.

Generally, I ascribe to one of the core insights of Lawrence Lessig: code is a form of law, which means that in many ways, programmers are law-writers and certainly we are in the critical path of implementing laws (applications) that control human behavior through software.

So I am constantly on the lookout for stories that drive this point home -- and I ran into an excellent one this morning in the Chicago Tribune. I reproduce the article below. Summary:

A poorly designed and tested interface allows an unsupervised, untrained and unauthorized user to change a value in a field which ends up disrupting the budgets and plans for a school district and several towns.

So test your code, people! And to help you test your PL/SQL code, I am building a new tool named Qute, the Quick Test Engine. We are testing pre-production releases now and you are welcome to download and try it out. Qute makes unit testing dramatically, qualitatively, breath-takingly easier than you can even imagine!

A computer error gave Indiana taxing bodies that false idea. They're out about $8 million.

By James Janega
Chicago Tribune
Tribune staff reporter
Published February 11, 2006

The story of the $400 million house began innocently enough--with a faulty computer entry.

This week, however, things went downhill fast, as school systems and towns throughout Porter County, Ind., scrambled to fill gaping budget holes resulting from a colossal goof in the tax valuation of a modest two-bedroom house in Valparaiso.

And on Friday, the owner of the wildly overvalued ranch was scratching his head, wondering if it's too late to sell.

"We'd sell it for 5 percent of that!" said Dennis Charnetzky, 32, who heard about the mistake that afternoon while working at his home remodeling business.

Pausing, he reconsidered: "At least I don't owe $8 million in taxes."

In October 2004, give or take, a real estate agent--or maybe a title company employee--checking on the value of the Valparaiso property on a county computer system apparently tapped the wrong key. Officials figure it was an accident.

The unidentified user stumbled onto a restricted screen, and then changed the value of the $120,000 house in the 1100 block of Chicago Street to $400 million.

Trying to reconstruct the event, officials imagine the user looking up and realizing something was amiss, then hitting "escape" to leave the screen. But the new value stayed in the computer, and the property tax bill for the house leaped from $1,500 to the upper seven figures.

"They never reported it to the county that they got a funny screen," Porter County Treasurer Jim Murphy said of the mystery typist.

The error was spotted at least twice, Murphy said. One woman in the county auditor's office spotted the problem the day after the entry when the county's equalized assessed value went sky high overnight.

The auditor's office fixed the glitch that allowed outsiders into the county tax computer system, but the $400 million valuation somehow remained.

Last May, the bank that held the home's escrow account, got a tax bill on the property for $8 million. The bank asked the county to take another look at things.

"We thought the problem had been fixed," Murphy said. "It had not been."

So, guided by its computers, the county expected to collect taxes on this startling new abundance, and other taxpayers were asked to pay a little less. Budgets were built around the phantom figures.

"And that's when the poop hit the fan," the treasurer said.

Eighteen taxing districts from the city of Valparaiso, the county and the Valparaiso schools now find themselves in the position of having to return to the county an advance of $3,090,287.33 that was never collected.

That's $1,700,192.51 from the Valparaiso Community Schools, which had counted on the money for their $38 million 2006 budget.

It's also $1,045,527.33 back from the city of Valparaiso (2006 budget: $21.3 million), which had been mounting an aggressive city beautification effort, complete with street resurfacing and sidewalk repairs.

"You can imagine the panic it caused here," said City Administrator Bill Hanna. "You won't find us buying laptops."

Still, no matter what anybody says, "We're not even thinking about laying people off," he said.

But that cracked sidewalk? Might have to wait until next year.

Officials say the county and its various taxing bodies will make it through the short term, as they negotiate with the state on how to correct the mistake. Long-term finances will depend on what's decided.

Back to the house.

"It's a nice home on a quiet street in Valparaiso," Murphy said. "Nice neighborhood, good schools."

The leafy street is just a half-mile from downtown restaurants, steps away from Parkview Elementary School, and walking distance from a lush, grassy park.

The 1,200-square-foot, two-bedroom, one-bath, single-story house (with basement) valued at $121,900 was built in 1949, according to Porter County tax records.

It seems to have little in common with other real estate in the $400 million range--for instance, the twisting 115-story, 2,000-foot Fordham Spire proposed last July for a site along Lake Shore Drive in Chicago. If built, that $400 million tower would be the tallest building in North America, with 250 condominium units and a 200-room luxury hotel.

Until 2004, the owner of the Valparaiso ranch home was Robert Affeld, who had lived there 20 years with his wife Sarah, now deceased.

"I never heard anything about it like that," said Affeld, who now lives with his daughter in Danville, Ind. "I sure ain't got $8 million to pay taxes, that's for sure."

He sold the house to Dennis' wife Daelyn Charnetzky, 31, a hair stylist. The Charnetzkys have lived there for more than a year, with no idea they had gotten such a bargain.

"I feel bad for the city for not getting all the property taxes they deserve," said Dennis Charnetzky, who did not sound like he felt bad at all. "It really blows my mind when you hear about stupid stuff like that happening."

On the other hand, Murphy said, one of the darkest weeks in Porter County financial history is over.

"There's a light at the end of the tunnel," he said. "At least it's Friday."


Friday, February 10, 2006

Recovering from a hands-on training

I gave a two-day, hands-on training on PL/SQL collection this week, organized by John Goodhue of Speak-Tech. It was a very enjoyable, interesting and eye-opening experience. [ Note: you can download, study and use (inside your company) all the materials for this course without charge by clicking here. ]

I have been doing lecture-style trainings for years, to audiences big and small, and they have become second nature to me. I put together a Powerpoint presentation of about 100 slides per day, and within those notes reference dozens of pre-defined scripts. I then strap on a wireless microphone and spend six hours talking too quickly, covering too much material, and running lots and lots of code demonstrating my points and PL/SQL's features.

Developers seem to love it. But they also complain about the pace and ask that I go more slowly and include hands-on exercises.

So John, who helps me organize these trainings in the US, asked me to put together a hands-on class. I decided to focus specifically on collections, since they are at the core of just about every interesting new feature in PL/SQL and they are very much under-utilized in the world of PL/SQL development.

So I took my lecture materials, reorganized them a bit, and built out an extensive set of exercises, which I implemented in Qnxo as a script repository. That was fun. Qnxo is a very flexible and handy tool. It focuses on code generation and code re-use, but it can be easily adapted to other purposes. The download referenced above contains a script that will install the course exercises into Qnxo if you have it installed. I went to Minneapolis to teach the course for about 15 students. The feedback was very positive -- and perhaps predictable, as more than a third of those attending said that I tried to cover too much in two days that that it should be a three day class!

But it also showed me how differently I must teach and prepare materials for a hands-on class compared to lecture-style. The materials I provided (and these weaknesses are still reflected in the download) did not offer enough basic information about syntax of features. That was fine when the main objective was to demonstrate to attendees how stuff works. But it was not adequate when those training materials were supposed to provide a foundation from which the students would actually write their own code in class.

So the next time I give this class (visit Speak-Tech to find out about scheduling of this class in 2006), the Powerpoint will have twice as many slides. I will also more carefully construct the exercises so that students will concentrate their code writing on the collection-specific topics.