Well, the documentation leaves a lot of room for improvement wink and you can sure write this into a much shorter version but I wanted to make sure folks get the (somewhat abstract) idea.

When your line is $list'ed you can be sure further processing is not affected by delimiters. I also have to say that my solution cannot compete with  John (Murray)'s crisp & elegant one-liner. smiley

 

You could use CSV() like this... (sorry for the weird formatting)

isdDemoCSVToList
quit


Test() public
{
set tDescriptor = "ROW(STR VARCHAR,STR2 VARCHAR,STR3 VARCHAR,STR4 VARCHAR, STR5 VARCHAR)"
set tLine = "ABC Company,""123 Main St, Ste 102"",""Anytown, DC"",10001,234-567-8901"
do CSVToList(tLine,tDescriptor,.tOutput)
write !
zw tOutput
kill tOutput
for i=1:1:3
{
set tLineArray($i(tLineArray)) = """eins"",""deux"",line"_i
}
do CSVToList(.tLineArray,tDescriptor,.tOutput)
write !
zw tOutput
}

CSVToList(&pLines,pDescriptor,&pListArray) public
{
#dim tRS as %SQL.StatementResult
kill tListArray
if ($d(pLines)<10) set pLines(1)=pLines,pLines=1 }
set tTempStream = ##class(%Stream.TmpCharacter).%New()
for tIdx = 1 : 1 : pLines
{
do tTempStream.WriteLine(pLines(tIdx))
}
do tTempStream.Rewind()
set tRS = ##class(%SQL.Statement).%ExecDirect(,"call %SQL_Util.CSV(,?,?)",.pDescriptor,tTempStream).%NextResult()
while (tRS.%Next(.tSC))
{
set tList = ""
for tCol = 1 : 1 : tRS.%GetMetadata().columnCount 
{
set tValue = tRS.%GetData(tCol)
set tList = tList _ $lb(tValue)
}
set pListArray($i(pListArray))=tList
}
quit
}

 

Your output then looks like this

tOutput=1
tOutput(1)=$lb("ABC Company","123 Main St, Ste 102","Anytown, DC","10001","234-567-8901")

tOutput=3
tOutput(1)=$lb("eins","deux","line1","","")
tOutput(2)=$lb("eins","deux","line2","","")
tOutput(3)=$lb("eins","deux","line3","","")

What is the reaction you expect here?
- truncate?
- throw an exception?

Do you want to precalculate length at runtime
- for all of the results
- per row
- per column
- only valid for the caller(i.e. each call has different length limitiations depending on context) ?

you sure could do some nasty things at runtime

You could pass an extra parameter that has a list of length elements for each column like
Query ClientList(pLengthList as %List = { $lb(10,10,50) })

SELECT SUBSTRING(f1,1,$listget(pLengthList,1,50)) as f1,
...

A simple sample could look like this

/// dynamic length restriction
Class User.DynaPar
{

ClassMethod RunTest(pMax As %Integer = 5)
{
#dim tSQL as %String
#dim tIdx as %Integer
#dim tRS as %SQL.StatementResult
set tSQL = "SELECT * FROM myQuery()"
for tIdx = 1 : 1 : 10
{
set tRS = ##class(%SQL.Statement).%ExecDirect(,tSQL)
if tRS.%Next()
{
write !,$j(tIdx,3),": "_tRS.%GetData(1)
}
}
}

Query myQuery(pListOfLengths As %List = {..GetLengths()}) As %SQLQuery [ SqlName = MyQuery, SqlProc ]
{
SELECT TOP 1 SUBSTRING('abcdef',1,$LISTGET(:pListOfLengths,1,1))
}

ClassMethod GetLengths() As %List
{
return $lb(1+$random(6))
}

}

Does that do what you want?

You might want to restructure your multiple conditions per $case in a somewhat unusual fashion like this


Class User.FunWithCASE
{
ClassMethod CaseDemo(pNum as %Integer, pBoole as %Boolean = 0, pStr as %String) as %String
{
    return $case(1,  /* whatever is true first */
                 (pNum>100)   :"num gt than 100",
                 ((pBoole=1) && (pStr["Green Bay Packers")) : "True Superbowl Champion found!!!!",
                 ((pNum<=2) || ($length(pStr)>40))          : "num lt 2 or a looong string",
                                                            : ..NestedCase(pNum,pBoole,pStr))    
}

ClassMethod NestedCase(pNum as %Integer, pBoole as %Boolean = 0, pStr as %String) as %String
{
        return $case(1,  /* whatever is true first */
                 (pBoole=0)               : "not true ",
                 (pStr["Miami Dolphins")  : "Whatever",
                 ($e(pStr,*-4,*)="kees")  : "Mon or Yan found",
                                          : "Nothing rhymed in here")_" (nested)"
}

ClassMethod RunTest()
{
    for tNum=1,2,10,101
    {
        for tBoole = 0,1
        {
            set tList= $lb( "Green Bay Packers",
                            "MiamiDolphins",
                            "New York Yankees",
                            "Maxwell's Silver Hammer",
                             $tr($j(" ",41)," ","*")
                           )
            set tPos=0,tStr=""
            while ($listnext(tList,tPos,tStr))                 
            {
                write !,"CaseDemo("_tNum_","_$case(tBoole,1:"true",:"false")_","_$$$quote(tStr)_")"
                       ,?40,..CaseDemo(tNum,tBoole,tStr)
            } 
        }
    }
}
}

I would probably go for some extra hashing safety and add a CRC or something similar to the hash. There may be more subtle hashing methods or combinations thereof that will make duplicates "almost impossible" ;)

Class Test.Duplicates Extends %Persistent
{

Property myText As %String (MAXLEN = 32000);

Property myHash as %String (MAXLEN=50)
[SqlComputed,
 Calculated,
 SqlComputeCode={ set {myHash} = $SYSTEM.Encryption.MD5Hash({myText})_"|"_$ZCRC({myText},7)},
 SqlComputeOnChange = (%%INSERT,%%UPDATE,myText)
 ];

/// a bitmap would not be worth the effort
Index ixMyHash on myHash;

ClassMethod RunTest(pMax as %Integer = 1000, pDelete as %Boolean = 0)
{
    write !,"begin "_..%ClassName(1)
    do DISABLE^%SYS.NOJRN()
    try
    {
        &sql(SET TRANSACTION %COMMITMODE NONE)
        do:pDelete ..%KillExtent()
        for tIdx = 1 : 1 : pMax
        {
            set tRandomStr = ##class(%PopulateUtils).StringMin(100,32000)
            &sql(INSERT INTO Duplicates(myText) VALUES(:tRandomStr))
            continue:SQLCODE'=0
            write:(tIdx#100=0) !,tIdx_" records inserted"
        }
        &sql(SET TRANSACTION %COMMITMODE IMPLICIT)
    }
    catch(tEx)
    {
        write !,"Oops..."
        write !,tEx.DisplayString()
    }
    DO ENABLE^%SYS.NOJRN()

    write !,"end "_..%ClassName(1)
}

}

I don't think that checking for duplicates can be solved other than have a reliable index per hash.

Note you can have more subtle hashing (hash sub-pieces, etc.) but you cannot have a reliable hasDuplicate property at runtime that goes beyond the current snapshot of data during INSERT.  IOW, it does not mean much in a system with lots of concurrent access.

In the end if will be 
 

SELECT COUNT(*) AS CountDuplicates
  FROM Test.Duplicates
 GROUP BY myHash
  HAVING COUNT(*) > 1

If you want to avoid duplicates you can make the index unique.

Agree with Robert (how couldn't I? ;) )

The crucial questions is how long does it take to copy the index vs. rebuilding it? And, if your index has not been rebuilt for a long time, will a reorganized global be beneficial to your performance? It usually is.

Also note that the recent versions of %BuildIndices() exploit multiple cores whenever possible and will run much faster than the older, serialized approach.

As Eduard mentioned, functional indices, or indices on collection with handcrafted BuildValueArray() for that matter, may be a different beast and may run for a long time. 

One of the really cool features of SQuirreL is the ability to have multiple different drivers of the same vendor. E.g. if you need to work with drivers from 2010.* to 2016.* you can have different drivers for each connection. 

In Java, it is a little bit of a challenge to use different versions of the same JDBC driver simultaneously, SQuirreL does that job very conveniently.

I also would like to understand the dummy number - what does that mean?

If you want to use %VID for pagination of large sets you might want to consider working with keysets in a snapshot rather than doing pseudo-windowing with %VID. This can become quite costly if you are enforcing an ordered output,

e.g.

SELECT *
  FROM
  (SELECT ID,Name
     FROM Sample.Person
    WHERE  Name LIKE ? )
WHERE %VID BETWEEN ? AND ?
ORDER BY Name

if your dataset is very large  this can be quite expensive if you plan to step through this pagewise.