User:Ravenhurst Xeno

From Second Life Wiki
Revision as of 13:16, 28 April 2008 by Ravenhurst Xeno (talk | contribs)
Jump to navigation Jump to search


LSL Database

These programs comprise RBase, an entirely LSL based database system. With an appropriate hashing function, it should be able to easily handle 25,000 entries and/or 1 MB of data.


The RBase components do nothing on their own but are designed to be a basic key/value database backend for one or more client scripts. All the database component scripts and the client scripts are contained within a prim and the client script(s) communicate with the database through llMessageLinked().

This wiki page has some basic information for using the LSLbase scripts. The bulk of the information on using the database system is contained in the comments in the scripts themselves.

License

RBase is released under the GNU Public License


Components

The complete RBase has several different components:


DB

This is the core database module. It can be used standalone by a client script or ganged together through DBMeta.

DBMeta

DBMeta is a multiple DB script controller that lets a client script gang together more than one core DB script for expanded data storage.

DBSort

DBSort is the sorting module that lets the client sort the data of one or more core DB scripts

DBTest

Interactive testing module to test the other components.


Sending RBase Commands

The client script(s) and the LSLbase component script(s) are placed in the same prim. The client communicates with the database components through link messages: <lsl>

 llMessageLinked( LINK_THIS, MsgId, DBCommand, Caller );

</lsl>

LINK_THIS

All components are in the same prim

MsgId

The client program can set this to an arbitrary value and responses from the database components will have the same message id.

DBCommand

The command to the database. It is of the form: Target|command[|operand][|operand...]

Each part of the command text is separated from the next by the '|' character.

Target

The name of the LSLbase script that is the target of this command. This can be "DB" for the basic core database, "DBMeta" if the client is using the controller. The core DB script(s) can be given any name so more than one can be used in the same prim.

command

This is one of several literal strings that indicate the database command to be processed. See the DB script for all the available commands.

operand

Many commands take one or more operand. Operands are separated by '|' characters so only the final operand to a command can actually contain a '|' character.

Caller

This is the name of calling client script. (llGetSriptName()). This will be used as the first part of the response message from the database component.


RBase Responses

Responses from the database will come back as link messages. The MsgId will match the MsgId of the command for which it is the response. The key value will be a s string containing the name of the RBase component that is sending the response. The actual message will be of the form: To|db_response|data[|data...]

To

This will match the Caller parameter in the original database command

db_response

The literal string 'db_response'

data[|data....]

All database commands that generate a response message will have one or more pieces of response data separated by '|' charaters


Sample Script

This shows some of the basic usage of the LSLbase system.

<lsl>

// Sample Script Here

</lsl>

DB

This is the core of the database system. It can be used standalone directly by a client script or through the DBMeta controller script

<lsl> // // RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // DB - Core general database script. DB stores and retreives key/value // pairs for other scripts // // link message formats: // to|db_add|key|value // to|db_update|key|value // to|db_delete|key // to|db_list // to|db_keyexists|key // to|db_valexists|value // to|db_fetch|key // to|db_fetchndx|ndx // to|db_fetchall // to|db_entries // to|db_clear // to|db_maxentries|value // // to - string matching the name of this script or 'all' // db_* - literal command // key - string key. All keys are unique in the db lists // value - string value // // // db_add - add the key/value pair to the db. If the key // already exists, it is not added to the database // db_update - replace or add the key/value pair to the db. // if the key already exists, the value is updated // if the key doesn't exist, it is added as a new // key value pair. // db_delete - remove the key value pair specified by key // db_list - dump the db contents to stdout // db_keyexists - determine if the specified key exists // a link message response of: // to|db_response|[0|ndx] // is sent back to the calling script where 0 // means the key doesn't exist and a positive // value is the key's index in the db list (index // NOT offset) // To avoid response collision, the link_message // number will be set to whatever the calling // link message used. // db_valexists - does the specified value exist in the db // have an entry where they are currently not // departed. (see db_keyexists for response) // db_fetch - return the specified key's value. // to|db_response|[""|value] // is sent back to the calling script where an empty // value means the key doesn't exist (or the key // was added with an empty value. use db_keyexists // to differentiate). // To avoid response collision, the link_message // number will be set to whatever the calling // db_fetchndx - return the key and value in the specified // ndx location. Key/Value pairs are maintained // in the order they are added/updated. // db_fetchall - returns in succession all the key/value // pairs. The order returned is the ordered // they were added. // to|db_response|index|key|value // is sent back to the calling script. // db_fetchallsorted - returns in succession all the key/value // pairs. The order returned is lexigraphic // sort order of the keys. The response will be: // to|db_response|key|value // is sent back to the calling script. // To avoid response collision, the link_message // number will be set to whatever the calling // db_entries - returns the number of entries in the // database. // db_memory - returns the free memory low water mark // as tallied by llGetFreeMemory() // db_clear - resets and clears the database // db_ping - causes DB to return an empty response // db_status - prints basic version/status information // db_reset - restarts script // db_maxentries - limits the number of enties in the db to the value // amount. Defaults to 100. Entries added in excess // of the maximum will cause the oldest entries to be // silently dropped from the database. // if maxentries is set to less than 1, then no max // entry limit will be enforced and the DB script // will continue to accept new entries until it crashes // because of memory exhuastion // // History: // 07 Dec 07 Begin Ravenhurst Xeno // 23 Mar 08 Begin release version Ravenhurst Xeno // //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------


       //==========================================================//
       //                                                          //
       //                    Attribute Variables                   //
       //                                                          //
       //==========================================================//


string gProductId = "Ravenhust Xeno RBase DB Core"; integer gVersion = 2008032301;

string gCmdSepChar;

integer gSetup = TRUE;

// // signalling data string gTo; // who the message is sent to string gFrom; integer gSenderPrim; integer gMsgId;


// // These are related to taking, parsing and acting on command text // Commands can come from chat, notecards and (soon) link messages // string gOp; // command to act on string gOperand; // data for gOp


// // Misc. attributes string gMe = "";


// // History Specific Data list gKeys; list gValues; string gKey; string gValue; integer gMaxEntries = 100;

       //==========================================================//
       //                                                          //
       //                    General Utility Functions             //
       //                                                          //
       //==========================================================//



       //==========================================================//
       //                                                          //
       //                Communications Funtions                   //
       //                                                          //
       //==========================================================//


// // take the passed in message and break it into the command operation // and its operand. The command is the first string upto whitespace. The // operand is any text after the first white space. The operator // goes in gOp and the rest of the message goes into gOperand // integer parseCommand( string message ) {


   //
   // backwards compatibility
   gCmdSepChar = "|";
   integer ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gCmdSepChar = " ";
   }        
  
   //
   // parse command
   message = llStringTrim( message, STRING_TRIM );
   ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gOp         = message;
       gOperand    = "";
   } 
   else {
       gOp       = llGetSubString(message, 0 , ndx - 1 );
       if( ndx + 1 >= llStringLength( message ) ) {
           gOperand = "";
       }
       else {
           gOperand  = llGetSubString(message, ndx + 1 , -1 );
       }
   }
   //!!!!!llOwnerSay("parse op ["+gOp+"] rand ["+gOperand+"]");//!!!!!
   return(1);

}


// // link commands are just normal commands prepended with the target's // name (and blank space or |). The target goes into gTo, the operator // into gOp and the rest into gOperand. If the link message was valid // and for us, 1 is returned. Otherwise 0 is returned

integer parseLinkCommand( string message ) {

   message = llStringTrim( message, STRING_TRIM );
       
   //
   // backwards compatibility
   gCmdSepChar = "|";
   integer ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gCmdSepChar = " ";
   }
   //
   // pull off target
   ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       return(0);
   }
   gTo = llGetSubString( message, 0 , ndx - 1 );


   //
   // a message for us?
   if( gTo == gMe || gTo == "all" ) {
       //
       // process the rest
       message = llGetSubString( message, ndx + 1, -1 );
       return( parseCommand( message ) ) ;
   }
       
   return(0);

}


// // display some summary status information

integer status() {


   string  msg =  gProductId + ". v. "+(string) gVersion; 
   msg += " Status: ";
   llSay(0, "["+gMe+"] "+msg );
   llSay(0, "["+gMe+"] DB Entries: "+(string)llGetListLength(gKeys));
   llSay(0, "["+gMe+"] Free Memory : "+(string)llGetFreeMemory() );


   return(1);

}

       //==========================================================//
       //                                                          //
       //                Database Related Functions                //
       //                                                          //
       //==========================================================//


// // does the key exist in the db? integer isInKeys( string k ) {

   integer   ndx = llListFindList( gKeys, [k] );
   return(++ndx);

}

// // does the value exist in the db integer isInValues( string value ) {

   integer   ndx = llListFindList( gValues, [value] );
   return(++ndx);

}

sendResponse( string payload ) {

   if( payload != "" ) {
       payload = "|" + payload;
   }
   llMessageLinked(
                       gSenderPrim,
                       gMsgId,
                       gFrom+"|db_response"+payload,
                       gMe
   );

}

// // if the database has grown past its max size, this function // trims it back down by removing the oldest entries first. integer fifoDB() {

   if( gMaxEntries < 1 ) {
       return(0);
   }
   integer   nbr   = llGetListLength( gKeys );
   integer   stop  = (nbr - gMaxEntries);
   if( nbr > gMaxEntries ) {
       gKeys       = llDeleteSubList( gKeys, 0, (stop - 1) );
       gValues     = llDeleteSubList( gValues, 0, (stop - 1 ));
       return(stop + 1);
   }
   return(0);

}


// // processCommand. Commands // Commands are accepted from chat and link messages. // The command line must be parsed into gOp & gOperand by the parseCommand // function before transitioning to this function.

processCommand() {

   // 
   // process command This is the msster switch
   
   //
   // first up, generic state controls
   if( gOp == "db_reset" ) {
       llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
       llResetScript();
   }
   else if( gOp == "db_status" ) {
       status();
   }
   
   //
   // db commands
   else if( gOp == "db_add" ) {
       
       parseCommand( gOperand );
       gKey    = gOp;
       gValue  = gOperand;
       if( ! isInKeys( gKey ) ) {
           gKeys     = (gKeys = []) + gKeys + [gKey];
           gValues   = (gValues = []) + gValues + [gValue];
       }
       fifoDB();
   }
   else if( gOp == "db_update" ) {
       parseCommand( gOperand );
       integer   ndx;
       gKey    = gOp;
       gValue  = gOperand;
       if( ( ndx = isInKeys( gKey ) ) ) {
         --ndx;
         gKeys   = llDeleteSubList( gKeys, ndx, ndx );
         gValues = llDeleteSubList( gValues, ndx, ndx );
       }
       gKeys     = (gKeys = []) + gKeys + [gKey];
       gValues   = (gValues = []) + gValues + [gValue];
   }
   else if( gOp == "db_delete" ) {

       integer ndx;
       gKey  = gOperand;
       //
       // don't delete non-existant keys (duh!)
       if( (ndx = isInKeys( gKey )) ) {
         --ndx;
         gKeys   = llDeleteSubList( gKeys, ndx, ndx );
         gValues = llDeleteSubList( gValues, ndx, ndx );
       }
   }
   else if( gOp == "db_list" ) {
       integer   nbr = llGetListLength( gKeys );
       integer   i;
       for( i = 0 ; i < nbr ; ++i ) {
           llSay(
                     0,
                           "::"+gMe+" "+
                           "["+(string)(i+1)+"] "
                     +     llList2String( gKeys, i ) + " - "
                     +     llList2String( gValues, i )
           );
       }
   } 
   else if( gOp == "db_keyexists" ) {
       
       gKey    = gOperand;
       sendResponse( (string)isInKeys( gKey ) );
   }
   else if( gOp == "db_valexists" ) {
       
       gValue    = gOperand;
       sendResponse( (string)isInValues( gValue ) );
   }
   else if( gOp == "db_fetch" ) {
       gKey          = gOperand;
       integer   ndx = isInKeys( gKey );
       if( ndx ) {
           --ndx;
           sendResponse( llList2String( gValues, ndx ) );
       }
       else {
           sendResponse( "" );
       }
   }
   else if( gOp == "db_fetchndx" ) {
       integer   ndx = (integer) gOperand;
       --ndx;                  // index -> offset
       if( ndx >= 0 && ndx < llGetListLength( gKeys ) ) {
           sendResponse( 
                                 llList2String( gKeys,ndx  ) 
                           +     "|" 
                           +     llList2String( gValues,ndx )
           );
       }
       else {
           sendResponse( "|" );
       }
   }
   else if( gOp == "db_fetchall" ) {
       integer   len   = llGetListLength( gKeys );
       integer   i;
       for( i = 0 ; i < len ; ++i ) {
           sendResponse( 
                               (string)(i + 1) 
                           +   "|" + llList2String( gKeys, i  ) 
                           +   "|" + llList2String( gValues, i  )
           );
           
           //
           // output throttle
           if( i > 50 ) {
               llSleep(0.25);              // don't overrun client
           }
       }
       llSleep(0.5);                       // token lag defense 
       sendResponse("fetchalldone");
   }
   else if( gOp == "db_entries" ) {
       sendResponse( (string)llGetListLength( gKeys ) );
   }
   else if( gOp == "db_memory" ) {
       sendResponse( (string)llGetFreeMemory() );
   }
   else if( gOp == "db_clear" ) {
       gKeys = [];
       gValues = [];
   }
   else if( gOp == "db_ping" ) {
       sendResponse("");
   }
   else if( gOp == "db_maxentries" ) {
       gMaxEntries = (integer)gOperand;
       fifoDB();
   }
   return;


}

       //==========================================================//
       //                                                          //
       //                       States                             //
       //                                                          //
       //==========================================================//

default {


   state_entry() {    
         
       //
       // our one time setup is fairly basic
       if( gSetup ) {        
           gSetup = FALSE;
           llSetRemoteScriptAccessPin( 0 );
           gMe = llGetScriptName();     
       }
   } 
   
 
   //
   // or the control can come from inside the linkset
   link_message(integer senderNbr, integer nbr, string message, key id) {
       gFrom           = (string) id;
       gMsgId          = nbr;
       gSenderPrim     = senderNbr;
       if( parseLinkCommand( message ) ) {
           processCommand();
       }
   }   
   
   on_rez( integer param ) {
       llResetScript();
   }
   

}

</lsl>


DB Meta

The DBMeta script is used to control multiple DB core scripts together to create an expanded storage space. Each core DB script can contain about 100 - 200 key/value pairs and/or about 10K of data (which ever limit is hit first). The DBMeta script, with an appropriate hashing function, should be able to control up to about 200 core DB scripts. This would give a client script a theoretical maximum storage of up to 40,000 key/value pairs and/or 2 MB of data.

<lsl>

// // RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // // DBMeta - Meta DB controller. DBMeta is a meta controller for multiple // DB scripts. Using DBMeta with multiple DB scripts allows for // a greatly expanded database storage at the expense of a // few features of the native DB script. // // In this configuration, the client script doesn't communicate // directly with the back end DB script. Instead it sends its // commands to the DBMeta controller and it then relays the // command(s) to the appropriate DB storage scripts. Basically // DBMeta is a hashing switch and the DB storage scripts are the // hash buckets: // // Client Script<--+----------+ // | | | // V | | // DBMeta >----+ | // ^ | // | | // +----------+----------+--------+ | // | | | | | // v v v v | // DB1 DB2 DB3 DBx | // | | | | | // +----------+----------+--------+-----+ // // Any responses to the client script can come from either // the individual DB bucket script or from the DBMeta controller // depending on the client's command. // // By default, DBMeta is configured to accecpt UUID's as // a key value and hash on the first character of the UUID // into one of sixteen DB buckets. // // To change the hashing stragegy, rewrite the HashTag // function to generate whatever is an appropriate hash // value for the expected input keys. List all the valid // output hash tags of the HashTag function in the gBucketTags // list. Then install a DB script for each hash bucket tag // with the name gBucketScriptBase + Tag // // e.g. // // if your database will be storing avatar names, // change the HashTag function so it returns the first letter // of the avatars name. Set the varialbe gBucketTags to ba // a list [ "A", "B", .... , "Z" ]; .Then create 26 copies of // the DB script names DBA, DBB, DBC, .... , DBZ (assuming // gDBucketScriptBase == "DB"). // // For the most part, all the native DB commands operate // just the same through DBMeta. There are some that are // changed or not supported: // // db_maxentries - sets the max number of entries // each DB bucket can contain, not the // database as a whole // db_fetchndx - not supported. // // // // // // History: // 07 Feb 08 Begin Ravenhurst Xeno // Adapted from DB // 23 Mar 08 Begin release version Ravenhurst Xeno //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------


       //==========================================================//
       //                                                          //
       //                    Attribute Variables                   //
       //                                                          //
       //==========================================================//


string gProductId = "Ravenhust Xeno RBase DB Hasher"; integer gVersion = 2008032301;


string gCmdSepChar;

integer gSetup = TRUE;

string gTo; // who the message is sent to string gFrom; integer gMsgId; integer gSenderPrim;


// // These are related to taking, parsing and acting on command text // Commands can come from chat, notecards and (soon) link messages // string gOp; // command to act on list gOps; // further enumeration string gOperand; // data for gOp


// // Misc. attributes string gMe = "";


// // Database specific values string gKey; string gValue; string gTag; string gProcessing; string gProcessingFrom; integer gProcessingSenderPrim; integer gProcessingMsgId; string gBucketScriptBase = "DB "; // trailing space is significant integer gBucketScriptBaseLen; integer gSorting = FALSE; integer gFetching = FALSE; string gFetchFrom; integer gFetchSenderPrim; integer gFetchMsgId; integer gFetchBucket = 0; integer gSortBucket = 0; string gSortOn = "val"; string gSortDirection = "ascend"; string gSortBite = "-1"; string gSortingFrom; integer gSortingSenderPrim; integer gSortingMsgId;

// // change this so there is an entry matching each possible return value // of HashTag

list gBucketTags = [

                                   "0",
                                   "1",
                                   "2",
                                   "3",
                                   "4",
                                   "5",
                                   "6",
                                   "7",
                                   "8",
                                   "9",
                                   "A",
                                   "B",
                                   "C",
                                   "D",
                                   "E",
                                   "F"
                               ];

list gBucketResponses;


       //==========================================================//
       //                                                          //
       //                   Database Functions                     //
       //                                                          //
       //==========================================================//

// // this is a fairly generic first charactger hash usuable for // several different basic hashing stratagies just by changing // the available hash buckets

string HashTag( string str ) {

   //
   // change this strategy to suit your needs
   string  s   = llToUpper( llGetSubString( str, 0, 0 ) );
   if( llListFindList( gBucketTags, [ s ] ) >= 0 ) {
       return( s );
   }
   else {
       return( llList2String( gBucketTags, 0 ) );
   }
   

}

// // StartProcessing, FinishProcessing, AddResponse - these are used // when we will sit between the client and the buckets for the entire // transaction and we will respond to the client rather than the // DB bucket itself. This happens when we need to get information // from all the buckets and then refactor that data for the client. // So we send the command to all the buckets, get all their responses // and then send our response to the client.

StartProcessing( string cmd ) {

   integer                 i;
   integer                 len = llGetListLength( gBucketTags );


   //
   // only process one at a time
   if( gProcessing != "" ) {
       return;
   }


   //
   // clear our response accumulator
   gBucketResponses  =     [];
   for( i = 0 ; i < len ; ++i ) {
       gBucketResponses =        ( gBucketResponses = [] ) 
                             +   gBucketResponses
                             +   [ "" ];
   }
   //
   // backtrace
   parseCommand( cmd );
   gProcessing             = gOp;
   gProcessingFrom         = gFrom;
   gProcessingSenderPrim   = gSenderPrim;
   gProcessingMsgId        = gMsgId;


   // 
   // don't wait indefinetly
   llSetTimerEvent( len + 10 );
   SendToAllBuckets( cmd );
   return;
   

}

AddResponse( string from, string response ) {

   from = llGetSubString( from, gBucketScriptBaseLen, -1 );
   integer     ndx = llListFindList( gBucketTags, [from] );
   integer     len = llGetListLength( gBucketTags );
   
   if( ndx < 0 ) {
       return;
   }
   gBucketResponses = llDeleteSubList( gBucketResponses, ndx, ndx );
   gBucketResponses = llListInsertList( gBucketResponses, [response], ndx );
 
   //
   // have all the buckets had their say?
   for( ndx = 0 ; ndx < len ; ++ndx ) {
       if( llList2String( gBucketResponses, ndx ) == "" ) {
           return;
       }
   }
   FinishProcessing();

}


FinishProcessing() {

   integer           i;
   integer           len = llGetListLength( gBucketTags );
   integer           val;
   llSetTimerEvent(0);
   if( gProcessing == "" ) {
       return;
   }
   if( gProcessing  == "db_valexists" ) {
       val = 0;
       i = 0;
       while( i < len && ! val ) {
           val = (integer)llList2String( gBucketResponses, i );
           ++i;
       }
           
       llMessageLinked(
                           LINK_THIS,
                           gProcessingMsgId,
                           gProcessingFrom+"|db_response|"+(string)val,
                           gMe
       );
         
   }
   else if( gProcessing == "db_entries" ) {
       val = 0;
       for( i = 0 ; i < len ; i++ ) {
           val += (integer)llList2String( gBucketResponses, i );
       }
           
       llMessageLinked(
                           LINK_THIS,
                           gProcessingMsgId,
                           gProcessingFrom+"|db_response|"+(string)val,
                           gMe
       );
   }
   ResetProcessing();

}

ResetProcessing() {

   llSetTimerEvent(0);
   gProcessing           = "";
   gProcessingFrom       = "";
   gProcessingSenderPrim = -1;
   gProcessingMsgId      = -1;

}


       //==========================================================//
       //                                                          //
       //                Communications Functions                  //
       //                                                          //
       //==========================================================//

// // There are two sets of messages to the bucket scripts: Send & Proxy // // Send is the normal send a message to them and we get any return // message from the bucket script. The proxy messages are sent on // behalf of our caller and any responses from the bucket script // go directly back to our caller instead of us

ProxyToAllBuckets( string payload ) {

   integer     i;
   integer     len     = llGetListLength( gBucketTags );
   for( i = 0 ; i < len ; i++ ) {
       gTag  = llList2String( gBucketTags, i );
       ProxyToBucket( payload );
       llSleep(0.2);
   }

}


ProxyToBucket( string payload ) {

   llMessageLinked(
                     LINK_THIS,
                     gMsgId,
                     gBucketScriptBase+gTag+"|"+payload,
                     gFrom
   );

}


SendToAllBuckets( string payload ) {

   integer     i;
   integer     len     = llGetListLength( gBucketTags );
   for( i = 0 ; i < len ; i++ ) {
       gTag  = llList2String( gBucketTags, i );
       SendToBucket( payload );
   }

}


SendToBucket( string payload ) {

   llMessageLinked(
                     LINK_THIS,
                     gMsgId,
                     gBucketScriptBase+gTag+"|"+payload,
                     gMe
   );

}

SortNextBucket() {

   //
   // Done sorting yet?
   if( gSortBucket >= llGetListLength( gBucketTags ) ) {
       gSorting = FALSE;
       llSetTimerEvent(0.0);
       llMessageLinked(
                           gSortingSenderPrim,
                           gSortingMsgId,
                           gSortingFrom+"|db_response|sortdone",
                           gMe
       );
       return;
   }
   
   //
   // not done, start the sort of the next bucket.
   // responses are funnelled through us only so we know when
   // the bucket has finished sorting
   gTag        = llList2String( gBucketTags, gSortBucket );
   gSortBucket++;
   string      payload =       "DBSort|db_sort"
                           +   "|"+gBucketScriptBase+gTag
                           +   "|"+gSortOn
                           +   "|"+gSortDirection
                           +   "|"+gSortBite;
   llMessageLinked( 
                       LINK_THIS,
                       gMsgId,
                       payload,
                       gMe
   );
   llSetTimerEvent( 60.0 );                 // give sorter time to do its work
   
   

}

// // Similar to the sort, we funnel all the fetchs through us. This // isn't strictly neccessary but it helps keep the data to the client // in bucket order and means the client will only see one fetchalldone // signal.

FetchAllNext() {

   //
   // Done fetching yet?
   if( gFetchBucket >= llGetListLength( gBucketTags ) ) {
       gFetching = FALSE;
       llSetTimerEvent(0.0);
       llMessageLinked(
                           gFetchSenderPrim,
                           gFetchMsgId,
                           gFetchFrom+"|db_response|fetchalldone",
                           gMe
       );
       return;
   }
   
   //
   // not done, start the fetch of the next bucket.
   // responses are funnelled through us only so we know when
   // the bucket has finished fetching
   gTag        = llList2String( gBucketTags, gFetchBucket );
   gFetchBucket++;
   llMessageLinked( 
                       LINK_THIS,
                       gMsgId,
                       gBucketScriptBase+gTag+"|db_fetchall",
                       gMe
   );
   llSetTimerEvent( 5.0 );                 

}


// // take the passed in message and break it into the command operation // and its operand. The command is the first string upto whitespace. The

// // take the passed in message and break it into the command operation // and its operand. The command is the first string upto whitespace. The // operand is any text after the first white space. The operator // goes in gOp and the rest of the message goes into gOperand // integer parseCommand( string message ) {

   //
   // backwards compatibility
   gCmdSepChar = "|";
   integer ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gCmdSepChar = " ";
   }


   //
   // parse command
   message = llStringTrim( message, STRING_TRIM );
   ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gOp         = message;
       gOperand    = "";
   } 
   else {
       gOp       = llGetSubString(message, 0 , ndx - 1 );
       gOperand  = llGetSubString(message, ndx + 1 , -1 );
   }
   return(TRUE);

}


// // link commands are just normal commands prepended with the target's // name (and blank space or |). The target goes into gTo, the operator // into gOp and the rest into gOperand. If the link message was valid // and for us, 1 is returned. Otherwise 0 is returned

integer parseLinkCommand( string message ) {

   message = llStringTrim( message, STRING_TRIM );
   
   //
   // backwards compatibility
   gCmdSepChar = "|";
   integer ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gCmdSepChar = " ";
   }
   
   //
   // pull off target
   ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       return(FALSE);
   }
   gTo = llGetSubString( message, 0 , ndx - 1 );
   //
   // a message for us?
   if( gTo == gMe || gTo == "all" ) {
       //
       // process the rest
       message = llGetSubString( message, ndx + 1, -1 );
       return( parseCommand( message ) ) ;
   }
       
   return(FALSE);

}


// // display some summary status information

integer status() {

   string  msg =  gProductId + " version "+(string) gVersion; 
   msg += " Status: ";
   llSay(0, msg );
   llSay(0," Free Memory : "+(string)llGetFreeMemory() );
   ProxyToAllBuckets( "db_status" );
   return(TRUE);

}

       //==========================================================//
       //                                                          //
       //                Database Related Functions                //
       //                                                          //
       //==========================================================//


// // processCommand. Commands // Commands are accepted from chat and link messages. // The command line must be parsed into gOp & gOperand by the parseCommand // function before transitioning to this function.

processCommand() {

   // 
   // process command This is the msster switch
   
   //
   // first up, generic state controls
   if( gOp == "db_reset" ) {
       llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
       SendToAllBuckets( "db_reset" );
       llResetScript();
   }
   else if( gOp == "db_status" ) {
       status();
   }
   //
   // db commands
   else if( gOp == "db_response" ) {
       if( gSorting ) {
           
           if( gOperand == "sortdone" ) {
               llSetTimerEvent(0.0);
               SortNextBucket();
           }
           else {
               llMessageLinked(
                                   LINK_THIS,
                                   gSortingMsgId,
                                   gSortingFrom+"|"+gOp+"|"+gOperand,
                                   gMe
               );
               llSetTimerEvent(3.0);
           }
       }
       else if( gFetching ) {
           if( gOperand == "fetchalldone" ) {
               llSetTimerEvent(0.0);
               FetchAllNext();
           }
           else {
               llMessageLinked(
                                   LINK_THIS,
                                   gFetchMsgId,
                                   gFetchFrom+"|"+gOp+"|"+gOperand,
                                   gMe
               );
               llSetTimerEvent(3.0);
           }
       }
       else if( gProcessing != "" ) {
           AddResponse( gFrom, gOperand );
       }
   }
   else if( gOp == "db_add" ) {
       
       parseCommand( gOperand );
       gKey            = gOp;
       gValue          = gOperand;
       gTag            = HashTag( gKey );
       ProxyToBucket( "db_add|"+gKey+"|"+gValue );
   }
   else if( gOp == "db_delete" ) {

       gKey  = gOperand;
       gTag  = HashTag( gKey );
       ProxyToBucket( gOp+"|"+gKey );
   }
   else if( gOp == "db_list" ) {
       ProxyToAllBuckets( "db_list" );
   } 
   else if( gOp == "db_keyexists" ) {
       
       gKey    = gOperand;
       gTag    = HashTag( gKey );
       ProxyToBucket( gOp+"|"+gKey );
   }
   else if( gOp == "db_valexists" ) {
       StartProcessing( "db_valexists|"+gOperand);
   }
   else if( gOp == "db_fetch" ) {
       gKey          = gOperand;
       gTag          = HashTag( gKey );
       ProxyToBucket( gOp + "|" + gOperand );
   }
   else if( gOp == "db_fetchall" ) {
       //
       // we will intermediary this 
       gFetching           =   TRUE;
       gFetchBucket        =   0;
       gFetchFrom          =   gFrom;
       gFetchSenderPrim    =   gSenderPrim;
       gFetchMsgId         =   gMsgId;
       FetchAllNext();
   }
   else if( gOp == "db_entries" ) {
       StartProcessing( "db_entries" );
   }
   else if( gOp == "db_memory" ) {
       ProxyToAllBuckets( gOp );
   }
   else if( gOp == "db_clear" ) {
       ProxyToAllBuckets( gOp );
   }
   else if( gOp == "db_sort" ) {
       
       gOps                = llParseString2List( gOperand, ["|"], [] );
       gSorting            = TRUE;
       gSortBucket         = 0;
       gSortingFrom        = gFrom;
       gSortingSenderPrim  = gSenderPrim;
       gSortingMsgId       = gMsgId;
       gSortOn             = llList2String( gOps, 1);
       gSortDirection      = llList2String( gOps, 2);
       gSortBite           = llList2String( gOps, 3);
       SortNextBucket();
   }   
   return;


}

       //==========================================================//
       //                                                          //
       //                       States                             //
       //                                                          //
       //==========================================================//

// // default. Since the particle system is set it and forget it, the // default state's primary responsiblity is to handle communications. // Command input can come from chat, notecards and from other linked // prims. //

default {


   state_entry() {    
         
       //
       // Startup processing is pretty minimal
       if( gSetup ) {        
           gSetup = FALSE;
           llSetRemoteScriptAccessPin( 0 );
           gMe = llGetScriptName();   
           gBucketScriptBaseLen    = llStringLength( gBucketScriptBase );  
       }
   } 
 
   //
   // or the control can come from inside the linkset
   link_message(integer senderPrim, integer nbr, string message, key id) {
       gFrom       = (string) id;
       gMsgId      = nbr;
       gSenderPrim = senderPrim;
       if( parseLinkCommand( message ) ) {
           processCommand();
       }
   }   


   timer() {
       //
       // ending up here usually means a processing failure. 
       // Either a dead hash bucket script or really bad script lag
       if( gSorting ) {
           llOwnerSay(gMe+" Sorting Timeout <<<<<< DB Tag ["+gTag+"]"); 
           SortNextBucket();
       }
       else if( gFetching ) {
           llOwnerSay(gMe+" Fetch Timeout <<<<<< DB Tag ["+gTag+"]"); 
           FetchAllNext();
       }
       else if( gProcessing != "" ) {
           llOwnerSay(
                             gMe
                         +   " Process Timout:"+gProcessing+" <<<<< "
                         +   "DB Tag [" + gTag +"]" 
           );
           ResetProcessing();
       }
   }
   
   on_rez( integer param ) {
       llResetScript();
   }
   

}


</lsl>



DBSort

DBSort is a sorting module that can return the sorted contents of a single DB core module if called directly by the client script. Or if called via DBMeta, can return the sorted contents of all the DB core modules controlled by the DBMeta script. Sorry, each DB module is sorted individually. The database isn't sorted as a whole through DBMeta


<lsl>

// // RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // DBSort - DB module to sort the contents of a DB script and return // it to the caller in sorted order // // link message formats: // to|db_sort|db_script|key_or_val|direction|bite // // to - string matching the name of this script or 'all' // db_sort - literal command // db_script - name of the DB script containing the data to be // sorted. // key_or_val - one of the literals 'key' or 'val' to indicate // sorting on the data key or the data value // direction - one of the literals 'ascend' or 'descend' to // indicate sort in ascending order or descending order // bite - how much of the key or value to compare. If // bite is 0 or less, the whole value or key will // be compared. // Because sorting requires more memory than just // storing data, it is possible to crash a DBSort // script through memory exhaustion. Using a sensible // bite value reduces the amount of memory needed by // DBSort. // // History: // 18 Feb 08 Begin Ravenhurst Xeno // Adapted from DB // 23 Mar 08 Begin release version Ravenhurst Xeno //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------


       //==========================================================//
       //                                                          //
       //                    Attribute Variables                   //
       //                                                          //
       //==========================================================//


string gProductId = "Ravenhust Xeno RBase Sorter"; integer gVersion = 2008032301;


string gCmdSepChar;

integer gSetup = TRUE;

string gTo; // who the message is sent to string gFrom; integer gSenderPrim; integer gMsgId;


// // These are related to taking, parsing and acting on command text // Commands can come from chat, notecards and (soon) link messages // string gOp; // command to act on list gOps; string gOperand; // data for gOp


// // Misc. attributes string gMe = "";


// // Sort Specific Data list gSorts; string gDB; integer gSortOn = 0; // 0 = key, 1 = value integer gDirection = 0; // 0 = ascend, 1 = descend; integer gBite = 0; integer gIndex = 0; string gData; string gSortFrom; integer gSortFromPrim; integer gSortFromMsgId;


       //==========================================================//
       //                                                          //
       //                Communications Funtions                   //
       //                                                          //
       //==========================================================//


// // take the passed in message and break it into the command operation // and its operand. The command is the first string upto whitespace. The // operand is any text after the first white space. The operator // goes in gOp and the rest of the message goes into gOperand // integer parseCommand( string message ) {

   //
   // backwards compatibility
   gCmdSepChar = "|";
   integer ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gCmdSepChar = " ";
   }
       
   //
   // parse command
   message = llStringTrim( message, STRING_TRIM );
   ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gOp         = message;
       gOperand    = "";
   } 
   else {
       gOp       = llGetSubString(message, 0 , ndx - 1 );
       if( ndx + 1 >= llStringLength( message ) ) {
           gOperand = "";
       }
       else {
           gOperand  = llGetSubString(message, ndx + 1 , -1 );
       }
   }
   return(1);

}


// // link commands are just normal commands prepended with the target's // name (and blank space). The target goes into gTo, the operator // into gOp and the rest into gOperand. If the link message was valid // and for us, 1 is returned. Otherwise 0 is returned

integer parseLinkCommand( string message ) {

   message = llStringTrim( message, STRING_TRIM );
   
   //
   // backwards compatibility
   gCmdSepChar = "|";
   integer ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gCmdSepChar = " ";
   }


   //
   // pull off target
   ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       return(0);
   }
   gTo = llGetSubString( message, 0 , ndx - 1 );
   //
   // a message for us?
   if( gTo == gMe || gTo == "all" ) {
       //
       // process the rest
       message = llGetSubString( message, ndx + 1, -1 );
       return( parseCommand( message ) ) ;
   }
       
   return(0);

}


// // display some summary status information

integer status() {


   string  msg =  gProductId + " version "+(string) gVersion; 
   msg += " Status: ";
   llSay(0, "["+gMe+"] "+msg );
   llSay(0, "["+gMe+"] Free Memory : "+(string)llGetFreeMemory() );


   return(1);

}

       //==========================================================//
       //                                                          //
       //                Database Related Functions                //
       //                                                          //
       //==========================================================//
       

DoSort() {

   integer   ndx;
   integer   i;
   integer   len = gIndex * 2;
   //
   // goes off when we are done receiving responses to be sorted
   // sort our abbreviated version of the data and then have 
   // the db module return
   gSorts = llListSort( gSorts, 2 , gDirection );
   for( i = 1 ; i < len ; i += 2 ) {
       ndx = (integer)llList2String( gSorts, i );
       llMessageLinked(
                           gSortFromPrim,
                           gSortFromMsgId,
                           gDB+"|db_fetchndx|"+(string)ndx,
                           gSortFrom
       );
       
       llSleep(0.2);           // don't overrun the db module 
   }
   
   //
   // give the db module time to finish responding
   llSleep(0.5);
   
   //
   // end of sort
   llMessageLinked(
                       gSortFromPrim,
                       gSortFromMsgId,
                       gSortFrom+"|db_response|sortdone",
                       gMe
   );
   gDB         = "";
   gSorts      = [];
   gSortFrom   = "";

}


// // processCommand. Commands // Commands are accepted from chat and link messages. // The command line must be parsed into gOp & gOperand by the parseCommand // function before transitioning to this function.


processCommand() {

   // 
   // process command This is the msster switch
   
   //
   // first up, generic state controls
   if( gOp == "db_reset" ) {
       llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
       llResetScript();
   }
   else if( gOp == "db_status" ) {
       status();
   }
   else if( gOp == "db_sort" ) {
       //
       // !!! might want to start by finding out how many entries
       // !!! there are first and make adjustments based on that?
       gSortFrom         = gFrom;
       gSortFromPrim     = gSenderPrim;
       gSortFromMsgId    = gMsgId;
       gOps          = llParseString2List( gOperand, ["|"], [] );    
       gDB           = llList2String( gOps, 0 );
       if(llToLower( llList2String( gOps, 1 ) ) == "val" ) {
           gSortOn = 1;
       }
       else {
           gSortOn = 0;
       }
       if( llToLower( llList2String( gOps, 2 ) ) == "descend" ) {
           gDirection = 0;
       }
       else {
           gDirection = 1;
       }
       gBite = (integer) llList2String( gOps, 3 );
       if( gBite < 1 ) {
           gBite = -1;
       }
       gIndex        = 0;
       gOps          = [];
       
       llMessageLinked( 
                           LINK_THIS,
                           101,
                               gDB+"|"
                           +   "db_fetchall",
                           gMe
       );
       llSetTimerEvent( 5 );
   }
   else if( gOp == "db_response" ) {
       if( gOperand == "fetchalldone" ) {
           
           //
           // got all the data to sort
           llSetTimerEvent(0);
           DoSort();
       }
       else {
           
           //
           // data to be accumulated,
           // break apart
           gOps          = llParseString2List( gOperand, ["|"], [] );
   
           //
           // get the basic sorting data item
           gData         =   llList2String( gOps, gSortOn + 1);
   
           //
           // trim it down to size?
           if( gBite > 0 && llStringLength( gData ) > ( gBite * 2 ) ) {
               gData   =       llGetSubString( gData, 0, gBite ) 
                           +   llGetSubString( gData, -gBite, -1 );
           }
   
           
           //
           // add it to the sort list
           gSorts    = (gSorts = []) + gSorts + [ 
                                                   gData, 
                                                   llList2String( gOps, 0) 
                                                ];
           gIndex++;
           llSetTimerEvent( 5 );
       }
   }
   return;


}

       //==========================================================//
       //                                                          //
       //                       States                             //
       //                                                          //
       //==========================================================//

// // default. Since the particle system is set it and forget it, the // default state's primary responsiblity is to handle communications. // Command input can come from chat, notecards and from other linked // prims. //

default {


   state_entry() {    
         
       //
       // on startups, try to read the default configuration notecard
       // (if it exists)
       if( gSetup ) {        
           gSetup = FALSE;
           llSetRemoteScriptAccessPin( 0 );
           gMe = llGetScriptName();     
       }
   } 
 
   
   //
   // or the control can come from inside the linkset
   link_message(integer senderNbr, integer nbr, string message, key id) {
       gFrom           = (string) id;
       gMsgId          = nbr;
       gSenderPrim     = senderNbr;
       if( parseLinkCommand( message ) ) {
           processCommand();
       }
   }   
   
   on_rez( integer param ) {
       llResetScript();
   }
   timer() {
       llSetTimerEvent(0);
       DoSort();        
   }
   

}

</lsl>


DBTest

This is an interactive test module for the database system. It is not needed for normal operation of the database and is included for anyone that wants to hack at the operational components. It is not well commented, but if you are motivated enough to be hacking the modules, everything in here should be easy enough to figure out.

<lsl>

// // RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno // // This file is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // // // // // DBTest - to interactivly test the SLbase modules // // History: // 10 Dec 07 Begin Ravenhurst Xeno // 23 Mar 08 Begin release version Ravenhurst Xeno // //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------


       //==========================================================//
       //                                                          //
       //                    Attribute Variables                   //
       //                                                          //
       //==========================================================//


string gProductId = "Ravenhust Xeno RBase DB Module Tester"; integer gVersion = 2008032301; integer gCommandLd; integer gCommandChannel = 10;


string gCmdSepChar;

integer gSetup = TRUE;

string gTo; // who the message is sent to key gAvKey; string gAvName; integer gMsgId; integer gSenderNbr;


// // These are related to taking, parsing and acting on command text // Commands can come from chat, notecards and (soon) link messages // list gOps; // holding space while parsing string gOp; // command to act on string gOperand; // data for gOp


// // Misc. attributes string gMe = "";


// // Used by the card reader states for inter-comm // key gQueryId;


// // test values integer gLastTestNbr = 0; list gTests; list gDeletedTests; string gTestKey; string gTestValue; integer gTestNbr; string gTarget = "DB";

string gSortCmd; integer gSortBite = 4;

integer gSaltBase; list gSaltChars = [

                               "0",
                               "1",
                               "2",
                               "3",
                               "4",
                               "5",
                               "6",
                               "7",
                               "8",
                               "9",
                               "a",
                               "b",
                               "c",
                               "d",
                               "e",
                               "f",
                               "g",
                               "h",
                               "i",
                               "j",
                               "k",
                               "l",
                               "m",
                               "n",
                               "o",
                               "p",
                               "q",
                               "r",
                               "s",
                               "t",
                               "u",
                               "v",
                               "w",
                               "x",
                               "y",
                               "z",
                               "A",
                               "B",
                               "C",
                               "D",
                               "E",
                               "F",
                               "G",
                               "H",
                               "I",
                               "J",
                               "K",
                               "L",
                               "M",
                               "N",
                               "O",
                               "P",
                               "Q",
                               "R",
                               "S",
                               "T",
                               "U",
                               "V",
                               "W",
                               "X",
                               "Y",
                               "Z"
                           ];
                               
                               
                               
                               
                               
                               
                               
                               
                               
                               
       //==========================================================//
       //                                                          //
       //                Communications Funtions                   //
       //                                                          //
       //==========================================================//

// // commReset - // commReset's main purpose is to reset the communication channels. // commReset() {

   // reset listener
   llListenRemove( gCommandLd );
   gCommandLd = llListen( gCommandChannel,  "", NULL_KEY, "" );
   return;

}


// // take the passed in message and break it into the command operation // and its operand. The command is the first string upto whitespace. The // operand is any text after the first white space. The operator // goes in gOp and the rest of the message goes into gOperand // integer parseCommand( string message ) {


   //
   // backwards compatibility
   gCmdSepChar = "|";
   integer ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gCmdSepChar = " ";
   }


   //
   // parse command
   message = llStringTrim( message, STRING_TRIM );
   ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gOp         = message;
       gOperand    = "";
   } 
   else {
       gOp       = llGetSubString(message, 0 , ndx - 1 );
       gOperand  = llGetSubString(message, ndx + 1 , -1 );
   }
   return(1);

}


// // link commands are just normal commands prepended with the target's // name (and blank space). The target goes into gTo, the operator // into gOp and the rest into gOperand. If the link message was valid // and for us, 1 is returned. Otherwise 0 is returned

integer parseLinkCommand( string message ) {

   message = llStringTrim( message, STRING_TRIM );
   
   //
   // backwards compatibility
   gCmdSepChar = "|";
   integer ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       gCmdSepChar = " ";
   }
   //
   // pull off target
   ndx = llSubStringIndex( message, gCmdSepChar );
   if( ndx < 0 ) {
       return(0);
   }
   gTo = llGetSubString( message, 0 , ndx - 1 );
   //
   // a message for us?
   if( gTo == gMe || gTo == "all" ) {
       //
       // process the rest
       message = llGetSubString( message, ndx + 1, -1 );
       return( parseCommand( message ) ) ;
   }
       
   return(0);

}


// // display some summary status information

integer status() {


   string  msg =  gProductId + ". v. "+(string) gVersion; 
   msg += " Status: ";
   llSay( 0, msg );
   llSay( 0, " DB_TEST Free Memory : "+(string)llGetFreeMemory() );


   return(1);

}

       //==========================================================//
       //                                                          //
       //            Monitor History Related Functions             //
       //                                                          //
       //==========================================================//

string SaltString( integer salt, integer len ) {

   integer saltLen = llGetListLength( gSaltChars );
   integer i;
   string  resp    =   "";
   integer ndx;
   
   for( i = 0 ; i < len ; i++ ) {
       
       ndx = (ndx + 1) * (salt + 1) * gSaltBase * 17;
       ndx %= saltLen;
       resp += llList2String( gSaltChars, ndx );
   }
   return( resp);
   

}


// // convert integer to 4 digit hex string string Int2Hex( integer nbr ) {

   integer     mod;
   integer     tmp;
   string      str;
   
   if( nbr >= 4096 ) {
       tmp = nbr / 4096;
       mod = tmp % 16;
       str = llList2String( gSaltChars, mod );
       nbr -= ( tmp * 4096 );
   }
   
   if( nbr >= 256 ) {
       tmp = nbr / 256;
       mod = tmp % 16;
       str += llList2String( gSaltChars, mod );
       nbr -= ( tmp * 256 );
   }
   
   if( nbr >= 16 ) {    
       tmp = nbr / 16;
       mod = tmp % 16;
       str += llList2String( gSaltChars, mod );
       nbr -= ( tmp * 16 );
   }
   
   mod = nbr % 16;
   str += llList2String( gSaltChars, mod );
   
   return( str );

}

makeTest( integer nbr ) {

 gTestNbr    = nbr;
 gTestKey    = (key) Int2Hex( nbr );
 gTestValue  =     SaltString( gTestNbr, 5 ) 
               +   " J. Random Value "
               +   Int2Hex( nbr ) + " ("
               +   (string)nbr +")";

}

getRandomTest() {

 integer   len = llGetListLength( gTests );
 integer   ndx = (integer) llFrand( (float) len );
 gTestNbr      = llList2Integer( gTests, ndx );
 makeTest( gTestNbr );
 llSay(0,
               "DB_TEST Grabbed random test ["
           +   (string)gTestKey+"] ["
           +   gTestValue+"]"
   );

}

getRandomDeletedTest() {

 integer   len = llGetListLength( gDeletedTests );
 integer   ndx = (integer) llFrand( (float) len );
 gTestNbr      = llList2Integer( gDeletedTests, ndx );
 makeTest( gTestNbr );
 llSay(0,
               "DB_TEST Grabbed deleted test ["
         +     (string)gTestKey+"] ["
         +     gTestValue+"]"
 );

}

addTest( integer nbr ) {

   gTests = ( gTests = [] ) + gTests + [nbr];

}

integer isTest( integer nbr ) {

   integer   ndx;
   ndx = llListFindList( gTests, [nbr] );
   return( ndx + 1 );

}

delTest( integer nbr ) {

   integer ndx = isTest( nbr );
   if( ndx ) {
       --ndx;
       gTests = llDeleteSubList( gTests, ndx, ndx );
       gDeletedTests = (gDeletedTests = []) + gDeletedTests + [nbr];
   }

}

dumpTests() {

   string  msg;
   integer len = llGetListLength( gTests );
   integer i;
   for( i = 0; i < len ; i++ ) {
       msg += llList2String( gTests, i ) + " ";  
   }
   llSay(0,"DB_TEST Test Avs: "+msg );


   len = llGetListLength( gDeletedTests );
   msg = "";
   for( i = 0; i < len ; i++ ) {
       msg += llList2String( gDeletedTests, i ) + " ";  
   }
   llSay(0,"DB_TEST Deleted Test Avs: "+msg );

}


integer dumpStringList( list l ) {

   integer     items;
   integer     ndx;
   
   items = llGetListLength( l );
   for( ndx = 0 ; ndx < items ; ndx++ ) {
       llOwnerSay("  ["+(string)ndx+"] ["+llList2String( l, ndx )+"]");
   }
   return(1);

}


// // processCommand. Commands // Commands are accepted from chat and link messages. // The command line must be parsed into gOp & gOperand by the parseCommand // function before transitioning to this function.

processCommand() {

   //
   // first up, generic state controls
   if( gOp == "reset" ) {
       llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
       llResetScript();
   }
   else if( gOp == "misc" ) {
       llDialog( 
                   gAvKey,
                   "Various Stuff",
                   [
                       "status",
                       "memory",
                       "entries",
                       "clear"
                   ],
                   gCommandChannel
       );
   }
   else if( gOp == "status" ) {
       llMessageLinked( 
                         LINK_THIS,
                         1,
                             gTarget+"|"
                         +   "db_status",
                         gMe
       );
       status();
   }
   else if( gOp == "add" ) {
       llDialog( 
                   gAvKey,
                   "How many to add",
                   [
                       "add_1",
                       "add_2",
                       "add_5",
                       "add_10",
                       "add_25",
                       "add_50",
                       "add_100"
                   ],
                   gCommandChannel
       );
   }
   else if(
                   gOp == "add_1"
               ||  gOp == "add_2"
               ||  gOp == "add_5"
               ||  gOp == "add_10"
               ||  gOp == "add_25"
               ||  gOp == "add_50"
               ||  gOp == "add_100"
   ) {
       integer     nbr = (integer)llDeleteSubString( gOp, 0, 3 );
       integer     i;
       
       for( i = 0 ; i < nbr ; ++i ) {
           makeTest( gLastTestNbr );
           ++gLastTestNbr;
           llSay(0,"DB_TEST Adding ["+(string)gTestKey+"] ["+gTestValue+"]");
           llMessageLinked( 
                             LINK_THIS,
                             2,
                                 gTarget+"|"
                             +   "db_add|"
                             +   gTestKey+"|"
                             +   gTestValue,
                             gMe
           );
           addTest(gTestNbr);
           llSleep(0.15);
       }
   }
   else if( gOp == "update" ) {
       llDialog(
                   gAvKey,
                   "Update Type",
                   [
                     "up_new",
                     "up_exist"
                   ],
                   gCommandChannel
       );
   }
   else if( gOp == "up_new") {
       makeTest( gLastTestNbr );
       ++gLastTestNbr;
       llSay(0,"DB_TEST Update/Add ["+(string)gTestKey+"] ["+gTestValue+"]");
       llMessageLinked( 
                         LINK_THIS,
                         2,
                             gTarget+"|"
                         +   "db_update|"
                         +   gTestKey+"|"
                         +   gTestValue,
                         gMe
       );
       addTest(gTestNbr);
   }
   else if( gOp == "up_exist") {
       getRandomTest();
       gTestValue = "Updated "+gTestValue;
       llSay(0,"DB_TEST Updating ["+(string)gTestKey+"] ["+gTestValue+"]");
       llMessageLinked( 
                         LINK_THIS,
                         2,
                             gTarget+"|"
                         +   "db_update|"
                         +   gTestKey+"|"
                         +   gTestValue,
                         gMe
       );
   }
   else if( gOp == "delete" ) {
       llDialog(
                   gAvKey,
                   "Delete Type",
                   [
                     "d_val_key",
                     "d_bad_key"
                   ],
                   gCommandChannel
       );
   }
   else if( gOp == "d_val_key" ) {
       getRandomTest();
       llMessageLinked( 
                         LINK_THIS,
                         3,
                             gTarget+"|"
                         +   "db_delete|"
                         +   (string)gTestKey,
                         gMe
       );
       delTest( gTestNbr );
       
   }
   else if( gOp == "d_bad_key" ) {
       makeTest( - 1 );
       llMessageLinked( 
                         LINK_THIS,
                         4,
                             gTarget+"|"
                         +   "db_delete|"
                         +   (string)gTestKey,
                         gMe
       );
   }
   else if( gOp == "list" ) {
       llMessageLinked( 
                         LINK_THIS,
                         5,
                             gTarget+"|"
                         +   "db_list",
                         gMe
       );
   } 
   else if( gOp == "mode" ) {
       llDialog( 
                   gAvKey,
                   "Current Test Target "+gTarget,
                   [
                       "mode_simple",
                       "mode_meta",
                       "mode_DB0",
                       "mode_DB1",
                       "mode_DB2",
                       "mode_DB3",
                       "mode_DB4",
                       "mode_DB5",
                       "mode_DB6",
                       "mode_DBA",
                       "mode_DBC",
                       "mode_DBF"
                   ],
                   gCommandChannel
       );
   }
   else if( gOp == "mode_simple" ) {
       gTarget   =   "DB";
   }
   else if( gOp == "mode_meta" ) {
       gTarget   =   "DBMeta";
   }
   else if( gOp == "mode_DB0" ){
       gTarget = "DB 0";
   }
   else if( gOp == "mode_DB1" ) {
       gTarget = "DB 1";
   }
   else if( gOp == "mode_DB2" ) {
       gTarget = "DB 2";    
   }  
   else if( gOp == "mode_DB3" ) {
       gTarget = "DB 3";
   }
   else if( gOp == "mode_DB4" ) {
       gTarget = "DB 4";
   }    
   else if( gOp == "mode_DB5" ) {
       gTarget = "DB 5";
   }    
   else if( gOp == "mode_DB6" ) {
       gTarget = "DB 6";
   }    
   else if( gOp == "mode_DBA" ) {
       gTarget = "DB A";
   }    
   else if( gOp == "mode_DBC" ) {
       gTarget = "DB C";
   }
   else if( gOp == "mode_DBF" ) {
       gTarget = "DB F";
   }                 
   else if( gOp == "keyexists" ) {
       llDialog(
                   gAvKey,
                   "key exists Check Type",
                   [
                     "key_val",
                     "key_bad"
                   ],
                   gCommandChannel
       );
   }
   else if( gOp == "key_val" ) {
       getRandomTest();
       llSay(0,"DB_TEST Good keyexists check");
       llMessageLinked( 
                         LINK_THIS,
                         6,
                             gTarget+"|"
                         +   "db_keyexists|"
                         +   (string)gTestKey,
                         gMe
       );
   }
   else if( gOp == "key_bad" ) {
       makeTest( - 1 );
       llSay(0,"DB_TEST Bad keyexists check");
       llMessageLinked( 
                         LINK_THIS,
                         7,
                             gTarget+"|"
                         +   "db_keyexists|"
                         +   (string)gTestKey,
                         gMe
       );
   }
   else if( gOp == "valexists" ) {
       llDialog(
                   gAvKey,
                   "key exists Check Type",
                   [
                     "val_val",
                     "val_bad"
                   ],
                   gCommandChannel
       );
   }
   else if( gOp == "val_val" ) {
       getRandomTest();
       llSay(0,"DB_TEST Good valexists check");
       llMessageLinked( 
                         LINK_THIS,
                         8,
                             gTarget+"|"
                         +   "db_valexists|"
                         +   (string)gTestValue,
                         gMe
       );
   }
   else if( gOp == "val_bad" ) {
       makeTest( - 1 );
       llSay(0,"DB_TEST Bad keyexists check");
       llMessageLinked( 
                         LINK_THIS,
                         9,
                             gTarget+"|"
                         +   "db_valexists|"
                         +   (string)gTestValue,
                         gMe
       );
   }
   else if( gOp == "fetchs" ) {
       llDialog( 
                   gAvKey,
                       "Fetch Type\n"
                   +   "  fetch_val - valid key fetch\n"
                   +   "  fetch_bad - invalid key fetch\n"
                   +   "  fetchndx_val - valid index fetch\n"
                   +   "  fetchndx_bad - invalid index fetch\n"
                   +   "  fetchall - fetch all",
                   [
                     "fetch_val",
                     "fetch_bad",
                     "fetchndx_val",
                     "fetchndx_bad",
                     "fetchall"
                   ],
                   gCommandChannel
       );
   }
   else if( gOp == "fetch_val" ) {
       getRandomTest();
       llSay(0,"DB_TEST Good fetch check");
       llMessageLinked( 
                         LINK_THIS,
                         10,
                             gTarget+"|"
                         +   "db_fetch|"
                         +   (string)gTestKey,
                         gMe
       );
   }
   else if( gOp == "fetch_bad" ) {
       makeTest( -1 );
       llSay(0,"DB_TEST Bad fetch check");
       llMessageLinked( 
                         LINK_THIS,
                         11,
                             gTarget+"|"
                         +   "db_fetch|"
                         +   (string)gTestKey,
                         gMe
       );
   }
   else if( gOp == "fetchndx_val" ) {
       getRandomTest();
       llSay(0,"DB_TEST Good fetch check");
       llMessageLinked( 
                         LINK_THIS,
                         12,
                             gTarget+"|"
                         +   "db_fetchndx|"
                         +   (string)gTestNbr,
                         gMe
       );
   }
   else if( gOp == "fetchndx_bad" ) {
       integer    len = llGetListLength( gTests );
       llSay(0,"DB_TEST Bad fetch check");
       llMessageLinked( 
                         LINK_THIS,
                         13,
                             gTarget+"|"
                         +   "db_fetchndx|"
                         +   (string)(len + 1),
                         gMe
       );
   }
   else if( gOp == "fetchall" ) {
       llMessageLinked( 
                         LINK_THIS,
                         14,
                             gTarget+"|"
                         +   "db_fetchall",
                         gMe
       );
   }
   else if( gOp == "entries" ) {
       llMessageLinked( 
                         LINK_THIS,
                         15,
                             gTarget+"|"
                         +   "db_entries",
                         gMe
       );
   }
   else if( gOp == "memory" ) {
       llMessageLinked( 
                         LINK_THIS,
                         16,
                             gTarget+"|"
                         +   "db_memory",
                         gMe
       );
   }
   else if( gOp == "clear" ) {
       llMessageLinked( 
                           LINK_THIS,
                           17,
                               gTarget+"|"
                           +   "db_clear",
                           gMe
       );
       
       gTestNbr        = 0;
       gTests          = [];
       gDeletedTests   = [];
   }
   else if( gOp == "sort" ) {
       
       //
       // build sort command in steps
       if( gTarget != "DBMeta" ) {
           gSortCmd =   "DBSort|db_sort|"+gTarget+"|";
       }
       else {
           gSortCmd =   gTarget+"|db_sort|X|";
       }
       
       llDialog(
                   gAvKey,
                   "\nSort type",
                   [
                       "sort_key",
                       "sort_val"
                   ],
                   gCommandChannel
       );
   }
   else if( 
                   gOp == "sort_key" 
               ||  gOp == "sort_val"
   ) {
       gSortCmd += llGetSubString( gOp, 5,7 )+"|";
       
       llDialog(
                   gAvKey,
                   "\nSort Dir",
                   [
                       "sort_ascend",
                       "sort_descend"
                   ],
                   gCommandChannel
       );
   }
   else if(
                   gOp == "sort_ascend"
               ||  gOp == "sort_descend"    
   ) {
       gSortCmd += llGetSubString( gOp, 5,-1)+"|"+(string)gSortBite;            
       //
       // Iniiate the sort
       llMessageLinked( 
                           LINK_THIS,
                           18,
                           gSortCmd,
                           gMe
       );
   }
   else if( gOp == "db_response" ) {
       llSay(0, "DB_TEST db_response ["+gOperand+"]");
   }
   else if( gOp == "keystress" ) {
       string  base    = "00000000-0000-0000-0000-00000000";
       string  tmpKey;
       integer i;
       
       
       llMessageLinked( 
                         LINK_THIS,
                         17,
                             gTarget+"|"
                         +   "db_clear",
                         gMe
       );
       
       llMessageLinked( 
                         LINK_THIS,
                         16,
                             gTarget+"|"
                         +   "db_memory",
                         gMe
       );    
       for( i = 0 ; i < 200 ; i++ ) {
           
   
           llSay(0, "DB_TEST Stress Add "+(string)i);
           tmpKey = base + (string)( 100 + i );
           llMessageLinked( 
                             LINK_THIS,
                             2,
                                 gTarget+"|"
                             +   "db_add|"
                             +   tmpKey+"|"
                             +   "",
                             gMe
           );
           llSleep(0.05);
       }
       
       llMessageLinked( 
                         LINK_THIS,
                         16,
                             gTarget+"|"
                         +   "db_memory",
                         gMe
       );
       llMessageLinked( 
                         LINK_THIS,
                         16,
                             gTarget+"|"
                         +   "db_list",
                         gMe
       );
       llMessageLinked( 
                         LINK_THIS,
                         16,
                             gTarget+"|"
                         +   "db_entries",
                         gMe
       );
       llMessageLinked( 
                         LINK_THIS,
                         16,
                             gTarget+"|"
                         +   "db_memory",
                         gMe
       );
   }     
   return;


}

       //==========================================================//
       //                                                          //
       //                       States                             //
       //                                                          //
       //==========================================================//

// // default. Since the particle system is set it and forget it, the // default state's primary responsiblity is to handle communications. // Command input can come from chat, notecards and from other linked // prims. //

default {


   state_entry() {    
         
       //
       // on startups, try to read the default configuration notecard
       // (if it exists)
       if( gSetup ) {        
           gSetup = FALSE;
           llSetRemoteScriptAccessPin( 0 );
           gMe = llGetScriptName();  
           gSaltBase = (llGetUnixTime() % llGetListLength(gSaltChars)) + 1;   
       }
       commReset();
   } 
 
   //
   // process commands from chat
   listen( integer channel, string name, key id, string message ) {
       gAvKey    = id;
       gAvName   = llKey2Name( gAvKey );
       parseCommand( message );
       processCommand();
   }
   
   touch_start( integer nbr ) {
       gAvKey    = llDetectedKey(0);
       gAvName   = llKey2Name( gAvKey );
       
       llDialog(
                 gAvKey,
                 "Monitor History Tester",
                 [
                     "misc",
                     "list",
                     "mode",
                     "add",
                     "delete",
                     "update",
                     "keyexists",
                     "valexists",
                     "fetchs",
                     "sort",
                     "keystress"
                 ],
                 gCommandChannel
               );
   }
   
   
   //
   // or the control can come from inside the linkset
   link_message(integer senderNbr, integer nbr, string message, key id) {
       gAvKey      = id;
       gAvName     = llKey2Name( gAvKey );
       gMsgId      = nbr;
       gSenderNbr  = senderNbr;
       if( parseLinkCommand( message ) ) {
           processCommand();
       }
   }   
   
   on_rez( integer param ) {
       llResetScript();
   }
   

}


</lsl>