Creating a Data File
The transactional interface gives developers tremendous flexibility in optimizing database applications. In providing such flexibility, the transactional interface exposes a great deal of the inner workings of the transactional database engine. If you are new to the transactional interface, the Create (14) operation may appear quite complex to you, but you do not need all of the features this operation provides to get started. This section highlights the basic requirements by stepping you through the creation of a simple, transaction-based data file. For simplification where necessary, this section uses C interface terminology.
*Note: In the same directory, no two files should share the same file name and differ only in their file name extension. For example, do not name a data file Invoice.btr and another one Invoice.mkd in the same directory. This restriction applies because the database engine uses the file name for various areas of functionality while ignoring the file name extension. Since only the file name is used to differentiate files, files that differ only in their file name extension look identical to the database engine.
Data Layout
This section uses an example data file that stores employee records. The application will retrieve employee information by providing either a unique employee ID or the employee’s last name. Because more than one employee can have the same last name, the database will allow duplicate values for the last name. Based on these criteria, the data layout for the file is as follows:
Now that the basic data layout is established, you can begin applying the terminology and requirements of the transactional interface. This includes determining information about the key structure and file structure before you actually create the file. You must work out these details in advance, because the Create (14) operation creates the file, index, and key information all at once. The following sections discuss the issues to consider in working out these details.
Key Attributes
First, determine any special functionality for the keys. The transactional database engine supports a variety of key attributes you can assign, as shown in the following table.
Table 13
You assign these key attributes for each key you define. Each key has its own key specification. If the key has multiple segments, you have to provide the specification for each segment. Some of these attributes can have different values for different segments within the same key. Using the previous example, the keys are the last name and the employee ID. Both keys use extended types; the last name is a string and the employee ID is an integer. Both are modifiable, but only the last name is duplicatable. In addition, the last name is case insensitive.
Regarding the data type you assign to a key, the transactional interface does not validate that the records you input adhere to the data types defined for the keys. For example, you could define a TIMESTAMP key in a file, but store a character string there or define a date key and store a value for February 30. Your transactional interface application would work fine, but an ODBC application that tries to access the same data might fail, because the byte format could be different and the algorithms used to generate the timestamp value could be different. For complete descriptions of the data types, refer to the SQL Engine Reference.
File Attributes
Next, determine any special functionality for the file.
The transactional database engine supports a variety of file attributes you can assign, as follows:
The example data file does not use any of these file attributes, because the records are fixed-length records of small size.
For definitions of file attributes, refer to File Types. For more information about specifying file attributes during the Create operation, refer to the Btrieve API Guide.
Creating File and Key Specification Structures
When you use the Create operation, you pass in a data buffer that contains file and key specification structures. The following structure uses the example employee data file.
6.0 to 8.0 file formats support page sizes of 512 times x, where x is any number up to the product 4,096.
2
0
0
0
0
0
1
0
0
0
0
0
4
0
0
0
0
0
1Unless specified otherwise, all data types are unsigned.
2For simplification, the non-numeric example values are for C applications.
3For files with variable-length records, the logical record length refers only to the fixed-length portion of the record.
4Short Integers (Short Int) must be stored in the “Little Endian” byte order, which is the Low To High ordering of Intel-class computers.
5Only used with page level compression. Must be used in conjunction with the Page Compression file flag (see Table 6). See also Creating a File with Page Level Compression for more information.
Creating a File with Page Level Compression
For Pervasive PSQL 9.5 and later, you can use the Create operation to create data files with page level compression. For earlier data files, logical pages map to physical pages, and this mapping is stored in a Page Allocation Table (PAT). A physical page is exactly the same size as a logical page.
When a file is compressed, each logical page is compressed into one or more physical page units that are smaller in size than a logical page. The physical page size is specified by the Physical Page Size attribute (see Table 15).
The Page Compression file flag (see Table 6) is used in conjunction with the Physical Page Size key specification to tell the MicroKernel to create the new data file with page level compression turned on. The logical and physical page sizes are validated as follows:
The value specified for the physical page size cannot be larger than the value specified for the logical page size. If it is then the MicroKernel will round down the value specified for the physical page size so that it is the same as the logical page size. The logical page size needs to be an exact multiple of the physical page size. If it is not then the logical page size is rounded down so that it becomes an exact multiple of the physical page size. If, as a result of these manipulations, the logical and physical values end up to be the same, then page level compression will not turned on for this file.
Calling the Create Operation
The Create operation (14) requires the following values:
In C, the API call would be as follows:
Create Operation
 
/* define the data buffer structures */
typedef struct
{
BTI_SINT recLength;
BTI_SINT pageSize;
BTI_SINT indexCount;
BTI_CHAR reserved[4];
BTI_SINT flags;
BTI_BYTE dupPointers;
BTI_BYTE notUsed;
BTI_SINT allocations;
} FILE_SPECS;
 
typedef struct
{
BTI_SINT position;
BTI_SINT length;
BTI_SINT flags;
BTI_CHAR reserved[4];
BTI_CHAR type;
BTI_CHAR null;
BTI_CHAR notUsed[2];
BTI_BYTE manualKeyNumber;
BTI_BYTE acsNumber;
} KEY_SPECS;
 
typedef struct
{
FILE_SPECS fileSpecs;
KEY_SPECS keySpecs[2];
} FILE_CREATE_BUF;
/* populate the data buffer */
FILE_CREATE_BUF databug;
memset (databuf, 0, size of (databuf)); /* initialize databuf */
dataBuf.recLength = 72;
dataBuf.pageSize = 4096;
dataBuf.indexCount = 2;
dataBuf.keySpecs[0].position = 1;
dataBuf.keySpecs[0].length = 25;
dataBuf.keySpecs[0].flags = EXTTYPE_KEY + NOCASE_KEY + DUP + MOD;
dataBuf.keySpecs[0].type = ZSTRING;
dataBuf.keySpecs[1].position = 52;
dataBuf.keySpecs[1].length = 4;
dataBuf.keySpecs[1].flags = EXTTYPE_KEY;
dataBuf.keySpecs[1].type = INTEGER;
/* create the file */
strcpy((BTI_CHAR *)keyBuf, "c:\\sample\\sample2.mkd");
dataLen = sizeof(dataBuf);
status = BTRV(B_CREATE, posBlock, &dataBuf, &dataLen, keyBuf, 0);
Create Index Operation
If you create files with pre-defined keys, the indexes are populated with each insert, update or delete. This is necessary for most database files. However, there is a class of database files that are fully populated before being read. These include temporary sort files and fully populated files that are delivered as part of a product.
For these files, it may be faster to build the keys with Create Index (31) after the records are written. The file should be created with no index defined so that inserting records can be accomplished faster. Then the Create Index operation will sort the keys and build the indexes in a more efficient manner.
Indexes created this way are also more efficient and provide faster access. There is no need for the transactional database engine to leave empty space at the end of each page during a Create Index (31) because the index pages are loaded in key order. Each page is filled to nearly 100%. In contrast, when an Insert (2) or Update (3) operation fills an index page, the page is split in half, with half of the records being copied to a new page. This process causes the average index page to be about 50 to 65 percent full. If index balancing is used, it may be 65 to 75 percent full.
Another advantage of the Create Index (31) operation is that all the new index pages created are located close together at the end of the file. For large files, this means less distance for the read head to cover between reads.
This technique may not be faster when files are small, such as a few thousand records. The file would also need larger index fields to benefit. Moreover, if all index pages can fit into the transactional interface cache, this method shows no improvement in speed. But if only a small percentage of the index pages are in cache at any one time, this method saves a lot of extra index page writes. A file containing a million records can be built in minutes or hours instead of days with this technique. The greater the number of index pages in the file, the faster it is to build indexes with Create Index (31) than it is one record insert at a time.
In summary, the critical thing to avoid in loading a Pervasive PSQL file from scratch is not enough cache memory to hold all the index pages. If this is the case, use Create (14) to create the file without indexes and use Create Index (31) when all the data records have been inserted.
See Btrieve API Guide for detailed information on these operations.