View previous topic :: View next topic |
Author |
Message |
seed
Joined: 01 Dec 2006 Posts: 44
|
Posted: Tue Feb 20, 2007 13:28 Post subject: SQLEncodeSpecialCharacter Truncates at 127? |
|
|
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 |
|
|
Grumalg
Joined: 04 Nov 2005 Posts: 70
|
Posted: Tue Feb 20, 2007 19:21 Post subject: |
|
|
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 |
|
|
seed
Joined: 01 Dec 2006 Posts: 44
|
Posted: Tue Feb 20, 2007 22:06 Post subject: |
|
|
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 |
|
|
FunkySwerve
Joined: 02 Jun 2005 Posts: 377
|
Posted: Tue Feb 20, 2007 22:18 Post subject: |
|
|
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 |
|
|
Gryphyn
Joined: 20 Jan 2005 Posts: 431
|
Posted: Tue Feb 20, 2007 23:08 Post subject: |
|
|
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 |
|
|
seed
Joined: 01 Dec 2006 Posts: 44
|
Posted: Tue Feb 20, 2007 23:30 Post subject: |
|
|
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 |
|
|
Gryphyn
Joined: 20 Jan 2005 Posts: 431
|
Posted: Wed Feb 21, 2007 2:19 Post subject: |
|
|
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 - costs a bit more in malloc() )
Cheers
Gryphyn |
|
Back to top |
|
|
Grumalg
Joined: 04 Nov 2005 Posts: 70
|
Posted: Wed Feb 21, 2007 2:27 Post subject: |
|
|
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 |
|
|
dirt
Joined: 13 Dec 2005 Posts: 8
|
Posted: Wed Feb 21, 2007 7:18 Post subject: |
|
|
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 |
|
|
seed
Joined: 01 Dec 2006 Posts: 44
|
Posted: Wed Feb 21, 2007 18:51 Post subject: |
|
|
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* thanks for the replies |
|
Back to top |
|
|
Grumalg
Joined: 04 Nov 2005 Posts: 70
|
Posted: Wed Feb 21, 2007 23:16 Post subject: |
|
|
@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
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 |
|
|
FunkySwerve
Joined: 02 Jun 2005 Posts: 377
|
Posted: Thu Feb 22, 2007 0:57 Post subject: |
|
|
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 |
|
|
dirt
Joined: 13 Dec 2005 Posts: 8
|
Posted: Thu Feb 22, 2007 3:26 Post subject: |
|
|
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* 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 |
|
|
Via Con Diablos
Joined: 23 Nov 2005 Posts: 55
|
Posted: Thu Feb 22, 2007 4:44 Post subject: |
|
|
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 |
|
|
|
|
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
|