User:Ravenhurst Xeno: Difference between revisions

From Second Life Wiki
Jump to navigation Jump to search
No edit summary
 
(10 intermediate revisions by the same user not shown)
Line 17: Line 17:
LSLBase is released under the   
LSLBase is released under the   
[http://www.gnu.org/licenses/gpl.txt GNU Public License]
[http://www.gnu.org/licenses/gpl.txt GNU Public License]


== Components ==
== Components ==
Line 61: Line 60:
</tr>
</tr>
</table>
</table>


== Sending LSLBase Commands ==
== Sending LSLBase Commands ==
Line 120: Line 118:
</td>
</td>
</tr>
</tr>
</table>
<tr>
<tr>
<td>
<td>
''Caller''
'''Caller'''
</td>
</td>
<td>
<td>
Line 128: Line 127:
</td>
</td>
</tr>
</tr>
</table>
</table>
</table>


== LSLBase Responses ==
== LSLBase Responses ==
Line 163: Line 159:
</tr>
</tr>
</table>
</table>


== Sample Script ==
== Sample Script ==
Line 171: Line 166:
<lsl>
<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
// RXBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno
Line 201: Line 187:
//
//
//
//
// DB - Core general database script. DB stores and retreives key/value
// DBExample    - This is an example DB client program
//      pairs for other scripts
//
//
// link message formats:
//             This script shows examples of using the RXBase system.
// to|db_add|key|value
//             It assumes a basic core module called 'DB' and sixteen
// to|db_update|key|value
//             ganged modules named 'DB 0'....'DB F' controlled through
// to|db_delete|key
//             DBMeta.
// 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'
// 28 Mar 08    Begin release version                  Ravenhurst Xeno
// db_*          - literal command
//-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------
// key            - string key. All keys are unique in the db lists
 
// value          - string value
 
 
string  gProductId          = "Ravenhust Xeno RXBase Example";
integer gVersion            = 2008032801;
 
 
integer gSetup              =  TRUE;
 
string gTo;                                // who the message is sent to
string  gFrom;
integer gSenderPrim;
integer gMsgId;
 
//
// state parameters
string  gTarget;
string  gKey;
string gValue;
 
 
 
//
//  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
// db_add        - add the key/value pair to the db. If the key
list    gOps;
//                 already exists, it is not added to the database
string  gOperand;                      // data for gOp
// 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.
// Misc. attributes
// db_delete      - remove the key value pair specified by key
string  gMe                =  "";
// db_list        - dump the db contents to stdout
 
// db_keyexists  - determine if the specified key exists
 
//                  a link message response of:
default {
//                  to|db_response|[0|ndx]
 
//                  is sent back to the calling script where 0
    state_entry() {
//                  means the key doesn't exist and a positive
        gMe    = llGetScriptName();
//                  value is the key's index in the db list (index
    }
//                  NOT offset)
   
//                  To avoid response collision, the link_message
    touch_start( integer nbrDetected ) {
//                  number will be set to whatever the calling
       
//                  link message used.
        llSay(0,"Adding some values directly to the DB module\n");
// db_valexists  - does the specified value exist in the db
       
//                 have an entry where they are currently not
        //
//                 departed. (see db_keyexists for response)
        // First we will interact directly with a single DB module
// db_fetch      - return the specified key's value.
        // and add some values to it
//                  to|db_response|[""|value]
       
//                 is sent back to the calling script where an empty
        gTarget    = "DB";
//                 value means the key doesn't exist (or the key
       
//                  was added with an empty value. use db_keyexists
        //
//                  to differentiate).
        // clear out any previous data
//                  To avoid response collision, the link_message
        llMessageLinked(
//                  number will be set to whatever the calling
                            LINK_THIS,
// db_fetchndx    - return the key and value in the specified
                            10,
//                  ndx location. Key/Value pairs are maintained
                            gTarget+"|db_clear",
//                 in the order they are added/updated.
                            gMe
// 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
        // add a few key/value pairs
//                  is sent back to the calling script.
        llMessageLinked(
// db_fetchallsorted    - returns in succession all the key/value
                            LINK_THIS,
//                  pairs. The order returned is lexigraphic
                            11,
//                  sort order of the keys. The response will be:
                            gTarget+"|db_add|red|This is the RED key value",
//                  to|db_response|key|value
                            gMe
//                  is sent back to the calling script.
        );
//                  To avoid response collision, the link_message
       
//                  number will be set to whatever the calling
        llMessageLinked(
// db_entries     - returns the number of entries in the
                            LINK_THIS,
//                  database.
                            12,
// db_memory      - returns the free memory low water mark
                                gTarget
//                  as tallied by llGetFreeMemory()
                            +  "|db_add|asharp|This is the ASHARP key value",
// db_clear      - resets and clears the database
                            gMe
// db_ping        - causes DB to return an empty response
        );
// db_status      - prints basic version/status information
      
// db_reset      - restarts script
        llMessageLinked(
// db_maxentries  - limits the number of enties in the db to the value
                            LINK_THIS,
//                  amount. Defaults to 100. Entries added in excess
                            13,
//                  of the maximum will cause the oldest entries to be
                                gTarget +
//                  silently dropped from the database.
                            "|db_add|green|This is the GREEN key value",
//                  if maxentries is set to less than 1, then no max
                            gMe
//                  entry limit will be enforced and the DB script
        );
//                  will continue to accept new entries until it crashes
       
//                  because of memory exhuastion
        llMessageLinked(
//
                            LINK_THIS,
// History:
                            14,
// 07 Dec 07    Begin                                  Ravenhurst Xeno
                                gTarget
// 23 Mar 08    Begin release version                  Ravenhurst Xeno
                            +  "|db_add|blue|This is the BLUE key value",
//
                            gMe
//-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------
        );


        //
        // list the data in the module. Note: the DB module will llSay
        // the values it contains.
        llSay(0,"List of DB's contents before delete");
        llMessageLinked(
                            LINK_THIS,
                            15,
                            gTarget + "|db_list",
                            gMe
        );
        llSleep(2.0);
       
        //
        // get rid of the odd man out
        llMessageLinked(
                            LINK_THIS,
                            16,
                            "DB|db_delete|asharp",
                            gMe
        );




         //==========================================================//
         llSay(0,"\nList of DB's contents after delete");
         //                                                         //
        llMessageLinked(
         //                   Attribute Variables                  //
                            LINK_THIS,
         //                                                          //
                            17,
         //==========================================================//
                            gTarget + "|db_list",
                            gMe
        );
        llSleep(2.0); 
       
             
        //
         // now we will go through a similar process but using the
         // dbmeta controller
          
         gTarget = "DBMeta";
       
        llSay(0,"\nStarting DBMeta controll. Adding Data via DBMeta");
        llMessageLinked(
                            LINK_THIS,
                            18,
                            gTarget + "|db_clear",
                            gMe
        );     
        llSleep(2.0);
           
        llMessageLinked(
                            LINK_THIS,
                            19,
                            gTarget + "|db_add|100|100 key value",
                            gMe
        );                       
        llMessageLinked(
                            LINK_THIS,
                            20,
                            gTarget + "|db_add|110|110 key value",
                            gMe
        );
        llMessageLinked(
                            LINK_THIS,
                            21,
                            gTarget + "|db_add|120|120 key value",
                            gMe
        );
        llMessageLinked(
                            LINK_THIS,
                            22,
                            gTarget + "|db_add|200|200 key value",
                            gMe
        );                                   
        llMessageLinked(
                            LINK_THIS,
                            23,
                            gTarget + "|db_add|300|300 key value",
                            gMe
        );


        //
        // list all the contents
        llSay(0,"\nList of data via DBMeta");
        llMessageLinked(
                            LINK_THIS,
                            24,
                            gTarget + "|db_list",
                            gMe
        );


string  gProductId          = "Ravenhust Xeno RXBase DB Core";
        llSleep(5.0);
integer gVersion            = 2008032301;
       
 
        //
string  gCmdSepChar;
        // list the contents of one of the buckets under DBMeta control
 
        llSay(0,"\nList of the DB 1 data bucket directly");
integer gSetup              =  TRUE;
        gTarget = "DB 1";
       
        llMessageLinked(
                            LINK_THIS,
                            25,
                            gTarget + "|db_list",
                            gMe
        );
   
        llSleep( 5.0 );
        state fetch;
                                                             
    }
       
}


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


state  fetch {
   
    state_entry() {
   
        llSay(0,"\nFetching Data Values");
 
        //
        // reteive from the single core db mudle
        gTarget = "DB";
        llMessageLinked(
                            LINK_THIS,
                            26,
                            gTarget+"|db_fetch|blue",
                            gMe
        );
       
        //
        // retrieve a value from the DBMeta controlled buckets
        gTarget = "DBMeta";
        llMessageLinked(
                            LINK_THIS,
                            27,
                            gTarget+"|db_fetch|200",
                            gMe
        );
   
        //
        // time for the fetches to return
        llSetTimerEvent( 5.0 ); 
    }
   
    //
    // or the control can come from inside the linkset
    link_message(integer senderPrim, integer nbr, string message, key id) {


//
        gOps = llParseString2List( message, ["|"], [] );
// These are related to taking, parsing and acting on command text
        if(
// Commands can come from chat, notecards and (soon) link messages
                llList2String( gOps, 0 ) == gMe
//
            && llList2String( gOps, 1 ) == "db_response"
string  gOp;                           // command to act on
        ) {
string gOperand;                       // data for gOp
            llSay(  
 
                    0,
                        "Fetch Response:  "
                    +  "MsgId  ["+(string)nbr+"]  "
                    +  "Message ["+message+"]  "
                    +  "From    ["+(string)id+"] "
            );
        }
    }  
   
    timer() {
        state sortSingle;
    }
   
   
}


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




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


         //==========================================================//
    state_entry() {
         //                                                         //
                     
         //                    General Utility Functions            //
         //
         //                                                          //
        // retreive them in sorted order. Notice this is sent to the
         //==========================================================//
         // DBSort module with the single DB module as the target
        // of the sort
        llSay(0,"\nSingle Bucket Sort");
         llMessageLinked(
                            LINK_THIS,
                            28,
                            "DBSort|db_sort|DB|key|ascend|0",
                            gMe
         );
          
    }
   
   
   
    //
    // or the control can come from inside the linkset
    link_message(integer senderPrim, integer nbr, string message, key id) {


        gOps = llParseString2List( message, ["|"], [] );
        if(
                llList2String( gOps, 0 ) == gMe
            &&  llList2String( gOps, 1 ) == "db_response"
        ) {
            if( llList2String( gOps, 2 ) == "sortdone" ) {
                llSay(0, "Single Sort Done");
                llSleep(5.0);
                state sortMulti;
            }
            else {
                llSay( 
                        0,
                            "Single Sort Response:  "
                        +  "MsgId  ["+(string)nbr+"]  "
                        +  "Message ["+message+"]  "
                        +  "From    ["+(string)id+"]  "
                );
            }
        }
    }
   
}


   
state  sortMulti {


    state_entry() {             


         //==========================================================//
        llSay(0,"\nSort all buckets via DBMeta");
         //                                                         //
         //
         //               Communications Funtions                  //
        // retreive them in sorted order. Notice this is sent to the
        //                                                          //
         // DBSort module with an invalid target as the sort target
         //==========================================================//
        // DBMeta will take over and provide the sorted response
        llMessageLinked(
                            LINK_THIS,
                            29,
                            "DBMeta|db_sort|X|key|ascend|0",
                            gMe
        );
 
          
    }
   
   
   
    //       llSay(0,"Huh .... "+gTarget+"|dblist" );//!!!
    // or the control can come from inside the linkset
    link_message(integer senderPrim, integer nbr, string message, key id) {
 
         gOps = llParseString2List( message, ["|"], [] );
        if(
                llList2String( gOps, 0 ) == gMe
            &&  llList2String( gOps, 1 ) == "db_response"
        ) {
            if( llList2String( gOps, 2 ) == "sortdone" ) {
                llSay(0, "Multi Sort Done");
            }
            else {
                llSay( 
                        0,
                            "Multi Sort Response:  "
                        +  "MsgId  ["+(string)nbr+"]  "
                        +  "Message ["+message+"]  "
                        +  "From    ["+(string)id+"]  "
                );
            }
        }
    }
   
}   
 
</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>
//
//
// take the passed in message and break it into the command operation
// RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno
// 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 ) {
// 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.
    // 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
// This program is distributed in the hope that it will be useful,
// name (and blank space or |). The target goes into gTo, the operator
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// into gOp and the rest into gOperand. If the link message was valid
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// and for us, 1 is returned. Otherwise 0 is returned
// GNU General Public License for more details.
 
//
integer parseLinkCommand( string message ) {
// You should have received a copy of the GNU General Public License
       
// along with this program; if not, write to the Free Software
    message = llStringTrim( message, STRING_TRIM );
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
       
//
    //
//
    // backwards compatibility
//
    gCmdSepChar = "|";
//
    integer ndx = llSubStringIndex( message, gCmdSepChar );
//
    if( ndx < 0 ) {
// DB - Core general database script. DB stores and retreives key/value
        gCmdSepChar = " ";
//      pairs for other scripts
    }
//
 
// link message formats:
    //
// to|db_add|key|value
    // pull off target
// to|db_update|key|value
    ndx = llSubStringIndex( message, gCmdSepChar );
// to|db_delete|key
    if( ndx < 0 ) {
// to|db_list
        return(0);
// to|db_keyexists|key
    }
// to|db_valexists|value
 
// to|db_fetch|key
    gTo = llGetSubString( message, 0 , ndx - 1 );
// to|db_fetchndx|ndx
 
// to|db_fetchall
 
// to|db_entries
    //
// to|db_clear
    // a message for us?
// to|db_maxentries|value
    if( gTo == gMe || gTo == "all" ) {
//
        //
// to            - string matching the name of this script or 'all'
        // process the rest
// db_*          - literal command
        message = llGetSubString( message, ndx + 1, -1 );
// key            - string key. All keys are unique in the db lists
        return( parseCommand( message ) ) ;
// value          - string value
    }
       
    return(0);
}
 
 
 
//
//
// display some summary status information
//  
 
// db_add        - add the key/value pair to the db. If the key
integer status() {
//                  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
    string  msg =  gProductId + ". v. "+(string) gVersion;
//                  if the key doesn't exist, it is added as a new
    msg += " Status: ";
//                  key value pair.
    llSay(0, "["+gMe+"] "+msg );
// db_delete      - remove the key value pair specified by key
    llSay(0, "["+gMe+"] DB Entries: "+(string)llGetListLength(gKeys));
// db_list        - dump the db contents to stdout
    llSay(0, "["+gMe+"] Free Memory : "+(string)llGetFreeMemory() );
// db_keyexists  - determine if the specified key exists
 
//                  a link message response of:
 
//                  to|db_response|[0|ndx]
    return(1);
//                  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
        //               Database Related Functions                //
//                  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.
// does the key exist in the db?
//                  to|db_response|[""|value]
integer isInKeys( string k ) {
//                  is sent back to the calling script where an empty
 
//                  value means the key doesn't exist (or the key
    integer  ndx = llListFindList( gKeys, [k] );
//                  was added with an empty value. use db_keyexists
    return(++ndx);
//                  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
// does the value exist in the db
//                  ndx location. Key/Value pairs are maintained
integer isInValues( string value ) {
//                  in the order they are added/updated.
 
// db_fetchall    - returns in succession all the key/value
    integer  ndx = llListFindList( gValues, [value] );
//                 pairs. The order returned is the ordered
    return(++ndx);
//                 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
sendResponse( string payload ) {
//                 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-------
 


    if( payload != "" ) {
        payload = "|" + payload;
    }


    llMessageLinked(
        //==========================================================//
                        gSenderPrim,
        //                                                          //
                        gMsgId,
        //                    Attribute Variables                  //
                        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 );
string  gProductId          = "Ravenhust Xeno RBase DB Core";
    integer   stop  = (nbr - gMaxEntries);
integer gVersion            = 2008032301;
    if( nbr > gMaxEntries ) {
 
        gKeys      = llDeleteSubList( gKeys, 0, (stop - 1) );
string  gCmdSepChar;
        gValues    = llDeleteSubList( gValues, 0, (stop - 1 ));
        return(stop + 1);
    }
    return(0);
}


integer gSetup              =  TRUE;


//
//
// processCommand. Commands
// signalling data
// Commands are accepted from chat and link messages.
string gTo;                                // who the message is sent to
// The command line must be parsed into gOp & gOperand by the parseCommand
string  gFrom;
// function before transitioning to this function.
integer gSenderPrim;
integer gMsgId;


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];
//  These are related to taking, parsing and acting on command text
            gValues  = (gValues = []) + gValues + [gValue];
//  Commands can come from chat, notecards and (soon) link messages
        }
//
        fifoDB();
string  gOp;                           // command to act on
    }
string  gOperand;                      // data for gOp
    else if( gOp == "db_update" ) {
 


        parseCommand( gOperand );
//
// Misc. attributes
string  gMe                =  "";


        integer  ndx;


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


         if( ( ndx = isInKeys( gKey ) ) ) {
         //==========================================================//
          --ndx;
        //                                                          //
          gKeys  = llDeleteSubList( gKeys, ndx, ndx );
        //                    General Utility Functions            //
          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,
        //                Communications Funtions                  //
                            "::"+gMe+" "+
        //                                                          //
                            "["+(string)(i+1)+"] "
        //==========================================================//
                      +    llList2String( gKeys, i ) + " - "
 
                      +    llList2String( gValues, i )
 
            );
//
        }
// take the passed in message and break it into the command operation
    }
// and its operand. The command is the first string upto whitespace. The
    else if( gOp == "db_keyexists" ) {
// operand is any text after the first white space. The operator
       
// goes in gOp and the rest of the message goes into gOperand
        gKey    = gOperand;
//
        sendResponse( (string)isInKeys( gKey ) );
integer parseCommand( string message ) {


    }
    else if( gOp == "db_valexists" ) {
       
        gValue    = gOperand;
        sendResponse( (string)isInValues( gValue ) );
    }
    else if( gOp == "db_fetch" ) {


        gKey          = gOperand;
    //
        integer   ndx = isInKeys( gKey );
    // backwards compatibility
        if( ndx ) {
    gCmdSepChar = "|";
            --ndx;
    integer ndx = llSubStringIndex( message, gCmdSepChar );
            sendResponse( llList2String( gValues, ndx ) );
    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 {
         else {
             sendResponse( "" );
             gOperand  = llGetSubString(message, ndx + 1 , -1 );
         }
         }
     }
     }
     else if( gOp == "db_fetchndx" ) {
     //!!!!!llOwnerSay("parse op ["+gOp+"] rand ["+gOperand+"]");//!!!!!
        integer  ndx = (integer) gOperand;
    return(1);
        --ndx;                  // index -> offset
}
        if( ndx >= 0 && ndx < llGetListLength( gKeys ) ) {
 
            sendResponse(
 
                                  llList2String( gKeys,ndx  )  
//
                            +     "|"  
// link commands are just normal commands prepended with the target's
                            +     llList2String( gValues,ndx )
// 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
        else {
 
            sendResponse( "|" );
integer parseLinkCommand( string message ) {
        }
       
    message = llStringTrim( message, STRING_TRIM );
       
    //
    // backwards compatibility
     gCmdSepChar = "|";
     integer ndx = llSubStringIndex( message, gCmdSepChar );
    if( ndx < 0 ) {
         gCmdSepChar = " ";
     }
     }
     else if( gOp == "db_fetchall" ) {
 
        integer  len  = llGetListLength( gKeys );
     //
        integer  i;
    // pull off target
        for( i = 0 ; i < len ; ++i ) {
    ndx = llSubStringIndex( message, gCmdSepChar );
            sendResponse(
    if( ndx < 0 ) {
                                (string)(i + 1)
         return(0);
                            +  "|" + 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 ) );
    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 ) ) ;
     }
     }
     else if( gOp == "db_memory" ) {
       
         sendResponse( (string)llGetFreeMemory() );
     return(0);
     }
}
    else if( gOp == "db_clear" ) {
 
        gKeys = [];
 
        gValues = [];
 
     }
//
    else if( gOp == "db_ping" ) {
// display some summary status information
        sendResponse("");
 
     }
integer status() {
    else if( gOp == "db_maxentries" ) {
        gMaxEntries = (integer)gOperand;
          
        fifoDB();
    string  msg =  gProductId + ". v. "+(string) gVersion;  
    }
     msg += " Status: ";
    return;
    llSay(0, "["+gMe+"] "+msg );
     llSay(0, "["+gMe+"] DB Entries: "+(string)llGetListLength(gKeys));
     llSay(0, "["+gMe+"] Free Memory : "+(string)llGetFreeMemory() );




    return(1);
}
}
         //==========================================================//
         //==========================================================//
         //                                                          //
         //                                                          //
         //                       States                            //
         //               Database Related Functions                //
         //                                                          //
         //                                                          //
         //==========================================================//
         //==========================================================//


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>
//
// 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);


}


== DB Meta ==
sendResponse( string payload ) {


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.
    if( payload != "" ) {
        payload = "|" + payload;
    }


<lsl>
    llMessageLinked(
                        gSenderPrim,
                        gMsgId,
                        gFrom+"|db_response"+payload,
                        gMe
    );
}


//
//
// RXBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno
// if the database has grown past its max size, this function
//
// trims it back down by removing the oldest entries first.
// This file is free software; you can redistribute it and/or modify
integer fifoDB() {
// it under the terms of the GNU General Public License as published by
   
// the Free Software Foundation; either version 2 of the License, or
    if( gMaxEntries < 1 ) {
// (at your option) any later version.
        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);
}
 
 
//
//
// This program is distributed in the hope that it will be useful,
// processCommandCommands
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// Commands are accepted from chat and link messages.  
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSESee the
// The command line must be parsed into gOp & gOperand by the parseCommand
// GNU General Public License for more details.
// function before transitioning to this function.  
//
 
// You should have received a copy of the GNU General Public License
processCommand() {
// along with this program; if not, write to the Free Software
     
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    //  
//
    // process command This is the msster switch
//
   
//
    //
//
    // first up, generic state controls
//
    if( gOp == "db_reset" ) {
//
        llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
// DBMeta - Meta DB controller. DBMeta is a meta controller for multiple
         llResetScript();
//          DB scripts. Using DBMeta with multiple DB scripts allows for
    }
//          a greatly expanded database storage at the expense of a
    else if( gOp == "db_status" ) {
//          few features of the native DB script.
        status();
//
     }
//         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
     // db commands
//          command(s) to the appropriate DB storage scripts. Basically
    else if( gOp == "db_add" ) {
//          DBMeta is a hashing switch and the DB storage scripts are the
       
//          hash buckets:
        parseCommand( gOperand );
//
        gKey    = gOp;
//                                Client Script<--+----------+
        gValue  = gOperand;
//                                      |         |          |
 
//                                      V        |          |
        if( ! isInKeys( gKey ) ) {
//                                    DBMeta >----+          |
            gKeys    = (gKeys = []) + gKeys + [gKey];
//                                      ^                    |
            gValues  = (gValues = []) + gValues + [gValue];
//                                      |                    |
        }
//                      +----------+----------+--------+     |
        fifoDB();
//                      |          |          |        |     |
    }
//                      v          v          v        v     |
    else if( gOp == "db_update" ) {
//                     DB1        DB2        DB3      DBx    |
 
//                      |          |          |        |     |
        parseCommand( gOperand );
//                       +----------+----------+--------+-----+
 
//
        integer   ndx;
//          Any responses to the client script can come from either
 
//          the individual DB bucket script or from the DBMeta controller
        gKey    = gOp;
//          depending on the client's command.
        gValue  = gOperand;
//
 
//          By default, DBMeta is configured to accecpt UUID's as
        if( ( ndx = isInKeys( gKey ) ) ) {
//          a key value and hash on the first character of the UUID
          --ndx;
//          into one of sixteen DB buckets.
          gKeys  = llDeleteSubList( gKeys, ndx, ndx );
//
          gValues = llDeleteSubList( gValues, ndx, ndx );
//          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-------


        gKeys    = (gKeys = []) + gKeys + [gKey];
        gValues  = (gValues = []) + gValues + [gValue];


    }
    else if( gOp == "db_delete" ) {
        integer ndx;


         //==========================================================//
         gKey  = gOperand;
         //                                                          //
         //
         //                   Attribute Variables                  //
         // 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 ) );


string  gProductId          = "Ravenhust Xeno RXBase DB Hasher";
    }
integer gVersion            = 2008032301;
    else if( gOp == "db_valexists" ) {
       
        gValue    = gOperand;
        sendResponse( (string)isInValues( gValue ) );
    }
    else if( gOp == "db_fetch" ) {


 
        gKey          = gOperand;
     
        integer   ndx = isInKeys( gKey );
string  gCmdSepChar;
        if( ndx ) {
 
            --ndx;
integer gSetup              =   TRUE;
            sendResponse( llList2String( gValues, ndx ) );
 
        }
string  gTo;                               // who the message is sent to
        else {
string  gFrom;
            sendResponse( "" );
integer gMsgId;
        }
integer gSenderPrim;
    }
 
    else if( gOp == "db_fetchndx" ) {
 
        integer  ndx = (integer) gOperand;
//
        --ndx;                 // index -> offset
//  These are related to taking, parsing and acting on command text
        if( ndx >= 0 && ndx < llGetListLength( gKeys ) ) {
//  Commands can come from chat, notecards and (soon) link messages
            sendResponse(
//
                                  llList2String( gKeys,ndx  )
string  gOp;                           // command to act on
                            +    "|"  
list    gOps;                           // further enumeration
                            +    llList2String( gValues,ndx )
string  gOperand;                      // data for gOp
            );
 
        }
 
        else {
//
            sendResponse( "|" );
// Misc. attributes
        }
string  gMe                =  "";
    }
 
    else if( gOp == "db_fetchall" ) {
 
        integer  len  = llGetListLength( gKeys );
//
        integer  i;
// Database specific values
         for( i = 0 ; i < len ; ++i ) {
string  gKey;
             sendResponse(
string  gValue;
                                (string)(i + 1)
string  gTag;
                            +   "|" + llList2String( gKeys, i  )
string  gProcessing;
                            +   "|" + llList2String( gValues, i )
string  gProcessingFrom;
            );
integer gProcessingSenderPrim;
           
integer gProcessingMsgId;
            //
string  gBucketScriptBase  =   "DB ";      // trailing space is significant
            // output throttle
integer gBucketScriptBaseLen;
            if( i > 50 ) {
integer gSorting            =   FALSE;
                llSleep(0.25);              // don't overrun client
integer gFetching          =   FALSE;
            }
string  gFetchFrom;
         }
integer gFetchSenderPrim;
        llSleep(0.5);                      // token lag defense
integer gFetchMsgId;
        sendResponse("fetchalldone");
integer gFetchBucket        =   0;
    }
integer gSortBucket         =   0;
    else if( gOp == "db_entries" ) {
string  gSortOn             =  "val";
        sendResponse( (string)llGetListLength( gKeys ) );
string gSortDirection      =   "ascend";
    }
string  gSortBite          =   "-1";
    else if( gOp == "db_memory" ) {
string gSortingFrom;
        sendResponse( (string)llGetFreeMemory() );
integer gSortingSenderPrim;
    }
integer gSortingMsgId;
    else if( gOp == "db_clear" ) {
 
        gKeys = [];
//
        gValues = [];
// change this so there is an entry matching each possible return value
    }
// of HashTag
    else if( gOp == "db_ping" ) {
 
        sendResponse("");
list    gBucketTags         =  [
    }
                                    "0",
    else if( gOp == "db_maxentries" ) {
                                    "1",
        gMaxEntries = (integer)gOperand;
                                    "2",
        fifoDB();
                                    "3",
    }
                                    "4",
    return;
                                    "5",
                                    "6",
                                    "7",
                                    "8",
                                    "9",
                                    "A",
                                    "B",
                                    "C",
                                    "D",
                                    "E",
                                    "F"
                                ];
list    gBucketResponses;




}
         //==========================================================//
         //==========================================================//
         //                                                          //
         //                                                          //
         //                   Database Functions                    //
         //                       States                            //
         //                                                          //
         //                                                          //
         //==========================================================//
         //==========================================================//


//
default {
// this is a fairly generic first charactger hash usuable for
   
// several different basic hashing stratagies just by changing
   
// the available hash buckets
    state_entry() {   
 
         
string  HashTag( string str ) {
        //
 
        // our one time setup is fairly basic
        if( gSetup ) {       
            gSetup = FALSE;
            llSetRemoteScriptAccessPin( 0 );
            gMe = llGetScriptName();   
        }
    }
   
 
     //
     //
     // change this strategy to suit your needs
     // or the control can come from inside the linkset
     string  s  = llToUpper( llGetSubString( str, 0, 0 ) );
     link_message(integer senderNbr, integer nbr, string message, key id) {
     if( llListFindList( gBucketTags, [ s ] ) >= 0 ) {
        gFrom          = (string) id;
        return( s );
        gMsgId          = nbr;
     }
        gSenderPrim     = senderNbr;
     else {
        if( parseLinkCommand( message ) ) {
         return( llList2String( gBucketTags, 0 ) );
            processCommand();
     }
        }
      
     }  
}
      
 
    on_rez( integer param ) {
//
         llResetScript();
// 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.
</lsl>
// 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 );


== 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.
    // only process one at a time
    if( gProcessing != "" ) {
        return;
    }


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


    //
    // 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;
         //                    Attribute Variables                  //
         while( i < len && ! val ) {
         //                                                          //
            val = (integer)llList2String( gBucketResponses, i );
         //==========================================================//
            ++i;
 
         }
 
           
string  gProductId          = "Ravenhust Xeno RBase DB Hasher";
         llMessageLinked(
integer gVersion            = 2008032301;
                            LINK_THIS,
 
                            gProcessingMsgId,
 
                            gProcessingFrom+"|db_response|"+(string)val,
     
                            gMe
string  gCmdSepChar;
        );
 
         
integer gSetup              =   TRUE;
    }
 
    else if( gProcessing == "db_entries" ) {
string  gTo;                                // who the message is sent to
        val = 0;
string gFrom;
        for( i = 0 ; i < len ; i++ ) {
integer gMsgId;
            val += (integer)llList2String( gBucketResponses, i );
integer gSenderPrim;
        }
           
        llMessageLinked(
                            LINK_THIS,
                            gProcessingMsgId,
                            gProcessingFrom+"|db_response|"+(string)val,
                            gMe
        );
    }


    ResetProcessing();
}


ResetProcessing() {
//
//  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


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


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




        //==========================================================//
//
        //                                                          //
// Database specific values
        //                Communications Functions                  //
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;


//
//
// There are two sets of messages to the bucket scripts: Send & Proxy
// change this so there is an entry matching each possible return value
//
// of HashTag
// 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 ) {
list    gBucketTags        =  [
 
                                    "0",
    integer    i;
                                    "1",
    integer    len    = llGetListLength( gBucketTags );
                                    "2",
                                    "3",
                                    "4",
                                    "5",
                                    "6",
                                    "7",
                                    "8",
                                    "9",
                                    "A",
                                    "B",
                                    "C",
                                    "D",
                                    "E",
                                    "F"
                                ];
list    gBucketResponses;


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


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


ProxyToBucket( string payload ) {
//
   
// this is a fairly generic first charactger hash usuable for
    llMessageLinked(
// several different basic hashing stratagies just by changing
                      LINK_THIS,
// the available hash buckets
                      gMsgId,
                      gBucketScriptBase+gTag+"|"+payload,
                      gFrom
    );


}
string  HashTag( string str ) {


 
    //
SendToAllBuckets( string payload ) {
     // change this strategy to suit your needs
 
     string  s  = llToUpper( llGetSubString( str, 0, 0 ) );
    integer     i;
     if( llListFindList( gBucketTags, [ s ] ) >= 0 ) {
     integer    len    = llGetListLength( gBucketTags );
         return( s );
 
    }
     for( i = 0 ; i < len ; i++ ) {
    else {
         gTag  = llList2String( gBucketTags, i );
         return( llList2String( gBucketTags, 0 ) );
         SendToBucket( payload );
     }
     }
   
}
}
//
// 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 );




SendToBucket( string payload ) {
   
    llMessageLinked(
                      LINK_THIS,
                      gMsgId,
                      gBucketScriptBase+gTag+"|"+payload,
                      gMe
    );
}
SortNextBucket() {
   
     //
     //
     // Done sorting yet?
     // only process one at a time
     if( gSortBucket >= llGetListLength( gBucketTags ) ) {
     if( gProcessing != "" ) {
        gSorting = FALSE;
        llSetTimerEvent(0.0);
        llMessageLinked(
                            gSortingSenderPrim,
                            gSortingMsgId,
                            gSortingFrom+"|db_response|sortdone",
                            gMe
        );
         return;
         return;
     }
     }
   
 
 
     //
     //
     // not done, start the sort of the next bucket.
     // clear our response accumulator
     // responses are funnelled through us only so we know when
     gBucketResponses  =     [];
     // the bucket has finished sorting
     for( i = 0 ; i < len ; ++i ) {
     gTag        = llList2String( gBucketTags, gSortBucket );
        gBucketResponses =        ( gBucketResponses = [] )
    gSortBucket++;
                              gBucketResponses
    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?
     // backtrace
     if( gFetchBucket >= llGetListLength( gBucketTags ) ) {
     parseCommand( cmd );
         gFetching = FALSE;
    gProcessing            = gOp;
        llSetTimerEvent(0.0);
    gProcessingFrom         = gFrom;
        llMessageLinked(
    gProcessingSenderPrim  = gSenderPrim;
                            gFetchSenderPrim,
    gProcessingMsgId        = gMsgId;
                            gFetchMsgId,
 
                            gFetchFrom+"|db_response|fetchalldone",
 
                            gMe
    //
        );
    // 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;
         return;
     }
     }
      
 
     gBucketResponses = llDeleteSubList( gBucketResponses, ndx, ndx );
    gBucketResponses = llListInsertList( gBucketResponses, [response], ndx );
 
     //
     //
     // not done, start the fetch of the next bucket.
     // have all the buckets had their say?
     // responses are funnelled through us only so we know when
     for( ndx = 0 ; ndx < len ; ++ndx ) {
    // the bucket has finished fetching
        if( llList2String( gBucketResponses, ndx ) == "" ) {
    gTag        = llList2String( gBucketTags, gFetchBucket );
            return;
    gFetchBucket++;
        }
    llMessageLinked(  
     }
                        LINK_THIS,
     FinishProcessing();
                        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


//
FinishProcessing() {
// 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 ) {


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


 
     llSetTimerEvent(0);
    //
     if( gProcessing == "" ) {
    // parse command
         return;
     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);
}


 
    if( gProcessing  == "db_valexists" ) {
//
        val = 0;
// link commands are just normal commands prepended with the target's
        i = 0;
// name (and blank space or |). The target goes into gTo, the operator
        while( i < len && ! val ) {
// into gOp and the rest into gOperand. If the link message was valid
            val = (integer)llList2String( gBucketResponses, i );
// and for us, 1 is returned. Otherwise 0 is returned
            ++i;
 
        }
integer parseLinkCommand( string message ) {
           
       
        llMessageLinked(
    message = llStringTrim( message, STRING_TRIM );
                            LINK_THIS,
   
                            gProcessingMsgId,
    //
                            gProcessingFrom+"|db_response|"+(string)val,
    // backwards compatibility
                            gMe
    gCmdSepChar = "|";
         );
    integer ndx = llSubStringIndex( message, gCmdSepChar );
         
    if( ndx < 0 ) {
         gCmdSepChar = " ";
     }
     }
      
     else if( gProcessing == "db_entries" ) {
    //
        val = 0;
    // pull off target
        for( i = 0 ; i < len ; i++ ) {
    ndx = llSubStringIndex( message, gCmdSepChar );
            val += (integer)llList2String( gBucketResponses, i );
    if( ndx < 0 ) {
        }
         return(FALSE);
           
        llMessageLinked(
                            LINK_THIS,
                            gProcessingMsgId,
                            gProcessingFrom+"|db_response|"+(string)val,
                            gMe
         );
     }
     }


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


ResetProcessing() {


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


//
}
// 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               //
         //                Communications Functions                 //
         //                                                          //
         //                                                          //
         //==========================================================//
         //==========================================================//


//
//
// processCommand.  Commands
// There are two sets of messages to the bucket scripts: Send & Proxy
// Commands are accepted from chat and link messages.
//
// The command line must be parsed into gOp & gOperand by the parseCommand
// Send is the normal send a message to them and we get any return
// function before transitioning to this function.  
// 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);
    }
}
 


processCommand() {
ProxyToBucket( string payload ) {
     
    //
    // process command This is the msster switch
      
      
     //
     llMessageLinked(
    // first up, generic state controls
                      LINK_THIS,
    if( gOp == "db_reset" ) {
                      gMsgId,
        llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
                      gBucketScriptBase+gTag+"|"+payload,
        SendToAllBuckets( "db_reset" );
                      gFrom
        llResetScript();
    );
    }
 
     else if( gOp == "db_status" ) {
}
         status();
 
 
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() {
   
     //
     //
     // db commands
     // Done sorting yet?
     else if( gOp == "db_response" ) {
     if( gSortBucket >= llGetListLength( gBucketTags ) ) {
         if( gSorting ) {
         gSorting = FALSE;
           
        llSetTimerEvent(0.0);
            if( gOperand == "sortdone" ) {
        llMessageLinked(
                llSetTimerEvent(0.0);
                            gSortingSenderPrim,
                SortNextBucket();
                            gSortingMsgId,
            }
                            gSortingFrom+"|db_response|sortdone",
            else {
                            gMe
                llMessageLinked(
        );
                                    LINK_THIS,
        return;
                                    gSortingMsgId,
    }
                                    gSortingFrom+"|"+gOp+"|"+gOperand,
   
                                    gMe
    //
                );
    // not done, start the sort of the next bucket.
                llSetTimerEvent(3.0);
    // responses are funnelled through us only so we know when
            }
    // the bucket has finished sorting
        }
    gTag        = llList2String( gBucketTags, gSortBucket );
        else if( gFetching ) {
    gSortBucket++;
            if( gOperand == "fetchalldone" ) {
    string      payload =      "DBSort|db_sort"
                llSetTimerEvent(0.0);
                            +  "|"+gBucketScriptBase+gTag
                FetchAllNext();
                            +  "|"+gSortOn
            }
                            +   "|"+gSortDirection
            else {
                            +   "|"+gSortBite;
                llMessageLinked(
    llMessageLinked(
                                    LINK_THIS,
                        LINK_THIS,
                                    gFetchMsgId,
                        gMsgId,
                                    gFetchFrom+"|"+gOp+"|"+gOperand,
                        payload,
                                    gMe
                        gMe
                );
    );
                llSetTimerEvent(3.0);
    llSetTimerEvent( 60.0 );                 // give sorter time to do its work
            }
   
        }
   
        else if( gProcessing != "" ) {
}
            AddResponse( gFrom, gOperand );
 
        }
//
    }
// Similar to the sort, we funnel all the fetchs through us. This
    else if( gOp == "db_add" ) {
// 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
        parseCommand( gOperand );
// signal.
        gKey            = gOp;
        gValue          = gOperand;
        gTag            = HashTag( gKey );
        ProxyToBucket( "db_add|"+gKey+"|"+gValue );


    }
FetchAllNext() {
    else if( gOp == "db_delete" ) {
        gKey  = gOperand;
        gTag  = HashTag( gKey );
        ProxyToBucket( gOp+"|"+gKey );


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


    }
    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
// take the passed in message and break it into the command operation
        gFetching          =  TRUE;
// and its operand. The command is the first string upto whitespace. The
        gFetchBucket        =  0;
        gFetchFrom          =  gFrom;
        gFetchSenderPrim    =  gSenderPrim;
        gFetchMsgId        =  gMsgId;
        FetchAllNext();


//
// 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 = " ";
     }
     }
     else if( gOp == "db_entries" ) {
 
         StartProcessing( "db_entries" );
 
    //
    // 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 );
     }
     }
     else if( gOp == "db_memory" ) {
     return(TRUE);
        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.
//
//
// 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


default {
integer parseLinkCommand( string message ) {
       
    message = llStringTrim( message, STRING_TRIM );
      
      
    //
    // backwards compatibility
    gCmdSepChar = "|";
    integer ndx = llSubStringIndex( message, gCmdSepChar );
    if( ndx < 0 ) {
        gCmdSepChar = " ";
    }
      
      
    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
     // pull off target
     link_message(integer senderPrim, integer nbr, string message, key id) {
     ndx = llSubStringIndex( message, gCmdSepChar );
        gFrom      = (string) id;
    if( ndx < 0 ) {
        gMsgId      = nbr;
        return(FALSE);
        gSenderPrim = senderPrim;
     }
        if( parseLinkCommand( message ) ) {
            processCommand();
        }
     }  


    gTo = llGetSubString( message, 0 , ndx - 1 );


     timer() {
     //
 
    // a message for us?
    if( gTo == gMe || gTo == "all" ) {
         //
         //
         // ending up here usually means a processing failure.
         // process the rest
         // Either a dead hash bucket script or really bad script lag
         message = llGetSubString( message, ndx + 1, -1 );
        if( gSorting ) {
         return( parseCommand( message ) ) ;
            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 ) {
     return(FALSE);
        llResetScript();
    }
   
}
}




</lsl>


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


== 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
        //                Database Related Functions                //
        //                                                          //
        //==========================================================//




<lsl>
//
// processCommand.  Commands
// Commands are accepted from chat and link messages.
// The command line must be parsed into gOp & gOperand by the parseCommand
// function before transitioning to this function.
 
processCommand() {
     
    //
    // process command This is the msster switch
   
    //
    // first up, generic state controls
    if( gOp == "db_reset" ) {
        llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
        SendToAllBuckets( "db_reset" );
        llResetScript();
    }
    else if( gOp == "db_status" ) {
        status();
    }
    //
    // db commands
    else if( gOp == "db_response" ) {
        if( gSorting ) {
           
            if( gOperand == "sortdone" ) {
                llSetTimerEvent(0.0);
                SortNextBucket();
            }
            else {
                llMessageLinked(
                                    LINK_THIS,
                                    gSortingMsgId,
                                    gSortingFrom+"|"+gOp+"|"+gOperand,
                                    gMe
                );
                llSetTimerEvent(3.0);
            }
        }
        else if( gFetching ) {
            if( gOperand == "fetchalldone" ) {
                llSetTimerEvent(0.0);
                FetchAllNext();
            }
            else {
                llMessageLinked(
                                    LINK_THIS,
                                    gFetchMsgId,
                                    gFetchFrom+"|"+gOp+"|"+gOperand,
                                    gMe
                );
                llSetTimerEvent(3.0);
            }
        }
        else if( gProcessing != "" ) {
            AddResponse( gFrom, gOperand );
        }
    }
    else if( gOp == "db_add" ) {
       
        parseCommand( gOperand );
        gKey            = gOp;
        gValue          = gOperand;
        gTag            = HashTag( gKey );
        ProxyToBucket( "db_add|"+gKey+"|"+gValue );
 
    }
    else if( gOp == "db_delete" ) {
        gKey  = gOperand;
        gTag  = HashTag( gKey );
        ProxyToBucket( gOp+"|"+gKey );
 
    }
    else if( gOp == "db_list" ) {
        ProxyToAllBuckets( "db_list" );
    }
    else if( gOp == "db_keyexists" ) {
       
        gKey    = gOperand;
        gTag    = HashTag( gKey );
        ProxyToBucket( gOp+"|"+gKey );
 
    }
    else if( gOp == "db_valexists" ) {
        StartProcessing( "db_valexists|"+gOperand);
    }
    else if( gOp == "db_fetch" ) {
        gKey          = gOperand;
        gTag          = HashTag( gKey );
        ProxyToBucket( gOp + "|" + gOperand );
    }
    else if( gOp == "db_fetchall" ) {
 
        //
        // we will intermediary this
        gFetching          =  TRUE;
        gFetchBucket        =  0;
        gFetchFrom          =  gFrom;
        gFetchSenderPrim    =  gSenderPrim;
        gFetchMsgId        =  gMsgId;
        FetchAllNext();
 
    }
    else if( gOp == "db_entries" ) {
        StartProcessing( "db_entries" );
    }
    else if( gOp == "db_memory" ) {
        ProxyToAllBuckets( gOp );
    }
    else if( gOp == "db_clear" ) {
        ProxyToAllBuckets( gOp );
    }
    else if( gOp == "db_sort" ) {
       
        gOps                = llParseString2List( gOperand, ["|"], [] );
        gSorting            = TRUE;
        gSortBucket        = 0;
        gSortingFrom        = gFrom;
        gSortingSenderPrim  = gSenderPrim;
        gSortingMsgId      = gMsgId;
        gSortOn            = llList2String( gOps, 1);
        gSortDirection      = llList2String( gOps, 2);
        gSortBite          = llList2String( gOps, 3);
        SortNextBucket();
    } 
    return;
 
 
}
        //==========================================================//
        //                                                          //
        //                      States                            //
        //                                                          //
        //==========================================================//
 
//
// default. Since the particle system is set it and forget it, the
// default state's primary responsiblity is to handle communications.
// Command input can come from chat, notecards and from other linked
// prims.
//
 
default {
   
   
    state_entry() {   
         
        //
        // Startup processing is pretty minimal
        if( gSetup ) {       
            gSetup = FALSE;
            llSetRemoteScriptAccessPin( 0 );
            gMe = llGetScriptName(); 
            gBucketScriptBaseLen    = llStringLength( gBucketScriptBase ); 
        }
    }
 
    //
    // or the control can come from inside the linkset
    link_message(integer senderPrim, integer nbr, string message, key id) {
        gFrom      = (string) id;
        gMsgId      = nbr;
        gSenderPrim = senderPrim;
        if( parseLinkCommand( message ) ) {
            processCommand();
        }
    } 
 
 
    timer() {
 
        //
        // ending up here usually means a processing failure.
        // Either a dead hash bucket script or really bad script lag
        if( gSorting ) {
            llOwnerSay(gMe+" Sorting Timeout <<<<<< DB Tag ["+gTag+"]");
            SortNextBucket();
        }
        else if( gFetching ) {
            llOwnerSay(gMe+" Fetch Timeout <<<<<< DB Tag ["+gTag+"]");
            FetchAllNext();
        }
        else if( gProcessing != "" ) {
            llOwnerSay(
                              gMe
                          +  " Process Timout:"+gProcessing+" <<<<< "
                          +  "DB Tag [" + gTag +"]"
            );
            ResetProcessing();
        }
 
    }
   
    on_rez( integer param ) {
        llResetScript();
    }
   
}
 
 
</lsl>
 
 
 
 
== DBSort ==
 
DBSort is a sorting module that can return the sorted contents of a single DB core module if called directly by the client script. Or if called via DBMeta, can return the sorted contents of all the DB core modules controlled by the DBMeta script. Sorry, each DB module is sorted individually. The database isn't sorted as a whole through DBMeta
 
 
<lsl>
 
//
// RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno
//
// This file is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//
//
//
//
// DBSort  - DB module to sort the contents of a DB script and return
//            it to the caller in sorted order
//
// link message formats:
// to|db_sort|db_script|key_or_val|direction|bite
//
// to            - string matching the name of this script or 'all'
// db_sort        - literal command
// db_script      - name of the DB script containing the data to be
//                  sorted.
// key_or_val    - one of the literals 'key' or 'val' to indicate
//                  sorting on the data key or the data value
// direction      - one of the literals 'ascend' or 'descend' to
//                  indicate sort in ascending order or descending order
// bite          - how much of the key or value to compare. If
//                  bite is 0 or less, the whole value or key will
//                  be compared.
//                  Because sorting requires more memory than just
//                  storing data, it is possible to crash a DBSort
//                  script through memory exhaustion. Using a sensible
//                  bite value reduces the amount of memory needed by
//                  DBSort.
//
// History:
// 18 Feb 08    Begin                                  Ravenhurst Xeno
//              Adapted from DB
// 23 Mar 08    Begin release version                  Ravenhurst Xeno
//-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------
 
 
 
        //==========================================================//
        //                                                          //
        //                    Attribute Variables                  //
        //                                                          //
        //==========================================================//
 
 
string  gProductId          = "Ravenhust Xeno RBase Sorter";
integer gVersion            = 2008032301;
 
 
     
string  gCmdSepChar;
 
integer gSetup              =  TRUE;
 
string  gTo;                                // who the message is sent to
string  gFrom;
integer gSenderPrim;
integer gMsgId;
 
 
//
//  These are related to taking, parsing and acting on command text
//  Commands can come from chat, notecards and (soon) link messages
//
string  gOp;                            // command to act on
list    gOps;
string  gOperand;                      // data for gOp
 


//
//
// RXBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno
// Misc. attributes
//
string  gMe                =  "";
// 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
// Sort Specific Data
// along with this program; if not, write to the Free Software
list    gSorts;
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
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
// DBSort  - DB module to sort the contents of a DB script and return
// into gOp and the rest into gOperand. If the link message was valid
//            it to the caller in sorted order
// and for us, 1 is returned. Otherwise 0 is returned
//
// 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-------
 


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


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


    //
    // pull off target
    ndx = llSubStringIndex( message, gCmdSepChar );
    if( ndx < 0 ) {
        return(0);
    }


string  gProductId          = "Ravenhust Xeno RXBase Sorter";
    gTo = llGetSubString( message, 0 , ndx - 1 );
integer gVersion            = 2008032301;
 
    //
    // a message for us?
    if( gTo == gMe || gTo == "all" ) {
        //
        // process the rest
        message = llGetSubString( message, ndx + 1, -1 );
        return( parseCommand( message ) ) ;
    }
       
    return(0);
}


 
     
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
// display some summary status information
//  Commands can come from chat, notecards and (soon) link messages
//
string  gOp;                            // command to act on
list    gOps;
string  gOperand;                      // data for gOp


 
integer status() {
//
// Misc. attributes
       
string  gMe                =   "";
    string  msg = gProductId + " version "+(string) gVersion;  
 
    msg += " Status: ";
 
    llSay(0, "["+gMe+"] "+msg );
//
    llSay(0, "["+gMe+"] Free Memory : "+(string)llGetFreeMemory() );
// 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;




    return(1);
}


         //==========================================================//
         //==========================================================//
         //                                                          //
         //                                                          //
         //                Communications Funtions                  //
         //                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
// 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
     // give the db module time to finish responding
     gCmdSepChar = "|";
     llSleep(0.5);
    integer ndx = llSubStringIndex( message, gCmdSepChar );
      
    if( ndx < 0 ) {
        gCmdSepChar = " ";
     }
       
 
     //
     //
     // parse command
     // end of sort
     message = llStringTrim( message, STRING_TRIM );
     llMessageLinked(
    ndx = llSubStringIndex( message, gCmdSepChar );
                        gSortFromPrim,
    if( ndx < 0 ) {
                        gSortFromMsgId,
        gOp        = message;
                        gSortFrom+"|db_response|sortdone",
        gOperand    = "";
                        gMe
    }
     );
     else {
 
        gOp      = llGetSubString(message, 0 , ndx - 1 );
    gDB         = "";
         if( ndx + 1 >= llStringLength( message ) ) {
    gSorts      = [];
            gOperand = "";
     gSortFrom  = "";
        }
        else {
            gOperand  = llGetSubString(message, ndx + 1 , -1 );
        }
     }
    return(1);
}
}




//
//
// link commands are just normal commands prepended with the target's
// processCommand.  Commands
// name (and blank space). The target goes into gTo, the operator
// Commands are accepted from chat and link messages.  
// into gOp and the rest into gOperand. If the link message was valid
// The command line must be parsed into gOp & gOperand by the parseCommand
// and for us, 1 is returned. Otherwise 0 is returned
// function before transitioning to this function.  
 


integer parseLinkCommand( string message ) {
processCommand() {
       
     
     message = llStringTrim( message, STRING_TRIM );
     //
    // process command This is the msster switch
      
      
     //
     //
     // backwards compatibility
     // first up, generic state controls
     gCmdSepChar = "|";
     if( gOp == "db_reset" ) {
    integer ndx = llSubStringIndex( message, gCmdSepChar );
        llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
    if( ndx < 0 ) {
        llResetScript();
        gCmdSepChar = " ";
     }
     }
 
     else if( gOp == "db_status" ) {
 
         status();
    //
    // pull off target
    ndx = llSubStringIndex( message, gCmdSepChar );
     if( ndx < 0 ) {
         return(0);
     }
     }
    else if( gOp == "db_sort" ) {


    gTo = llGetSubString( message, 0 , ndx - 1 );
    //
    // a message for us?
    if( gTo == gMe || gTo == "all" ) {
         //
         //
         // process the rest
         // !!! might want to start by finding out how many entries
         message = llGetSubString( message, ndx + 1, -1 );
         // !!! there are first and make adjustments based on that?
         return( parseCommand( message ) ) ;
        gSortFrom        = gFrom;
    }
         gSortFromPrim    = gSenderPrim;
          
         gSortFromMsgId    = gMsgId;
    return(0);
}


        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;
// display some summary status information
        }
 
        else {
integer status() {
            gDirection = 1;
        }
        gBite = (integer) llList2String( gOps, 3 );
        if( gBite < 1 ) {
            gBite = -1;
        }
        gIndex        = 0;
        gOps          = [];
          
          
    string  msg =  gProductId + " version "+(string) gVersion;
        llMessageLinked(
    msg += " Status: ";
                            LINK_THIS,
    llSay(0, "["+gMe+"] "+msg );
                            101,
     llSay(0, "["+gMe+"] Free Memory : "+(string)llGetFreeMemory() );
                                gDB+"|"
                            +   "db_fetchall",
                            gMe
        );
        llSetTimerEvent( 5 );
     }
    else if( gOp == "db_response" ) {


 
        if( gOperand == "fetchalldone" ) {
    return(1);
           
}
            //
 
            // got all the data to sort
        //==========================================================//
            llSetTimerEvent(0);
         //                                                          //
            DoSort();
         //               Database Related Functions                //
         }
        //                                                         //
         else {
        //==========================================================//
           
          
            //
DoSort() {
            // data to be accumulated,
            // break apart
            gOps          = llParseString2List( gOperand, ["|"], [] );
   
            //
            // get the basic sorting data item
            gData         =  llList2String( gOps, gSortOn + 1);
      
      
    integer  ndx;
            //
    integer   i;
            // trim it down to size?
    integer   len = gIndex * 2;
            if( gBite > 0 && llStringLength( gData ) > ( gBite * 2 ) ) {
 
                gData   =      llGetSubString( gData, 0, gBite )
     //
                            +   llGetSubString( gData, -gBite, -1 );
    // 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 ) {
            // add it to the sort list
        ndx = (integer)llList2String( gSorts, i );
            gSorts   = (gSorts = []) + gSorts + [
        llMessageLinked(
                                                    gData,
                            gSortFromPrim,
                                                    llList2String( gOps, 0)  
                            gSortFromMsgId,
                                                ];
                            gDB+"|db_fetchndx|"+(string)ndx,
            gIndex++;
                            gSortFrom
            llSetTimerEvent( 5 );
        );
        }
       
    }
    return;


        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  = "";
}


}
        //==========================================================//
        //                                                          //
        //                      States                            //
        //                                                          //
        //==========================================================//


//
//
// processCommand. Commands
// default. Since the particle system is set it and forget it, the
// Commands are accepted from chat and link messages.
// default state's primary responsiblity is to handle communications.
// The command line must be parsed into gOp & gOperand by the parseCommand
// Command input can come from chat, notecards and from other linked
// function before transitioning to this function.
// prims.
//


 
default
processCommand() {
{
     
      
    //
     // process command This is the msster switch
      
      
     //
     state_entry() {   
    // first up, generic state controls
         
    if( gOp == "db_reset" ) {
        //
         llSay(0,llGetObjectName()+"::"+gMe+" Reseting");
        // 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();
         llResetScript();
     }
     }
    else if( gOp == "db_status" ) {
        status();
    }
    else if( gOp == "db_sort" ) {


        //
     timer() {
        // !!! might want to start by finding out how many entries
 
        // !!! there are first and make adjustments based on that?
         llSetTimerEvent(0);
        gSortFrom        = gFrom;
         DoSort();         
        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                            //
        //                                                          //
        //==========================================================//


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


    }
   
}




Line 1,915: Line 2,302:


//
//
// RXBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno
// RBase an Open Source LSL based database (c) 2008 by Ravenhurst Xeno
//
//
// This file is free software; you can redistribute it and/or modify
// This file is free software; you can redistribute it and/or modify
Line 1,953: Line 2,340:




string  gProductId          = "Ravenhust Xeno RXBase DB Module Tester";
string  gProductId          = "Ravenhust Xeno RBase DB Module Tester";
integer gVersion            = 2008032301;
integer gVersion            = 2008032301;
integer gCommandLd;
integer gCommandLd;
Line 2,957: Line 3,344:
      
      
}
}
</lsl>






</lsl>
</lsl>

Latest revision as of 08:40, 31 August 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>

// // 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. // // // // // // DBExample - This is an example DB client program // // This script shows examples of using the RXBase system. // It assumes a basic core module called 'DB' and sixteen // ganged modules named 'DB 0'....'DB F' controlled through // DBMeta. // // 28 Mar 08 Begin release version Ravenhurst Xeno //-0-----!-1-------!-2-------!-3-------!-4-------!-5-------!-6-------!-7-------


string gProductId = "Ravenhust Xeno RXBase Example"; integer gVersion = 2008032801;


integer gSetup = TRUE;

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

// // state parameters string gTarget; string gKey; string gValue;


// // 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 = "";


default {

   state_entry() {
       gMe     = llGetScriptName();
   }
   
   touch_start( integer nbrDetected ) {
       
       llSay(0,"Adding some values directly to the DB module\n");
       
       //
       // First we will interact directly with a single DB module
       // and add some values to it
       
       gTarget     = "DB";
       
       //
       // clear out any previous data
       llMessageLinked(
                           LINK_THIS,
                           10,
                           gTarget+"|db_clear",
                           gMe
       );
       
       //
       // add a few key/value pairs
       llMessageLinked(
                           LINK_THIS,
                           11,
                           gTarget+"|db_add|red|This is the RED key value",
                           gMe
       );
       
       llMessageLinked(
                           LINK_THIS,
                           12,
                               gTarget
                           +   "|db_add|asharp|This is the ASHARP key value",
                           gMe
       );
   
       llMessageLinked(
                           LINK_THIS,
                           13,
                               gTarget +
                           "|db_add|green|This is the GREEN key value",
                           gMe
       );
       
       llMessageLinked(
                           LINK_THIS,
                           14,
                               gTarget
                           +   "|db_add|blue|This is the BLUE key value",
                           gMe
       );
       //
       // list the data in the module. Note: the DB module will llSay
       // the values it contains.
       llSay(0,"List of DB's contents before delete");
       llMessageLinked(
                           LINK_THIS,
                           15,
                           gTarget + "|db_list",
                           gMe
       );
       llSleep(2.0);
       
       //
       // get rid of the odd man out
       llMessageLinked(
                           LINK_THIS,
                           16,
                           "DB|db_delete|asharp",
                           gMe
       ); 


       llSay(0,"\nList of DB's contents after delete");
       llMessageLinked(
                           LINK_THIS,
                           17,
                           gTarget + "|db_list",
                           gMe
       );
       llSleep(2.0);  
       
             
       //
       // now we will go through a similar process but using the
       // dbmeta controller 
       
       gTarget = "DBMeta";
       
       llSay(0,"\nStarting DBMeta controll. Adding Data via DBMeta");
       llMessageLinked(
                           LINK_THIS,
                           18,
                           gTarget + "|db_clear",
                           gMe
       );       
       llSleep(2.0);
           
       llMessageLinked(
                           LINK_THIS,
                           19,
                           gTarget + "|db_add|100|100 key value",
                           gMe
       );                         
       llMessageLinked(
                           LINK_THIS,
                           20,
                           gTarget + "|db_add|110|110 key value",
                           gMe
       );
       llMessageLinked(
                           LINK_THIS,
                           21,
                           gTarget + "|db_add|120|120 key value",
                           gMe
       );
       llMessageLinked(
                           LINK_THIS,
                           22,
                           gTarget + "|db_add|200|200 key value",
                           gMe
       );                                    
       llMessageLinked(
                           LINK_THIS,
                           23,
                           gTarget + "|db_add|300|300 key value",
                           gMe
       );
       //
       // list all the contents
       llSay(0,"\nList of data via DBMeta");
       llMessageLinked(
                           LINK_THIS,
                           24,
                           gTarget + "|db_list",
                           gMe
       );
       llSleep(5.0);
       
       //
       // list the contents of one of the buckets under DBMeta control
       llSay(0,"\nList of the DB 1 data bucket directly");
       gTarget = "DB 1";
       
       llMessageLinked(
                           LINK_THIS,
                           25,
                           gTarget + "|db_list",
                           gMe
       );
    
       llSleep( 5.0 );
       state fetch;
                                                              
   }
       

}


state fetch {

   state_entry() {
   
       llSay(0,"\nFetching Data Values");
 
       //
       // reteive from the single core db mudle
       gTarget = "DB";
       llMessageLinked(
                           LINK_THIS,
                           26,
                           gTarget+"|db_fetch|blue",
                           gMe
       );
       
       //
       // retrieve a value from the DBMeta controlled buckets
       gTarget = "DBMeta";
       llMessageLinked(
                           LINK_THIS,
                           27,
                           gTarget+"|db_fetch|200",
                           gMe
       );
    
       //
       // time for the fetches to return
       llSetTimerEvent( 5.0 );   
   }
   
   //
   // or the control can come from inside the linkset
   link_message(integer senderPrim, integer nbr, string message, key id) {
       gOps = llParseString2List( message, ["|"], [] );
       if( 
               llList2String( gOps, 0 ) == gMe
           &&  llList2String( gOps, 1 ) == "db_response"
       ) {
           llSay(  
                   0,
                       "Fetch Response:  "
                   +   "MsgId   ["+(string)nbr+"]  "
                   +   "Message ["+message+"]  "
                   +   "From    ["+(string)id+"]  "
           );
       }
   }  
   
   timer() {
       state sortSingle;
   }
   
   

}


state sortSingle {


   state_entry() {
                     
       //
       // retreive them in sorted order. Notice this is sent to the
       // DBSort module with the single DB module as the target
       // of the sort
       llSay(0,"\nSingle Bucket Sort");
       llMessageLinked(
                           LINK_THIS,
                           28,
                           "DBSort|db_sort|DB|key|ascend|0",
                           gMe
       );
       
   }
   
   
   
   //
   // or the control can come from inside the linkset
   link_message(integer senderPrim, integer nbr, string message, key id) {
       gOps = llParseString2List( message, ["|"], [] );
       if( 
               llList2String( gOps, 0 ) == gMe
           &&  llList2String( gOps, 1 ) == "db_response"
       ) {
           if( llList2String( gOps, 2 ) == "sortdone" ) {
               llSay(0, "Single Sort Done");
               llSleep(5.0);
               state sortMulti;
           }
           else {
               llSay(  
                       0,
                           "Single Sort Response:  "
                       +   "MsgId   ["+(string)nbr+"]  "
                       +   "Message ["+message+"]  "
                       +   "From    ["+(string)id+"]  "
               );
           }
       }
   } 
   

}


state sortMulti {


   state_entry() {              
       llSay(0,"\nSort all buckets via DBMeta");
       //
       // retreive them in sorted order. Notice this is sent to the
       // DBSort module with an invalid target as the sort target
       // DBMeta will take over and provide the sorted response
       llMessageLinked(
                           LINK_THIS,
                           29,
                           "DBMeta|db_sort|X|key|ascend|0",
                           gMe
       );


   }
   
   
   
   //        llSay(0,"Huh .... "+gTarget+"|dblist" );//!!!
   // or the control can come from inside the linkset
   link_message(integer senderPrim, integer nbr, string message, key id) {
       gOps = llParseString2List( message, ["|"], [] );
       if( 
               llList2String( gOps, 0 ) == gMe
           &&  llList2String( gOps, 1 ) == "db_response"
       ) {
           if( llList2String( gOps, 2 ) == "sortdone" ) {
               llSay(0, "Multi Sort Done");
           }
           else {
               llSay(  
                       0,
                           "Multi Sort Response:  "
                       +   "MsgId   ["+(string)nbr+"]  "
                       +   "Message ["+message+"]  "
                       +   "From    ["+(string)id+"]  "
               );
           }
       }
   } 
   

}

</lsl>

DB

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

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


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


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

string gCmdSepChar;

integer gSetup = TRUE;

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


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


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


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

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



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


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


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

}


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

integer parseLinkCommand( string message ) {

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


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

}


// // display some summary status information

integer status() {


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


   return(1);

}

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


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

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

}

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

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

}

sendResponse( string payload ) {

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

}

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

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

}


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

processCommand() {

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

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


}

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

default {


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

}

</lsl>


DB Meta

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

<lsl>

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


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


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


string gCmdSepChar;

integer gSetup = TRUE;

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


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


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


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

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

list gBucketTags = [

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

list gBucketResponses;


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

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

string HashTag( string str ) {

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

}

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

StartProcessing( string cmd ) {

   integer                 i;
   integer                 len = llGetListLength( gBucketTags );


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


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


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

}

AddResponse( string from, string response ) {

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

}


FinishProcessing() {

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

}

ResetProcessing() {

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

}


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

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

ProxyToAllBuckets( string payload ) {

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

}


ProxyToBucket( string payload ) {

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

}


SendToAllBuckets( string payload ) {

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

}


SendToBucket( string payload ) {

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

}

SortNextBucket() {

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

}

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

FetchAllNext() {

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

}


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

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

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


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

}


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

integer parseLinkCommand( string message ) {

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

}


// // display some summary status information

integer status() {

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

}

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


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

processCommand() {

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

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


}

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

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

default {


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


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

}


</lsl>



DBSort

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


<lsl>

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


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


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


string gCmdSepChar;

integer gSetup = TRUE;

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


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


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


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


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


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

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

}


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

integer parseLinkCommand( string message ) {

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


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

}


// // display some summary status information

integer status() {


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


   return(1);

}

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

DoSort() {

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

}


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


processCommand() {

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


}

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

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

default {


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

}

</lsl>


DBTest

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

<lsl>

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


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


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


string gCmdSepChar;

integer gSetup = TRUE;

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


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


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


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


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

string gSortCmd; integer gSortBite = 4;

integer gSaltBase; list gSaltChars = [

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

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

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

}


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


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


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

}


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

integer parseLinkCommand( string message ) {

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

}


// // display some summary status information

integer status() {


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


   return(1);

}

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

string SaltString( integer salt, integer len ) {

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

}


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

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

}

makeTest( integer nbr ) {

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

}

getRandomTest() {

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

}

getRandomDeletedTest() {

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

}

addTest( integer nbr ) {

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

}

integer isTest( integer nbr ) {

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

}

delTest( integer nbr ) {

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

}

dumpTests() {

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


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

}


integer dumpStringList( list l ) {

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

}


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

processCommand() {

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


}

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

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

default {


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

}


</lsl>