|
|
|
|
Throughout this document we assume (and strongly suggest) a design
in which each RedBase relation is stored in its own RM component file,
and each tuple of a RedBase relation is stored as a record in the
appropriate file. Consequently, "files" are now sometimes referred to
as "relations," and "records" are now referred to as "tuples."
|
|
DBname is a user-specified name associated with a particular database. Your RedBase system should be able to manage multiple databases, although it is invoked (using the redbase command) for only one database at a time. All of the files associated with a given database DBname -- files containing relations, indexes, and metadata -- should be stored in a subdirectory for that database; the subdirectory can be called DBname. Command dbcreate should set up the subdirectory. Command dbdestroy should destroy the subdirectory and all of its contents. One of the first things command redbase should do is change the current working directory to the subdirectory for the specified DBname.
main (
int argc, // length of array argv
char **argv ) // argv is an array of pointers
// argv[0] points to the name of the command
// argv[1] points to argument DBname
{
char *dbname;
char command[80] = "mkdir ";
if (argc != 2) {
cerr << "Usage: " << argv[0] << " dbname \n";
exit(1);
}
// The database name is the second argument
dbname = argv[1];
// Create a subdirectory for the database
system (strcat(command,dbname));
if (chdir(dbname) < 0) {
cerr << argv[0] << " chdir error to " << dbname << "\n";
exit(1);
}
// Create the system catalogs
...
}
char command[80] = "rm -r "; system(strcat(command, dbname));You don't need to worry about individually removing each file for DBname -- all files in the subdirectory will be removed when you invoke rm with the -r option.
main(...)
{
...
// initialize RedBase components
PF_Manager pfm;
RM_Manager rmm(pfm);
IX_Manager ixm(pfm);
SM_Manager smm(ixm, rmm);
QL_Manager qlm(smm, ixm, rmm);
// open the database
if (rc = smm.OpenDb(dbname)) ...
// call the parser
RBparse(pfm, smm, qlm);
// close the database
if (rc = smm.CloseDb()) ...
}
Methods SM_Manager::OpenDb and SM_Manager::CloseDb
are described in the SM interface below. We
are providing stub files defining the QL_Manager class (more
on this later). You do not need to write routine RBparse --
it is provided by the parser (see parse.y if you're
interested). When RBparse is called, a loop repeatedly
prompts the user for a command and then calls appropriate SM or QL
methods to process the command. The command loop terminates and the
call to RBparse returns when the user types "exit;"
at the parser prompt. (Termination also occurs if a negative error
code is returned from an SM or QL method call, or if an end-of-file
character is generated accidentally.)
|
|
To describe the DDL and system utility commands, we'll first specify the syntax of the commands and explain what they should do from a user's perspective. Then we'll specify the SM component interface, which includes the methods that support the commands, as well as methods OpenDb and CloseDb.
The create table command creates a relation with the specified name and schema. Relation and attribute names are limited to MAXNAME = 24 characters each, and must begin with a letter. Within a given database every relation name must be unique, and within a given relation every attribute name must be unique. (However, relation names may be duplicated across databases, and attribute names may be duplicated across relations.) Every relation must have at least one and no more than MAXATTRS = 40 attributes. The syntax of each Type argument is a one-character type specification -- "i", "f", or "c", for integer, float, or character string, respectively -- followed by an integer length. For "i" and "f" the only valid length is 4; for "c" valid lengths range from 1 to MAXSTRINGLEN = 255. As an example, "c50" is the type specification for an attribute whose values are character strings of length 50. Note that when the type specification "cN" is given, the attribute must be able to store strings of length N -- the system should not use one of the characters for null termination. The constants MAXNAME, MAXATTRS, and MAXSTRINGLEN are defined in redbase.h.
The drop table command destroys relation relName, along with all indexes on the relation.
The create index command creates an index on attribute attrName of relation relName and builds the index for the current tuples in the relation. Only one index may be created for each attribute of a relation.
The drop index command destroys the index on attribute attrName of relation relName.
The load utility performs bulk loading of the named relation from the specified Unix file: all tuples specified in the load file are inserted into the relation. The FileName should be a complete Unix path enclosed in quotes (e.g., "/usr/class/cs346/redbase/data/student.data"). This file holds the data to be loaded into the relation in ASCII format. Every tuple to be inserted is on a separate line in the file, with attribute values separated by commas and appearing in the same order as in the create table command that was executed for relation relName.
Integer attribute values are specified in the ASCII load file as, e.g., 10 or -5, float values are specified as, e.g., 3.5E-3, and you may assume these values are in the right format. Character string values are specified as, e.g., Smith (without quotes). You may assume that character string attribute values will not contain commas. Character strings in the load file can be of any length up to the length specified for the corresponding attribute, including zero length (no characters for that field in the load file). If a character string is too long, you may silently truncate it, or you may generate a nonzero return code and stop loading, whichever behavior you prefer.
Some example load files can be found in directory /usr/class/cs346/redbase/data/. We strongly suggest that you create your own additional data files for loading, as well as for future testing and experimentation. If you would like to share your data files with the class, you may place them in directory /usr/class/cs346/redbase/data/, as discussed in the RedBase Logistics document.
The square brackets here are not part of the command syntax -- they indicate that relName is optional. If a relName is not specified, then the help utility prints the names of all relations in the database. (You may include additional information if you like.) If a relName is specified, then the help utility prints the name, type, length, and offset of each attribute in the specified relation, together with any other information you feel may be useful.
The print utility displays the current set of tuples in relation relName.
The set utility allows the user to set system parameters without needing to recompile the system, or even exit the RedBase prompt. You should determine if there are any parameters you might find useful to control in this manner. (Examples might be level of tracing information produced, debugging flags, etc.) You are not required to implement set for any particular parameters; we're just providing the necessary "hooks" in case you find it convenient to do so.
|
|
// Used by SM_Manager::CreateTable
struct AttrInfo {
char *attrName; // Attribute name
AttrType attrType; // Type of attribute
int attrLength; // Length of attribute
};
// Used by Printer class
struct DataAttrInfo {
char relName[MAXNAME+1]; // Relation name
char attrName[MAXNAME+1]; // Attribute name
int offset; // Offset of attribute
AttrType attrType; // Type of attribute
int attrLength; // Length of attribute
int indexNo; // Attribute index number
};
class SM_Manager {
public:
SM_Manager (IX_Manager &ixm, RM_Manager &rmm); // Constructor
~SM_Manager (); // Destructor
RC OpenDb (const char *dbName); // Open database
RC CloseDb (); // Close database
RC CreateTable (const char *relName, // Create relation
int attrCount,
AttrInfo *attributes);
RC DropTable (const char *relName); // Destroy relation
RC CreateIndex (const char *relName, // Create index
const char *attrName);
RC DropIndex (const char *relName, // Destroy index
const char *attrName);
RC Load (const char *relName, // Load utility
const char *fileName);
RC Help (); // Help for database
RC Help (const char *relName); // Help for relation
RC Print (const char *relName); // Print relation
RC Set (const char *paramName, // Set system parameter
const char *value);
};
This method should first update the system catalogs: a tuple for the new relation should be added to a catalog relation called relcat, and a tuple for each attribute should be added to a catalog relation called attrcat. (Catalogs relcat and attrcat describe all relations and attributes in the database, respectively; details are given below.) To create entries for the new relation in the catalogs, you will need to calculate tuple length and attribute offset information from the arguments passed to this method. After updating the catalogs, method RM_Manager::CreateFile should be called to create a file that will hold the tuples of the new relation.
|
|
Note: You should not call SM_PrintError
directly from within the SM (or any other) component.
SM_PrintError is called automatically by the parser when it
receives a nonzero return code from an SM component method it invokes.
You may, however, need to call SM_PrintError from your
stand-alone programs implementing the command line
utilities dbcreate, dbdestroy, and
redbase.
|
|
For the basic project you will need only two catalogs: relcat and attrcat. Please do not change the names of these catalogs. These two catalog relations should be created when the "dbcreate DBname" command is executed, and they should be stored in the subdirectory called DBname (each database has its own catalogs). Since you will create and manipulate these relations using RM and perhaps IX component methods, the stand-alone code you write for the dbcreate command must be linked with your RM and IX components.
The relcat relation is used to keep track of all relations in the database. There is one tuple in relcat for each relation. Examples of the kind of information you might choose to include in each tuple of the relcat relation are:
| relName | relation name |
| tupleLength | tuple length in bytes |
| attrCount | number of attributes |
| indexCount | number of indexed attributes |
The actual information in relcat may vary from design to design -- you should include all information that you find useful.
The attrcat relation is used to keep track of all attributes of all relations in the database. There is one tuple in attrcat for each attribute of each relation. Examples of the kind of information you might choose to include in each tuple of the attrcat relation are:
| relName | this attribute's relation |
| attrName | attribute name |
| offset | offset in bytes from beginning of tuple |
| attrType | attribute type |
| attrLenth | attribute length |
| indexNo | index number, or -1 if not indexed |
Again, the actual information in attrcat may vary from design to design, and you should include all information that you find useful.
You should enable the user to access the catalog relations using standard commands, as if the catalogs were regular database relations. To do so, you will need to insert descriptions for relcat and attrcat into relcat and attrcat when you create them. Users should not, however, be permitted to load tuples into or drop either of the system catalogs.
Because the system catalogs are accessed very frequently, we suggest that you open the catalogs for a database when the database is opened, then keep the catalogs open until the database is closed. One effect of doing so is that updates to the catalogs may not be reflected onto disk immediately. Thus, if you open a catalog a second time (to implement the help utility, for example, or to print the contents of a catalog), then you may not see the most current version of the catalog. One solution to this problem is to call RM_FileHandle::ForcePages each time a catalog is changed -- don't forget to also flush any header information, and to call IX_FileHandle::ForcePages for any indexes you've created on the catalog.
|
|
|
|
The parser is implemented using flex and yacc. If
you would like to modify the parser, the file called
Parser.HowTo explains how to get started.
|
|
class Printer {
public:
Printer(const DataAttrInfo *attributes, const int attrCount);
~Printer();
void PrintHeader(ostream &c) const;
Void Print(ostream &c, const char * const data);
void Print(ostream &c, const void * const data[]);
void PrintFooter(ostream &c) const;
};
As an example of how the Printer class is used, the following code segment is similar to what you will write in your SM_Manager::Print method. Note that this code is simplified, and you will need to fill in the details.
DataAttrInfo *attributes;
int attrCount;
RM_FileHandle rfh;
RM_Record rec;
char *data;
// Fill in the attributes structure, define the RM_FileHandle
...
// Instantiate a Printer object and print the header information
Printer p(attributes, attrCount);
p.PrintHeader(cout);
// Open the file and set up the file scan
if ((rc=rmm->OpenFile(relName, rfh)))
return(rc);
RM_FileScan rfs;
if ((rc = rfs.OpenScan(rfh, INT, sizeof(int), 0, NO_OP, NULL)))
return (rc);
// Print each tuple
while (rc!=RM_EOF) {
rc = rfs.GetNextRec(rec);
if (rc!=0 && rc!=RM_EOF)
return (rc);
if (rc!=RM_EOF) {
rec.GetData(data);
p.Print(cout, data);
}
}
// Print the footer information
p.PrintFooter(cout);
// Close the scan, file, delete the attributes pointer, etc.
...
|
|
| reset buffer; | Remove all unpinned pages from the buffer pool |
| print buffer; | Display simple information about pages in the buffer |
| resize buffer i; | Resize the buffer pool to i pages (i an integer) |
| print io; | Display the I/O statistics |
| reset io; | Reset the I/O statistics |
|
|
dbcreate, dbdestrory, and
redbase, which should be the result of running
"make" from within your submission directory.
Because the SM component enables you to work with the interactive RedBase prompt and submit user-level commands, a separate test program is not required. However, you will need to create ASCII data files in order to bulk-load your relations, and you may find it convenient to use scripts for testing. There are some data files located in directory /usr/class/cs346/redbase/data/ that you may use if you like, but we strongly encourage you to create your own additional data files. We also have provided an initial test shell script sm_test and one example test sm_test.1, obtained when you run setup. The example test loads relations from the data files mentioned above. As always, you will need to generate many more tests to thoroughly exercise your code, and we will conduct tests of our own during the grading process. You should find that, beginning with this component, it's much more fun to test your system since you can now interact with it in user mode.