Page 1 of 1

Can this be made to execute faster?

Posted: Fri Mar 18, 2022 9:04 pm
by swhite
Hi

I would like some suggestions for making this code faster. I need to create a JSON array of records from SQLite tables. However, I cannot use PB JSON functions because they do not handle doubles correctly (for example 2.00 becomes 2 in the JSON which then gets parsed out as an integer) and I need to strip out any commas at the end of text fields. So I created the following code which works but I wonder if it could be made much faster.

Code: Select all

Procedure DoSelect(lcSQL.s)
Define lcComma1.s,lcComma2.s,lcJSON.s,ln.i,lcTxBuf.s,lnC.i,lnFnd.i,lnETX.i,lcVal.s,lnO.i,lndBHnd.i
lndBHnd = OpenDatabase(#PB_Any,#dCDataBaseName,"","",#PB_Database_SQLite)
If lndBHnd
   If DatabaseQuery(lndBHnd,lcSQL,#PB_Database_StaticCursor)
      lcTxBuf = ""
      lcComma1=""
      CreateJSON(0)
      lnO = SetJSONObject(JSONValue(0))
      While NextDatabaseRow(lndBHnd)
         lcJSON = lcComma1 +"{"
         lcComma2 = ""
         For ln = 0 To DatabaseColumns(lndBHnd) - 1
            Select DatabaseColumnType(lndBHnd,ln)
               Case #PB_Database_String, 0 ; SQLite returns 0 for any column not based on a schema and treats it like text.
                  SetJSONString(AddJSONMember(lnO,DatabaseColumnName(lndBHnd,ln)),RTrim(GetDatabaseString(lndBHnd,ln),","))
               Case #PB_Database_Long
                  lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
                  lcComma2 = ","
               Case #PB_Database_Double
                  lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
                  lcComma2 = ","
               Case #PB_Database_Quad
                  lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
                  lcComma2 = ","
               Case #PB_Database_Float
                  lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
                  lcComma2 = ","
               Default
                  lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+#DQUOTE$+#DQUOTE$
                  lcComma2 = ","
            EndSelect
         Next
         If Len(lcJSON) <= 2
            lcTxBuf + lcJSON + LTrim(ComposeJSON(0),"{")
         Else
            lcTxBuf + lcJSON + RTrim(","+LTrim(ComposeJSON(0),"{"),",")
         EndIf
         lcComma1 = ","
      Wend
      FreeJSON(0)
      FinishDatabaseQuery(lndBHnd)
      ProcedureReturn lcTxBuf
   EndIf
EndIf      

Re: Can this be made to execute faster?

Posted: Sat Mar 19, 2022 8:35 pm
by infratec
Of course it can be faster.

Simply don't use usual string commands. :wink:

I'll have a look...

But it would be much easier if you provided a working code :!:

Re: Can this be made to execute faster?

Posted: Sat Mar 19, 2022 9:21 pm
by STARGÅTE
  1. Do not use functions like "To DatabaseColumns(lndBHnd) - 1" in the To-Statement, because they will be called each time of the loop.
    Call the function before the loop header.
  2. Do not add strings incrementally to a final string like "lcTxBuf + lcJSON", because each time the buffer have to be resized.
    Store them in a linked list or array, track the lengths and create a finale total string at once, e.g. CopyMemoryString()

Re: Can this be made to execute faster?

Posted: Sat Mar 19, 2022 9:29 pm
by infratec
Download:

https://www.sqlite.org/2022/sqlite-dll- ... 380100.zip

or

https://www.sqlite.org/2022/sqlite-dll- ... 380100.zip

Then try:

Code: Select all

Procedure.s DoSelect(lndBHnd.i, lcSQL.s)
  
  Protected lcComma1.s,lcComma2.s,lcJSON.s,ln.i,lcTxBuf.s,lnC.i,lnFnd.i,lnETX.i,lcVal.s,lnO.i
  
  If lndBHnd
    If DatabaseQuery(lndBHnd,lcSQL,#PB_Database_StaticCursor)
      lcTxBuf = ""
      lcComma1=""
      CreateJSON(0)
      lnO = SetJSONObject(JSONValue(0))
      While NextDatabaseRow(lndBHnd)
        lcJSON = lcComma1 +"{"
        lcComma2 = ""
        For ln = 0 To DatabaseColumns(lndBHnd) - 1
          Select DatabaseColumnType(lndBHnd,ln)
            Case #PB_Database_String, 0 ; SQLite returns 0 for any column not based on a schema and treats it like text.
              SetJSONString(AddJSONMember(lnO,DatabaseColumnName(lndBHnd,ln)),RTrim(GetDatabaseString(lndBHnd,ln),","))
            Case #PB_Database_Long
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
              lcComma2 = ","
            Case #PB_Database_Double
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
              lcComma2 = ","
            Case #PB_Database_Quad
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
              lcComma2 = ","
            Case #PB_Database_Float
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
              lcComma2 = ","
            Default
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+#DQUOTE$+#DQUOTE$
              lcComma2 = ","
          EndSelect
        Next
        If Len(lcJSON) <= 2
          lcTxBuf + lcJSON + LTrim(ComposeJSON(0),"{")
        Else
          lcTxBuf + lcJSON + RTrim(","+LTrim(ComposeJSON(0),"{"),",")
        EndIf
        lcComma1 = ","
      Wend
      FreeJSON(0)
      FinishDatabaseQuery(lndBHnd)
      ProcedureReturn lcTxBuf
    EndIf
  EndIf
EndProcedure


Procedure.s DoJSONSelect(DB.i, SQL$)
  
  Protected.i Pos, Colons, i
  Protected JSONSQL$, Fields$, JSON$, Table$
  
  
  LCaseSQL$ = LCase(SQL$)
  If Left(LCaseSQL$, 7) = "select "
    Pos = FindString(LCaseSQL$, " from ", 8)
    If Pos
      Fields$ = Mid(SQL$, 8, Pos - 8)
      
      If Trim(Fields$) = "*"
        Fields$ = ""
        Table$ = Mid(LCaseSQL$, Pos + 6)
        Table$ = StringField(Table$, 1, " ")
        If DatabaseQuery(DB, "Select name FROM PRAGMA_TABLE_INFO('" + Table$ + "')")
          While NextDatabaseRow(DB)
            Fields$ + GetDatabaseString(DB, 0) + ","
          Wend
          Fields$ = RTrim(Fields$, ",")
          FinishDatabaseQuery(DB)
        EndIf
      EndIf
      
      EndSQL$ = Mid(SQL$, Pos)
      JSONSQL$ = "SELECT json_group_array(json_object("
      
      Colons = CountString(Fields$, ",") + 1
      For i = 1 To Colons
        Field$ = Trim(StringField(Fields$, i, ","))
        JSONSQL$ + "'" + Field$ + "'," + Field$ + ","
      Next i
      JSONSQL$ = RTrim(JSONSQL$, ",") + "))"
      
      JSONSQL$ + EndSQL$
      
      Debug JSONSQL$
      
      If DatabaseQuery(DB, JSONSQL$)
        If NextDatabaseRow(DB)
          JSON$ = GetDatabaseString(DB, 0)
        EndIf
        FinishDatabaseQuery(DB)
      Else
        JSON$ = DatabaseError()
      EndIf
      
    EndIf
  EndIf
  
  ProcedureReturn JSON$
  
EndProcedure


UseSQLiteDatabase("sqlite3.dll")

DB = OpenDatabase(#PB_Any, ":memory:", "", "")
If DB
  
  If DatabaseQuery(DB, "SELECT sqlite_version()")
    If NextDatabaseRow(DB)
      Debug GetDatabaseString(DB, 0)
    EndIf
    FinishDatabaseQuery(DB)
  EndIf
  
  DatabaseUpdate(DB, "CREATE TABLE test (f1 TEXT, f2 INTEGER, f3 FLOAT, f4 BOOLEAN, f5 TIMESTAMP)")
  DatabaseUpdate(DB, "INSERT INTO test VALUES ('test', 123, 4.567, true, time('now'))")
  DatabaseUpdate(DB, "INSERT INTO test VALUES ('tset', 321, 7.654, false, time('now'))")
  Debug DatabaseError()
  
  Debug DoSelect(DB, "SELECT * FROM test")
  
  Debug ""
  Debug "Inbuild"
  
  Debug ""
  Debug DoJSONSelect(DB, "SELECT f1, f2, f3 FROM test")
  Debug ""
  Debug DoJSONSelect(DB, "SELECT * FROM test WHERE 1=1")
  
  CloseDatabase(DB)
EndIf
sqlite_version() should show 3.38.1

Re: Can this be made to execute faster?

Posted: Sat Mar 19, 2022 11:41 pm
by infratec
Added to detect all column names if SELECT * FROM is used.

Re: Can this be made to execute faster?

Posted: Mon Mar 21, 2022 2:19 pm
by swhite
Hi

Thanks for the tip about the Databasecolumns being called each pass. I will indeed make it a variable. I will look into the CopyMemoryString.
STARGÅTE wrote: Sat Mar 19, 2022 9:21 pm
  1. Do not use functions like "To DatabaseColumns(lndBHnd) - 1" in the To-Statement, because they will be called each time of the loop.
    Call the function before the loop header.
  2. Do not add strings incrementally to a final string like "lcTxBuf + lcJSON", because each time the buffer have to be resized.
    Store them in a linked list or array, track the lengths and create a finale total string at once, e.g. CopyMemoryString()

Re: Can this be made to execute faster?

Posted: Mon Mar 21, 2022 2:27 pm
by swhite
Hi

Thanks I will give you code a try.

I tried using json_group_array(json_object()) functions before I posted my question but ran into the 127 parameter limit on tables with more than 60 columns. It is possible to overcome this with

select json_group_array(json_insert(json_insert(json_object('trnno',[trnno],'total',[total]),'$.date',[date],'$.qty',[qty]),'$.price',[price],'$.authby',[authby]))

Where you can put 126 parameters in json_object and another 126 in json_insert and so on until you get the full number of columns.

Simon

infratec wrote: Sat Mar 19, 2022 9:29 pm Download:

https://www.sqlite.org/2022/sqlite-dll- ... 380100.zip

or

https://www.sqlite.org/2022/sqlite-dll- ... 380100.zip

Then try:

Code: Select all

Procedure.s DoSelect(lndBHnd.i, lcSQL.s)
  
  Protected lcComma1.s,lcComma2.s,lcJSON.s,ln.i,lcTxBuf.s,lnC.i,lnFnd.i,lnETX.i,lcVal.s,lnO.i
  
  If lndBHnd
    If DatabaseQuery(lndBHnd,lcSQL,#PB_Database_StaticCursor)
      lcTxBuf = ""
      lcComma1=""
      CreateJSON(0)
      lnO = SetJSONObject(JSONValue(0))
      While NextDatabaseRow(lndBHnd)
        lcJSON = lcComma1 +"{"
        lcComma2 = ""
        For ln = 0 To DatabaseColumns(lndBHnd) - 1
          Select DatabaseColumnType(lndBHnd,ln)
            Case #PB_Database_String, 0 ; SQLite returns 0 for any column not based on a schema and treats it like text.
              SetJSONString(AddJSONMember(lnO,DatabaseColumnName(lndBHnd,ln)),RTrim(GetDatabaseString(lndBHnd,ln),","))
            Case #PB_Database_Long
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
              lcComma2 = ","
            Case #PB_Database_Double
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
              lcComma2 = ","
            Case #PB_Database_Quad
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
              lcComma2 = ","
            Case #PB_Database_Float
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln)
              lcComma2 = ","
            Default
              lcJSON + lcComma2 + #DQUOTE$ + DatabaseColumnName(lndBHnd,ln) + #DQUOTE$+":"+#DQUOTE$+#DQUOTE$
              lcComma2 = ","
          EndSelect
        Next
        If Len(lcJSON) <= 2
          lcTxBuf + lcJSON + LTrim(ComposeJSON(0),"{")
        Else
          lcTxBuf + lcJSON + RTrim(","+LTrim(ComposeJSON(0),"{"),",")
        EndIf
        lcComma1 = ","
      Wend
      FreeJSON(0)
      FinishDatabaseQuery(lndBHnd)
      ProcedureReturn lcTxBuf
    EndIf
  EndIf
EndProcedure


Procedure.s DoJSONSelect(DB.i, SQL$)
  
  Protected.i Pos, Colons, i
  Protected JSONSQL$, Fields$, JSON$, Table$
  
  
  LCaseSQL$ = LCase(SQL$)
  If Left(LCaseSQL$, 7) = "select "
    Pos = FindString(LCaseSQL$, " from ", 8)
    If Pos
      Fields$ = Mid(SQL$, 8, Pos - 8)
      
      If Trim(Fields$) = "*"
        Fields$ = ""
        Table$ = Mid(LCaseSQL$, Pos + 6)
        Table$ = StringField(Table$, 1, " ")
        If DatabaseQuery(DB, "Select name FROM PRAGMA_TABLE_INFO('" + Table$ + "')")
          While NextDatabaseRow(DB)
            Fields$ + GetDatabaseString(DB, 0) + ","
          Wend
          Fields$ = RTrim(Fields$, ",")
          FinishDatabaseQuery(DB)
        EndIf
      EndIf
      
      EndSQL$ = Mid(SQL$, Pos)
      JSONSQL$ = "SELECT json_group_array(json_object("
      
      Colons = CountString(Fields$, ",") + 1
      For i = 1 To Colons
        Field$ = Trim(StringField(Fields$, i, ","))
        JSONSQL$ + "'" + Field$ + "'," + Field$ + ","
      Next i
      JSONSQL$ = RTrim(JSONSQL$, ",") + "))"
      
      JSONSQL$ + EndSQL$
      
      Debug JSONSQL$
      
      If DatabaseQuery(DB, JSONSQL$)
        If NextDatabaseRow(DB)
          JSON$ = GetDatabaseString(DB, 0)
        EndIf
        FinishDatabaseQuery(DB)
      Else
        JSON$ = DatabaseError()
      EndIf
      
    EndIf
  EndIf
  
  ProcedureReturn JSON$
  
EndProcedure


UseSQLiteDatabase("sqlite3.dll")

DB = OpenDatabase(#PB_Any, ":memory:", "", "")
If DB
  
  If DatabaseQuery(DB, "SELECT sqlite_version()")
    If NextDatabaseRow(DB)
      Debug GetDatabaseString(DB, 0)
    EndIf
    FinishDatabaseQuery(DB)
  EndIf
  
  DatabaseUpdate(DB, "CREATE TABLE test (f1 TEXT, f2 INTEGER, f3 FLOAT, f4 BOOLEAN, f5 TIMESTAMP)")
  DatabaseUpdate(DB, "INSERT INTO test VALUES ('test', 123, 4.567, true, time('now'))")
  DatabaseUpdate(DB, "INSERT INTO test VALUES ('tset', 321, 7.654, false, time('now'))")
  Debug DatabaseError()
  
  Debug DoSelect(DB, "SELECT * FROM test")
  
  Debug ""
  Debug "Inbuild"
  
  Debug ""
  Debug DoJSONSelect(DB, "SELECT f1, f2, f3 FROM test")
  Debug ""
  Debug DoJSONSelect(DB, "SELECT * FROM test WHERE 1=1")
  
  CloseDatabase(DB)
EndIf
sqlite_version() should show 3.38.1

Re: Can this be made to execute faster?

Posted: Mon Mar 21, 2022 10:04 pm
by swhite
Using the tips given I now have a version that is twice as fast. I still need to add a little code to handle re-allocating memory if the data to be retrieved is larger than my buffer.

Code: Select all

Global gcJSON.s=Space(100000)
Procedure DoSelect2(tcSQL.s)
   Define lcSQL.s,lcComma1.s,lcComma2.s,ln.i,lcTxBuf.s,lnC.i,lnFnd.i,lnETX.i,lcVal.s,lnO.i,lndBHnd.i,lnCol.i,lnR.i,lnLen.i,*Pntr
   
   lndBHnd = OpenDatabase(#PB_Any,"mySQLiteDB","","",#PB_Database_SQLite)
   If lndBHnd
      If DatabaseQuery(lndBHnd,tcSQL,#PB_Database_StaticCursor)
         lnCol = DatabaseColumns(lndBHnd) - 1
         Dim laFld.s(lnCol)
         Dim laTyp.i(lnCol)
         For ln = 0 To lnCol
            laFld(ln)=DatabaseColumnName(lndBHnd,ln)
            laTyp(ln)=DatabaseColumnType(lndBHnd,ln)
         Next
         lcComma1=""
         *Pntr = @gcJSON
         CopyMemoryString("[",@*Pntr)
         While NextDatabaseRow(lndBHnd)
            CopyMemoryString(lcComma1 +"{",@*Pntr)
            lcComma2 = ""
            For ln = 0 To lnCol
               Select laTyp(ln)
                  Case #PB_Database_String, 0 ; SQLite returns 0 for any column not based on a schema and treats it like text.
                     CopyMemoryString(lcComma2 + #DQUOTE$ + laFld(ln) + #DQUOTE$+":"+#DQUOTE$+RTrim(GetDatabaseString(lndBHnd,ln),",")+#DQUOTE$,@*Pntr)
                  Case #PB_Database_Long
                     CopyMemoryString(lcComma2 + #DQUOTE$ + laFld(ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln),@*Pntr)
                     lcComma2 = ","
                  Case #PB_Database_Double
                     CopyMemoryString(lcComma2 + #DQUOTE$ + laFld(ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln),@*Pntr)
                     lcComma2 = ","
                  Case #PB_Database_Quad
                     CopyMemoryString(lcComma2 + #DQUOTE$ + laFld(ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln),@*Pntr)
                     lcComma2 = ","
                  Case #PB_Database_Float
                     CopyMemoryString(lcComma2 + #DQUOTE$ + laFld(ln) + #DQUOTE$+":"+GetDatabaseString(lndBHnd,ln),@*Pntr)
                     lcComma2 = ","
                  Default
                     CopyMemoryString(lcComma2 + #DQUOTE$ + laFld(ln) + #DQUOTE$+":"+#DQUOTE$+#DQUOTE$,@*Pntr)
                     lcComma2 = ","
               EndSelect
            Next
            lcComma1 = ","
            CopyMemoryString("}",@*Pntr)
         Wend
         CopyMemoryString("]",@*Pntr)
         FinishDatabaseQuery(lndBHnd)
      Else
         Debug DatabaseError()
      EndIf
      CloseDatabase(lndBHnd)
   EndIf
EndProcedure

Re: Can this be made to execute faster?

Posted: Wed Mar 23, 2022 9:37 pm
by swhite
While optimizing this code in Pb 5.45 64 bit ASCII mode I had some encrypted fields that needed to decrypted before they could be included in the the JSON. The code below using CopyMemoryString() was only returning a single character after the decryption. It was then that I discovered the each character in *Ascii was two bytes long where the second byte as a Null. So I had to use the PeekS() to extract the data.

Code: Select all

...
lcTxt1 = GetDatabaseString(tndBHnd,ln)
If lcTxt1 <> ""
   lcTxt2 = Space(128)
   lnLen = Base64Decoder(@lcTxt1,Len(lcTxt1),@lcTxt2,128)  
   If lnLen < 16
      lcTxt2 = LSet(lcTxt2,16)
      lnLen = 16
   EndIf
   AESDecoder(@lcTxt2,*Ascii,lnLen,?Key,256,?IV,#PB_Cipher_CBC) 
   lcTxt1 = PeekS(*Ascii,-1,#PB_Unicode)
EndIf
CopyMemoryString(lcComma2 + #DQUOTE$ + laFld(ln) + #DQUOTE$+":"+#DQUOTE$+RTrim(Trim(lcTxt1),",")+#DQUOTE$,@*Pntr)
The strange part is that lcTxt1 only uses one byte per character.

lcTxt1 = GetDatabaseString(tndBHnd,ln)

00000000151BEF4 2B 75 5A 54 6C 38 69 6E 48 39 52 49 52 76 71 61 +uZTl8inH9RIRvqa
000000000151BF04 6D 76 4C 59 67 39 2F 38 2F 33 4D 77 4F 33 4A 31 mvLYg9/8/3MwO3J1
000000000151BF14 4D 59 56 31 58 70 69 63 37 62 73 3D 00 0D F0 AD MYV1Xpic7bs=..ð­
000000000151BF24 0B 00 00 00 64 61 74 65 00 74 65 78 74 00 00 00 ....date.text...

whereas the following uses two bytes per character.

lcTxt1 = "+uZTl8inH9RIRvqamvLYg9/8/3MwO3J1MYV1Xpic7bs="

0000000001AB9FD0 2B 00 75 00 5A 00 54 00 6C 00 38 00 69 00 6E 00 +.u.Z.T.l.8.i.n.
0000000001AB9FE0 48 00 39 00 52 00 49 00 52 00 76 00 71 00 61 00 H.9.R.I.R.v.q.a.
0000000001AB9FF0 6D 00 76 00 4C 00 59 00 67 00 39 00 2F 00 38 00 m.v.L.Y.g.9./.8.
0000000001ABA000 2F 00 33 00 4D 00 77 00 4F 00 33 00 4A 00 31 00 /.3.M.w.O.3.J.1.
0000000001ABA010 4D 00 59 00 56 00 31 00 58 00 70 00 69 00 63 00 M.Y.V.1.X.p.i.c.
0000000001ABA020 37 00 62 00 73 00 3D 00 00 00

After Decrypting *Ascii contained:

AESDecoder()

0000000000EDFF8C 32 00 32 00 31 00 39 00 33 00 3D 00 37 00 38 00 2.2.1.9.3.=.7.8.
0000000000EDFF9C 32 00 35 00 31 00 37 00 20 00 20 00 20 00 20 00 2.5.1.7. . . . .

lcTxt1 = PeekS(*Ascii,-1,#PB_Unicode) uses I byte per character.

0000000000EDEEE4 32 32 31 39 33 3D 37 38 32 35 31 37 20 20 20 20 22193=782517
0000000000EDEEF4 00

However if I do not use a database and just try decrypting the string:

lcTxt1 = PeekS(*Ascii,-1,#PB_Unicode) uses 2 byte per character.

00000000019C4690 32 00 32 00 31 00 39 00 33 00 3D 00 37 00 38 00 2.2.1.9.3.=.7.8.
00000000019C46A0 32 00 35 00 31 00 37 00 20 00 20 00 20 00 20 00 2.5.1.7. . . . .

The fact that strings sometimes use 2 bytes per character in some situations and only 1 byte per character in other situation was confusing. I do not know if this a bug or by design but I do not think this behaviour is documented and it took me a long time to track down.