The Storage Kit Table of Contents | The Storage Kit Index |
Derived from: BEntryList
Declared in: be/storage/Query.h
Library: libbe.so
A query is a means of asking the file system for a set of entries that satisfy certain criteria. As examples, you can ask for all the entries with names that start with a certain letter, or that have nodes that are bigger than a certain size, or that were modified within the last N days, and so on.
The BQuery class lets you create objects that represent specific queries. To use a BQuery you have to follow these steps:
1. Initialize. The first thing you have to do is initialize the object; there are two parts to the initialization: You have to set the volume that you want to query over (SetVolume()), and set the query's "criteria formula" (SetPredicate())
2. Fetch. After the BQuery has been properly initialized, you invoke Fetch(). The function returns immediately while the query executes in the background.
3. Read. As soon as Fetch() returns, you can start reading the list of winning entries by making iterative calls to the entry-list functions GetNextRef(), GetNextEntry(), and GetNextDirents(). If you ask for entries faster than the query can deliver them, your GetNext...() call will block until the next entry arrives. The function returns an error when there are no more entries to retrieve.
The set of entries that the GetNext...() calls retrieve (for a given fetch) are called the query's "static" entries. This distinction will become useful when we speak of "live" queries, below.
Want to go around again? You can, but first you have to clear the object:
Clearing erases the object's predicate, volume, target (which we'll get to later), and list of static entries—in other words, clearing gets you back to a fresh BQuery object.
And speaking of going around again, be aware that the Rewind() function, which BQuery inherits from BEntryList, is implemented to be a no-op: You can't rewind a BQuery's list of static entries. After you've performed a fetch, you should read the entry list as quickly as possible and get on with things; you can't turn back or start over.
CountEntries() is also a no-op. This function is also defined by BEntryList. It doesn't apply to BQueries.
A live query is the gift that keeps on giving. After you tell a live query to fetch, you walk through the entry list (as described above), and then you wait for "query update" messages to be sent to your "target." A query update message describes a single entry that has changed so that...
Not every BQuery is live; you have to tell it you want it to be live. To do this, all you have to do is set the object's target, through the SetTarget() function. The target is a BMessenger that identifies a BHandler/BLooper pair (as described in the SetTarget() function). Also...
Another important point regarding live queries is that you can start receiving updates before you're done looking at all the static entries (in other words, before you've reached the end of the GetNext...() loop). It's possible that your target could receive an "entry dropped out" update before you retrieve the entry through a GetNext...() call. If you're using live queries, you should take care in synchronizing the GetNext...() iteration with the target's message processing.
We'll look at the format of the update message in a moment; first, let's fill in some gaps.
A BQuery's predicate is a logical expression that evaluates to true or false. The "atoms" of the expression are comparisons in the form...
|
...where attribute is the name of an existing attribute, op is a constant that represents a comparison operation (==, <, >, etc), and value is the value that you want to compare the attribute to.
As mentioned above, the attribute part of a query is a string name. When you tell the query to fetch, the file system looks for all nodes that have an attribute with that name and then compare the attribute's value to the appropriate value in the predicate. However...
To index an attribute, you call the fs_create_index() function. Unfortunately, there's currently no way to retroactively include existing attributes in a newly created index. (Such a utility would be simple enough to write, but it would take a long time to execute since it would have to look at every file in the file system.)
Only string and numeric attributes can be queried. Although an attribute can hold any type of data (it's stored as raw bytes), the query mechanism can only perform string and numeric comparisons.
On the bright side, every file gets three attributes for free:
Technically, "name", "size", and "last_modified" aren't actually attributes—you can't get them through BNode::ReadAttr(), for example. But they're always eligible as the attribute component in a query.
The value part of the "attribute op value" equation is any expression that can be evaluated at the time the predicate is set. Once evaluated, the value doesn't change. For example, you can't specify another attribute as the value component in hopes of comparing, file by file, the value of one attribute to the value of another. The value is just data. And data is data.
The type of the value should match the type of the attribute: You compare string attributes to strings; numeric attributes to numbers. You aren't prevented from comparing a string to a number (for example), but it may not give you the result you expect.
|
There are two ways to construct a predicate:
You can't combine the methods: Pushing the predicate always takes precedence over SetPredicate(), regardless of the order in which the methods are deployed.
SetPredicate() features:
The following are all legitimate strings that you can pass to SetPredicate():
|