logo logo

 Back to main page

The NWNX Community Forum

 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 
 
SQLEncodeSpecialCharacter Truncates at 127?

 
Post new topic   Reply to topic    nwnx.org Forum Index -> Development
View previous topic :: View next topic  
Author Message
seed



Joined: 01 Dec 2006
Posts: 44

PostPosted: Tue Feb 20, 2007 13:28    Post subject: SQLEncodeSpecialCharacter Truncates at 127? Reply with quote

When I run a string through the encode special characters function it gets truncated to 127 characters. Is this supposed to be?
Back to top
View user's profile Send private message
Grumalg



Joined: 04 Nov 2005
Posts: 70

PostPosted: Tue Feb 20, 2007 19:21    Post subject: Reply with quote

At the top of the nwnx_sql include file you'll see a constant string smallColSpacer declared. The number of periods in this declaration sets the memory size reserved for passing data. The stock size of this string is where your truncation is comming from.

--- Grumalg ---
Back to top
View user's profile Send private message
seed



Joined: 01 Dec 2006
Posts: 44

PostPosted: Tue Feb 20, 2007 22:06    Post subject: Reply with quote

so would this fix the issue? or at least allow me to encode 512 byte strings? (that should be enough length)

Code:
string SQLEncodeSpecialChars(string sString)
{
   object oModule = GetModule();
   int i;
   string sSpacer;
   for (i=0;i<4;i++)  sSpacer+=smallColSpacer;  // reserve 4*128 bytes
   SetLocalString(oModule, "NWNX!SQL!SET_ESCAPE_STRING", sString);
   SetLocalString(oModule, "NWNX!SQL!GET_ESCAPE_STRING", sSpacer);
   return GetLocalString(oModule, "NWNX!SQL!GET_ESCAPE_STRING");
}
Back to top
View user's profile Send private message
FunkySwerve



Joined: 02 Jun 2005
Posts: 377

PostPosted: Tue Feb 20, 2007 22:18    Post subject: Reply with quote

I've always wondered, why not set it larger still? Iirc, in nwnx2 its 8x128, would there be any adverse effects to increasing that more? I ask because I'm intending to hook the abilty to return SELECT statements into SIMTools, and am unsure whether the reserved space will be suffiecient, or even if it is an obstacle to that. It certainly seems like I've retrieved much more data from that on selects before.
Funky
Back to top
View user's profile Send private message
Gryphyn



Joined: 20 Jan 2005
Posts: 431

PostPosted: Tue Feb 20, 2007 23:08    Post subject: Reply with quote

FunkySwerve wrote:
I've always wondered, why not set it larger still? Iirc, in nwnx2 its 8x128, would there be any adverse effects to increasing that more? I ask because I'm intending to hook the abilty to return SELECT statements into SIMTools, and am unsure whether the reserved space will be suffiecient, or even if it is an obstacle to that. It certainly seems like I've retrieved much more data from that on selects before.
Funky

Performance...it takes a while to check each byte for the null terminator, to determine the buffer-size/length. The larger this buffer, the longer it takes.
It becomes waisted processing when you're only dealing with smaller actual data.

Cheers
Gryphyn
Back to top
View user's profile Send private message
seed



Joined: 01 Dec 2006
Posts: 44

PostPosted: Tue Feb 20, 2007 23:30    Post subject: Reply with quote

so then the most efficent way to modify this code would be to reserve just enough 128 byte blocks to fit the data:

Code:
string SQLEncodeSpecialChars(string sString)
{
   object oModule = GetModule();
   int i;
   int nLoops = GetStringLength(sString) / 128 + 1;
   string sSpacer;
   for (i=0;i<nLoops;i++)  sSpacer+=smallColSpacer;  // reserve Loops*128 bytes
   SetLocalString(oModule, "NWNX!SQL!SET_ESCAPE_STRING", sString);
   SetLocalString(oModule, "NWNX!SQL!GET_ESCAPE_STRING", sSpacer);
   return GetLocalString(oModule, "NWNX!SQL!GET_ESCAPE_STRING");
}
Back to top
View user's profile Send private message
Gryphyn



Joined: 20 Jan 2005
Posts: 431

PostPosted: Wed Feb 21, 2007 2:19    Post subject: Reply with quote

seed wrote:
so then the most efficent way to modify this code would be to reserve just enough 128 byte blocks to fit the data:

Code:
string SQLEncodeSpecialChars(string sString)
{
   object oModule = GetModule();
   int i;
   int nLoops = GetStringLength(sString) / 128 + 1;
   string sSpacer;
   for (i=0;i<nLoops;i++)  sSpacer+=smallColSpacer;  // reserve Loops*128 bytes
   SetLocalString(oModule, "NWNX!SQL!SET_ESCAPE_STRING", sString);
   SetLocalString(oModule, "NWNX!SQL!GET_ESCAPE_STRING", sSpacer);
   return GetLocalString(oModule, "NWNX!SQL!GET_ESCAPE_STRING");
}


Yes, Although you don't know how many characters are going to be escaped... (I use a base of 32 dots/bytes - easier to count Wink - costs a bit more in malloc() )

Cheers
Gryphyn
Back to top
View user's profile Send private message
Grumalg



Joined: 04 Nov 2005
Posts: 70

PostPosted: Wed Feb 21, 2007 2:27    Post subject: Reply with quote

Simplest way is to just enlarge the smallColSpacer string constant by adding period chars to raise it's total length. While you could build a longer string by looping concatenating it's size that will be slower than just useing the larger constant directly.

Looping doing SetLocalString doesn't help, because they don't add. The size is set by whatever the last SetLocalString() put in it. If you do want to loop concatenating the constant size, concatenate it in a string variable and then use that to SetLocalString with *once*.

Stock it's 128 chars useing period as a filler char. If you copied and pasted the period contents between the quotes, you'd double it to 256 char for example. You see truncation after 127 because of the terminating null which takes up the 128 char spot.

--- Grumalg ---
Back to top
View user's profile Send private message
dirt



Joined: 13 Dec 2005
Posts: 8

PostPosted: Wed Feb 21, 2007 7:18    Post subject: Reply with quote

Grumalg wrote:
Simplest way is to just enlarge the smallColSpacer string constant by adding period chars to raise it's total length. While you could build a longer string by looping concatenating it's size that will be slower than just useing the larger constant directly.

Looping doing SetLocalString doesn't help, because they don't add. The size is set by whatever the last SetLocalString() put in it. If you do want to loop concatenating the constant size, concatenate it in a string variable and then use that to SetLocalString with *once*.

Stock it's 128 chars useing period as a filler char. If you copied and pasted the period contents between the quotes, you'd double it to 256 char for example. You see truncation after 127 because of the terminating null which takes up the 128 char spot.

--- Grumalg ---


I made a post in the DB forum about this issue I think. I'm having the exact same problem reading in long text strings from a DB.

I tried a few of the methods above to correct it, but I'm still having the same issue. But, I understand what your saying I think. (And please correct me if I'm wrong)

The dot string is 127 chars + a null character(128). So, for example I have some various text strings, some less than 128 & some more. On the ones less, the last character is always cut off(null). On the ones that are more, it cuts off at 127 also(null again).

So to correct this, I need to redo the dot string like so: (127 dots + 1 dot) + (127 dots)? <- The extra dot on the first set would replace the null character and thus effectively give me 255 chars consecutively?
Back to top
View user's profile Send private message
seed



Joined: 01 Dec 2006
Posts: 44

PostPosted: Wed Feb 21, 2007 18:51    Post subject: Reply with quote

dirt - use the SQLGetDataText for long columns:

// Return value of column iCol in the current row of result set sResultSetName
// Maximum column size: 128 characters
// Faster than SQLGetDataText()
string SQLGetData(int iCol);

// Return value of column iCol in the current row of result set sResultSetName
// Maximum column size: 1024 characters
// Somewhat slower than SQLGetData()
string SQLGetDataText(int iCol);

Grumalg - the code posted was looping and concatting a string *once* Smile thanks for the replies
Back to top
View user's profile Send private message
Grumalg



Joined: 04 Nov 2005
Posts: 70

PostPosted: Wed Feb 21, 2007 23:16    Post subject: Reply with quote

@dirt

The 'dot' string set in the loval var reserves a block of memory for nwnx to put returning data. Data returned gets truncated if it's bigger than the space reserved to prevent overwriting things that could be in memory past the reserved size. The max size of returned data is always one less than the 'dot' string length because of the null termination on the returned data.

@seed

My bad, I had only glanced at the code and responded under the impression it was looping around the SetLocalString. I had also forgotten about the SQLGetDataText function, as I haven't needed anything that large to date. Gimme 50 lashes with a wet noodle Smile


In the demo module sticky thread you'll find some discussion about what the stock 'dot' string length should be. It looks like it's going to change from 128 to 256 as that will support everything one could use except large varchar, text, and blob fields. Thus it would avoid some of the truncation issue for new users.

--- Grumalg ---
Back to top
View user's profile Send private message
FunkySwerve



Joined: 02 Jun 2005
Posts: 377

PostPosted: Thu Feb 22, 2007 0:57    Post subject: Reply with quote

I'm still not understanding how this memory reserve works - I'm fairly certain that I am exceeding it by far. Take the following for example:

Code:

//FunkySwerve's Persistent Banking System
#include "aps_include"
//function below lifted from Lanthar's system
int GetIsItemStackable(object oItem)//is it stackable? dont want to store these
{
    int iType = GetBaseItemType(oItem);
    if (iType == BASE_ITEM_GEM || iType == BASE_ITEM_POTIONS ||
        iType == BASE_ITEM_HEALERSKIT || iType == BASE_ITEM_THIEVESTOOLS ||
        iType == BASE_ITEM_SCROLL || iType == BASE_ITEM_ARROW ||
        iType == BASE_ITEM_BOLT || iType == BASE_ITEM_BULLET ||
        iType == BASE_ITEM_DART || iType == BASE_ITEM_THROWINGAXE ||
        iType == BASE_ITEM_SHURIKEN || iType == BASE_ITEM_GOLD)
        return TRUE;
    else
        return FALSE;
}
//function below constructed from Lanthar's system
int GetIsItemCharged(object oItem)  //is it charged? dont want to store these - no db column for charges
{
    int iType = GetBaseItemType(oItem);
    if(iType == BASE_ITEM_ENCHANTED_WAND || iType == BASE_ITEM_MAGICROD ||
       iType == BASE_ITEM_MAGICSTAFF || iType == BASE_ITEM_MAGICWAND)
       return TRUE;
    else
        return FALSE;
}
void main()
{
    // Declare variables...
    object oPC = GetLastClosedBy();
    if (!GetIsObjectValid(oPC)) return;
    object oItemChecker = GetNearestObjectByTag("BadItemCheck");//invisible object used to verify that the item is creatable from a resref (items created inside it)
    object oCheck;
    string sKey = GetLocalString(oPC, "KEY");
    string sResref, sResCheck, sBadObj;
    string sName = GetName(oPC);
    string sPlayerName = GetPCPlayerName(oPC);
    string sLog;
    int iCount = 0;
    // Lock self...
    ActionLockObject(OBJECT_SELF);
    // Start loop through inventory...
    object oItem = GetFirstItemInInventory(OBJECT_SELF);
    while (GetIsObjectValid(oItem))
        {
        if (GetIsItemStackable(oItem))//dont store stackables
            {
            FloatingTextStringOnCreature("<cþ<<>**WARNING** You can't store stackable items in this chest. No items were saved. Please remove the item " + GetName(oItem) + " from the chest.", oPC);
            sLog = "Bank User Error: player: " + sPlayerName + ", character: " + sName + ", error: stackable.";
            WriteTimestampedLogEntry(sLog);
            ActionUnlockObject(OBJECT_SELF);
            return;
            }
        else if (GetIsItemCharged(oItem))//'' charged items
            {
            sLog = "Bank User Error: player: " + sPlayerName + ", character: " + sName + ", error: charged item.";
            WriteTimestampedLogEntry(sLog);
            FloatingTextStringOnCreature("<cþ<<>**WARNING** You can't store charged items in this chest. No items were saved. Please remove the item " + GetName(oItem) + " from the chest.", oPC);
            ActionUnlockObject(OBJECT_SELF);
            return;
            }
        else if (GetHasInventory(oItem))//dont store containers
            {
            sLog = "Bank User Error: player: " + sPlayerName + ", character: " + sName + ", error: container.";
            WriteTimestampedLogEntry(sLog);
            FloatingTextStringOnCreature("<cþ<<>**WARNING** You can't store containers in this chest. No items were saved. Please remove the item " + GetName(oItem) + " from the chest.", oPC);
            ActionUnlockObject(OBJECT_SELF);
            return;
            }
        // Check if the item exists in this modules pallete...
        sResCheck = GetResRef(oItem);
        oCheck = CreateItemOnObject(sResCheck, oItemChecker); //create copy then destroy
        if(GetIsObjectValid(oCheck))
            {
            DestroyObject(oCheck);
            }
        else
            {
            sBadObj = GetName(oItem);
            sLog = "Bank User Error: player: " + sPlayerName + ", character: " + sName + ", error: invalid object.";
            WriteTimestampedLogEntry(sLog);
            FloatingTextStringOnCreature("<cþ<<>**WARNING** You can't store " + sBadObj + " in this chest. No items were saved. Please remove the container or containers, and inform a DM.", oPC);
            ActionUnlockObject(OBJECT_SELF);
            return;
            }
        iCount++; //otherwise count it
        oItem = GetNextItemInInventory(OBJECT_SELF);
        }
    // Changed limit to 50
    if (iCount > 50) //if too many, dont store
        {
        sLog = "Bank User Error: player: " + sPlayerName + ", character: " + sName + ", error: over 50 items.";
        WriteTimestampedLogEntry(sLog);
        FloatingTextStringOnCreature("<cþ<<>**WARNING** You can't store more than 50 items. You have "+IntToString(iCount)+" items in the chest. No items were saved. Please remove some items.", oPC);
        ActionUnlockObject(OBJECT_SELF);
        return;
        }
    // Nothing in the chest if iCount is still 0
    if (iCount == 0)
        {
        FloatingTextStringOnCreature("Chest empty, No items saved", oPC);
        ActionUnlockObject(OBJECT_SELF);
        return;
        }
    int iCount2 = 0;
    string sCount;
    string sPrefix = "INSERT INTO objdata (player,tag,name,val,expire) VALUES ";
    string sValues = "";
    string sAdd;
    string sPlayer = SQLEncodeSpecialChars(GetLocalString(oPC, "ID"));
    string sTag = SQLEncodeSpecialChars(GetLocalString(oPC, "NAME"));
    // Start loop through inventory (Again)...
    oItem = GetFirstItemInInventory(OBJECT_SELF);
    while (GetIsObjectValid(oItem))
        {
        iCount2++;
        sCount = IntToString(iCount2);
        sResref = GetResRef(oItem);
        sAdd = "('" + sPlayer + "','" + sTag + "','STORAGE" + sKey + sCount + "','" + sResref + "',0)";
        if (iCount > iCount2) sAdd += ",";
        sValues += sAdd;
        DestroyObject(oItem);
        oItem = GetNextItemInInventory(OBJECT_SELF);
        }
    string sSQL = sPrefix + sValues + ";";
    WriteTimestampedLogEntry(sSQL);
    SQLExecDirect(sSQL);
    FloatingTextStringOnCreature("<c þ >Items successfully saved", oPC);
    DeleteLocalObject(OBJECT_SELF, "LastUsedBy"); //track whether chest is stuck
    ActionUnlockObject(OBJECT_SELF);
    ExportSingleCharacter(oPC);
}


At a max of 50 items stored, with playername (say 16 characters, could be waaay more), plus another 8 or 10 for object tag used, plus 16 for storagekeycount, and 16 for resref, plus quotes and commas and parens etc, multiply all that by 50, seems to be waaaay over 128x8. What am I not understanding? I should add that I haven't had any issues with this script...
Thanks,
Funky
Back to top
View user's profile Send private message
dirt



Joined: 13 Dec 2005
Posts: 8

PostPosted: Thu Feb 22, 2007 3:26    Post subject: Reply with quote

seed wrote:
dirt - use the SQLGetDataText for long columns:

// Return value of column iCol in the current row of result set sResultSetName
// Maximum column size: 128 characters
// Faster than SQLGetDataText()
string SQLGetData(int iCol);

// Return value of column iCol in the current row of result set sResultSetName
// Maximum column size: 1024 characters
// Somewhat slower than SQLGetData()
string SQLGetDataText(int iCol);

Grumalg - the code posted was looping and concatting a string *once* Smile thanks for the replies


Hey seed, just wanted to tell you thanks, that worked like a charm. You guys are the best here at NWNX.
Back to top
View user's profile Send private message
Via Con Diablos



Joined: 23 Nov 2005
Posts: 55

PostPosted: Thu Feb 22, 2007 4:44    Post subject: Reply with quote

Funky:
The issue appears to be with the SQLEncodeSpecialCharacters, at least from what I'm reading in the thread.
All you are ever passing to it is the player's name, and ID, which I both imagine are smaller than that size.

The SQLExecDirect probably doesn't have the same character limitation.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    nwnx.org Forum Index -> Development All times are GMT + 2 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group