Difference between revisions of "User:Ravenhurst Xeno"

From Second Life Wiki
Jump to navigation Jump to search
Line 11: Line 11:
prim and the client script(s) communicate with the database through llMessageLinked().
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.


There LSLBase has several different components:
== License ==
 
LSLBase is released under the 
[http://www.gnu.org/licenses/gpl.txt GNU Public License]
 
 
== Components ==
 
The complete LSLBase has several different components:




Line 54: Line 63:




== Using LSLBase ==
== Sending LSLBase Commands ==


The client script(s) and the LSLbase component script(s) are placed in the same prim. The client communicates
The client script(s) and the LSLbase component script(s) are placed in the same prim. The client communicates
Line 100: Line 109:
</td>
</td>
<td>
<td>
This is one of several literal strings that indicate the database command to be processed.
This is one of several literal strings that indicate the database command to be processed. See the DB script for all the available commands.
</td>
</tr>
<tr>
<td>
''operand''
</td>
<td>
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.
</td>
</tr>
<tr>
<td>
''Caller''
</td>
<td>
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.
</td>
</td>
</tr>
</tr>
</table>
</table>
</table>
== LSLBase 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 LSLBase component that is sending the response. The actual message will be of the form: '''To|db_response|data[|data...]
<table {{Prettytable}}>
<tr>
<td>
'''To'''
</td>
<td>
This will match the Caller parameter in the original database command
</td>
</tr>
<tr>
<td>
'''db_response'''
</td>
<td>
The literal string 'db_response'
</td>
</td>
</tr>
</tr>
<tr>
<tr>
<td>
<td>
''operand''
'''data[|data....]'''
</td>
</td>
<td>
<td>
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.
All database commands that generate a response message will have one or more pieces of response data separated by '|' charaters
</td>
</td>
</tr>
</tr>
</table>
</table>
== 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>
//
// RXBase 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 RXBase 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>
//
// RXBase 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 RXBase 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>
//
// RXBase 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 RXBase 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();       
    }
   
}
== 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>
//
// RXBase 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 RXBase 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>
</lsl>

Revision as of 10:07, 29 March 2008


LSL Database

These programs comprise LSLBase, 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 LSLBase 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

LSLBase is released under the GNU Public License


Components

The complete LSLBase 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 LSLBase 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.


LSLBase 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 LSLBase 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> // // RXBase 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 RXBase 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>

// // RXBase 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 RXBase 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>

// // RXBase 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 RXBase 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();        
   }
   

}


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>

// // RXBase 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 RXBase 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>


</lsl>