//>
TableQueryFunctions.pkg
//>
//> The idea is that you declare a variable of type tTableQuery and tell it what table and what
//> filters you want and what output order you want. TQ then calculates the index to use for the.
//> scan. If it can not find an index in accordance with the desired output it will collect
//> all the sorting information in memory and refind the records in the desired order.
//>
//> If the code happens to be running against an MSSQL backend it will skip all the figuring
//> out and apply embedded SQL to the problem. This gives a potentially huge performance
//> increase at no effort on behalf of the programmer.
//>
//> oTQ is a global object (of class cTableQueryFunctions) that encapsulates a group of
//> functions. Using it to scan all orders in the order table goes like this:
//>
//>
//> Use TableQueryFunctions.pkg // Define oTableQueryFunctions object
//>
//> Procedure RunQuery
//> tTableQuery strQuery // I use the prefix 'str' to signify a struct type variable.
//>
//> Get NewQuery of oTQ Order.File_Number to strQuery
//>
//> While (FindRecord(oTQ,&strQuery))
//> Showln Order.Order_Number
//> Loop
//> End_Procedure
//>
//>
//> Note that the strQuery parameter is passed "by reference" to the FindRecord
//> function. That variable maintains the complete state of the scan and it is
//> modified for each iteration. The table query object itself is stateless.
//>
//> For every loop of the FindRecord function the record buffer of the order
//> table will be active with the record found. If you are planning on modifying or
//> deleting these rows you should force a "prefetch" of the rows by inserting
//>
//>
//> Send ForcePrefetch of oTQ (&strQuery)
//>
//>
//> right after the "Get NewQuery..." line. Transparantly to the FindRecord loop this
//> will find all the rows in advance and store their rowid's in an array. Only
//> then will it start looping through the array finding each record (again) by its rowid.
//>
//>
//> Not having to constantly write 'of oTQ' the code becomes easier to
//> read and therefore this form will be used from here on. But in real life, always
//> use the oTQ object.
//>
//>
//> Now let us say that we wanted (1) all invoices for a particular customer that was (2)
//> payed after a given date. Furthermore this Invoice table has a column called Country
//> and (3) we only want invoices for a subset of countries. And the records should be
//> ordered by the amount on the order, highest amount first (4).
//>
//>
//> Object oMyQuery is a cTableQueryFunctions
//> Procedure RunQuery
//> String[] aCountries
//> tTableQuery strQuery
//>
//> Get ListOfCountries to aCountries // Arbitrary list. It's an array of strings. That's what's important.
//>
//> Get NewQuery Invoice.File_Number to strQuery
//> Send AddFilterRelate (&strQuery) Customer.File_Number // 1
//> Send AddFilter (&strQuery) File_Field Invoice.Payed_Date tqGT dSomeDate // 2
//> Send AddFilterOrlist (&strQuery) File_Field Invoice.Country aCountries // 3 (must be a string array no matter the column data type)
//>
//> // And we also want it to be ordered backwards by the order total:
//> Send AddOrderBy (&strQuery) File_Field Invoice.Total True False // 4 (bDescending=True bUppercase=False)
//> // (add more ordering segments)
//>
//> While (FindRecord(Self,&strQuery))
//> Showln Invoice.Invoice_Number " " Invoice.Total
//> Loop
//> End_Procedure
//> End_Object
//>
//>
//> If you look at filter (2) in the example above you can see that the ">"-comperator is represented by
//> a tqGT symbol. Other comperators that may be used are:
//>
//> tqLT, tqLE, tqEQ, tqGE, tqGT
//>
//> end then there are these special ones intended for string evaluation and which cannot be used
//> for search optimization:
//>
//> tqNE, tqMATCH, tqUCMATCH
//>
//> Well, tqNE is not just for string evaluation.
//>
//>
//> You can run nested queries like this:
//>
//>
//> Object oMyQuery is a cTableQueryFunctions
//> Procedure RunQuery
//> tTableQuery strOrderQ strOrderLineQ
//>
//> Get NewQuery Order.File_Number to strOrderQ
//>
//> While (FindRecord(Self,&strOrderQ)) // <-- Go through all orders
//> Get NewQuery OrderLine.File_Number to strOrderLineQ
//> Send AddFilterRelate (&strOrderLineQ) Order.File_Number
//>
//> While (FindRecord(Self,&strOrderLineQ)) // <-- Go through all lines on active order
//> // Something
//> Loop
//> Loop
//> End_Procedure
//> End_Object
//>
//>
//> If you have a 2 level scan like above, you can save execution time by creating
//> the sub-query only once and then call ReUse to reinitialise it.
//>
//> With the ReUse method you can skip the repeated compilation (of the inner query):
//>
//>
//> Object oMyQuery is a cTableQueryFunctions
//> Procedure RunQuery
//> Open Order
//> Open OrderLin
//> tTableQuery strOrderQ strOrderlineQ
//> Get NewQuery Order.file_number to strOrderQ
//> Get NewQuery OrderLin.file_number to strOrderlineQ
//> Send AddFilterRelate (&strOrderlineQ) Order.file_number
//>
//> While (FindRecord(Self,&strOrderQ))
//> Send ReUse (&strOrderlineQ) // Only "compile" once
//> While (FindRecord(Self,&strOrderlineQ))
//> Loop
//> Loop
//> End_Procedure
//> End_Object
//>
//>
//> You may use the .iLimit member of the tTableQuery value to limit
//> the number of rows returned:
//>
//>
//> Send SetLimit (&strQuery) 50
//>
//>
//> And finally, to quickly find out if a query will return any rows at all
//> you can call function HasData:
//>
//>
//> Get HasData strQuery to bContinue // Note that strQuery for once is _not_ byref'ed.
//>
//>
//> Calling HasData could be a lot faster than setting iLimit to 1 since the latter
//> strategy may still force the query machine into generating the whole result-set and
//> sort it some way in order to find out which record is actually the first. Calling
//> HasData does not do that.
//>
//> If the table that you are table querying is an SQL table and it has been "opened as", TableQuery
//> has no way of knowing what table name to use in the select statement. If you prepare the
//> query with this line:
//>
//>
//> Send ForceSQLTableName (&strQuery) "Customer"
//>
//>
//> that name will be used in the ESQL select statement and you'll be fine.
//>
//> A note on the source code in this package:
//>
//> An "_" character in the beginning of a name indicates that the procedure, property or whatever is
//> meant to be used internally by this package only.
//>
//> Variable names are prefixed by s if string, b if boolean asf. Struct type variables are prefixed
//> 'str' which should make one think 'struct' (not 'string').
//>
//> In order to test the function of TableQueryFunctions.pkg without actually programming you may
//> download VDFXray (a multi-purpose VDF utility) from this address:
//>
//> ftp.stureaps.dk/software/visualdataflex/vdfxray/
//>
//> Use VDFXray to open a workspace, select a table in the tables list. Right click and select
//> "TableQuery Engine Inspector".
//>
//> You'll notice two panels appearing with the "TableQuery test facility" panel on top. If
//> your screen is large enough try to position that panel so that you can see the list of
//> columns in the panel behind it. If necessary by dragging the second panel partly off
//> screen. You'll see the selection of that list changes as you specify columns to filter by.
//> pkg.doc.end
// DAW packages:
Use VdfBase.pkg
Use cli.pkg
Use sql.pkg
// StureApsPublicLib packages
Use DateFunctions.pkg // oDateFunctions object
Use StringFunctions.pkg // oStringFunctions object
Use IndexFunctions.pkg
Use StackFunctions.pkg // oStackFunctions object
Use FileFunctions.pkg // oFileFunctions object
Use TemporaryTables.pkg
Use WindowsInfo.pkg
Use SQLStatementHandles.pkg
Use TableDriverFunctions.pkg
Use StructFunctions.pkg
Use RecordBufferFunctions.pkg
Enum_List // Column value types
Define tqSTRING
Define tqNUMBER
Define tqTEXT
Define tqDATE
Define tqDATETIME
End_Enum_List
Enum_List // Filter comparison modes
Define tqLT
Define tqLE
Define tqEQ
Define tqGE
Define tqGT
Define tqNE
// These can never be used for index optimization:
// Define tqCONTAINS // DO NOT USE
// Define tqIN // DO NOT USE (use orlist instead)
Define tqMATCH // When evaluated on the embedded database tqMATCH and tqUCMATCH may be
Define tqUCMATCH // expensive. On SQL they are converted to 'like' predicates and they fly.
Define _tqComparisonMax // Stop-marker
End_Enum_List
// Struct tTQResultRow
// RowID riRowId
// String sSortValue
// String[] aColumnValues
// End_Struct
class _cTQResultRow is a cArray
item_property_list
item_property string psRowID
item_property string psSortValue
item_property array phColumnValues // Pointer to string array
end_item_property_list _cTQResultRow
end_class
// Struct tTQResultSet
// Integer iTable
// tTQResultRow[] aRecords
// Integer _iCurrentRowPointer
// End_Struct
class _cTQResultSet is a cArray
procedure construct_object integer liImg
forward send construct_object liImg
property integer piTable
property integer phRecords // Points to _cTQResultRow type object
property integer _piCurrentRowPointer
end_procedure
end_class
// Struct _tTQValueSource
// // An actual value is derived like this:
// // If iTable<>0 then: get_field_value iTable iColumn
// // Else If (hFunction<>0) then: get hFunction of hObject iColumn // Column is passed to hFunction as argument
// // Else If aOrList has elements the value of the _iOrListPointer'th element in aOrList is assumed
// // Else: sConstant
// Integer iTable
// Integer iColumn
//
// Integer hObject
// Integer hFunction
//
// Integer iType
// Integer iLen
// Integer iDecimals
//
// String sConstant
//
// String _sValue
//
// String[] aOrList
// Integer _iOrListPointer
//
// String sSQLResultSetColumnName
// // Integer iSQLResultSetColumnNumber // This is implicitly given by the position of the _tTQValueSource member in the aSelectedColumns array of a tTableQuery type variable.
// End_Struct
class _cTQValueSource is a cArray
procedure construct_object integer liImg
forward send construct_object liImg
end_procedure
end_class
Struct tTQFilterSegment
_tTQValueSource strLeftValue
Integer iComp // tqEQ, tqLT, tgLE, tqMATCH etc...
tWildCardEvalSequence strMatchEvalSequence // If selection by wild card string matching this member is needed
_tTQValueSource strRightValue
End_Struct
Struct _tTQTableRelationSegment
Integer iSourceTable
Integer iSourceColumn
Integer iInnerJoinComp
Integer iTargetColumn
End_Struct
Struct tTQTableRelation
Integer iTargetTable // The table that is being related to
String sForceSQLTableName // If represented in a select statement we can force its name by setting this
_tTQTableRelationSegment[] aSegments // The columns being used for the relation. Note that these may stem from a number of different
// child tables. Note also that if aSegments is empty the record of the iTargetTable table is
// constant for the duration of the query execution. But they are all in effect "outer left joins".
Integer _iEmbeddedIndex // What embedded index should be used to find a record of this kind (the iTargetTable kind)
Boolean bFixed // A table is fixed if the record buffer is constant through the query.
Boolean bInnerJoin // If a relation is marked as "inner join" the SQL generated will do something I can't remember.
End_Struct
Struct tTQFilterExpression //
Number nExpense // Expected expense of evaluating this expression.
Boolean bNot // Applies to strOperand or aSubExpressions whichever is 'active'
tTQFilterSegment strOperand
tTQFilterExpression[] aSubExpressions // If this array has members then tTQFilterSegment is disregarded
Integer iAndOr // 0=leaf(nothing), 1=and, 2=or, 3=xOr
Boolean _bOpen
End_Struct
// a or b and c xor (d or not(e))
Enum_List // Filter expression 'keys'
Define _tqKeyNULL
Define tqKeyAND
Define tqKeyOR
Define tqKeyNOT
Define tqKeyPARLEFT
Define tqKeyPARRIGHT
End_Enum_List
Struct tTQOrderBySegment
Boolean bDescending
Boolean bUppercase
_tTQValueSource strValueSource
End_Struct
Struct _tTQInnerJoinControlBlock
Boolean bEnabled // Inner join enabled
Boolean bInitialized // Inner join initialised
String[] aCurrentParentSortValues // Sort values of current parent record
Boolean bSkipNextFetch // First run in inner join
Boolean bEndOfResultSet
End_Struct
Struct tTQPrefetchCacheControl
Boolean bUseSettings
Integer iPrefetchRecordCount
Integer iPrefetchRecordTimeout
End_Struct
Struct tTSQueryControlBlock
Boolean bCompiled
Boolean bInitialized
// How much do we prefer to limit the number of finds performed against the
// database over not having to sort all the selected rows in an array?
//
// A value of 0.1 (its default value) indicates that we are willing to accept
// 10 times more finds to not have to prefetch the whole set. A value of 1
// would mean that we are not willing to perform any unnecessary finds and
// thus prefetching would only occur if absolutely necessary.
//
// (actually it might make sense to set it according to the computing power or the
// network speed of the client and the expected size of the result set)
//
// If you set it higher than 1 it will prefer prefetch indices for no good
// reason (so don't do that).
Number nPrefetchAvoidanceFactor // Default assigned by NewQuery is 0.1
Integer iFindCount
Integer iSelectCount
Boolean bFirstRecord
Boolean bBreakScan
String sLogTag
Integer iCursor
Boolean[] aRelatedRecords // True if the same index in the aTableRelations array has already been executed
Integer iQueryMethod // tqDataFlex, tqSQLPKG or tqMERTECHINC
tSQLStatementHandle strSQLStatementHandle
Boolean bSQLTableIsAnsi
tTQPrefetchCacheControl strSQLCacheControl
tTQPrefetchCacheControl _strPreviousSQLCacheControl // Used to push the cache settings and restore previous value upon loop exit
_tTQInnerJoinControlBlock strInnerJoinCB
Boolean bFilterExpressionActive
End_Struct
Enum_List // Query methods
Define tqDATAFLEX
Define tqSQLPKG
Define tqMERTECHINC
End_Enum_List
// Struct tTSQueryCompiled
// Integer iIndex
// Boolean bBackwards
//
// Number nENR // Expected number of rows according to iIndex. If nENR is zero the index is fully specified
//
// Boolean bPrefetch
// Boolean bIndexSeedSharpeningEnabled
// Integer _iLimit
// Integer _iPushLimit
// tTQFilterSegment[] aJumpInValues
// tTQFilterSegment[] aJumpOutValues
// tTQFilterSegment[] aOtherConditions // Non optimized conditions
// End_Struct
class _cTSQueryCompiled is an cArray
procedure construct_object integer liImg
forward send construct_object liImg
property integer piIndex
property integer pbBackwards
property number pnENR
property integer pbPrefetch
property integer pbIndexSeedSharpeningEnabled
property integer _piLimit
property integer _piPushLimit
end_procedure
end_class
class _cTSQueryControlBlock is an cArray
procedure construct_object integer liImg
forward send construct_object liImg
end_procedure
end_class
class _cTQResultSet is an cArray
procedure construct_object integer liImg
forward send construct_object liImg
end_procedure
end_class
class cTableQueryCM is an cArray
procedure construct_object integer liImg
forward send construct_object liImg
property integer piTable
property integer pbNoESQL //> If true: Do not use ESQL even if SQL table.
property integer pbForcePrefetch //> If true: Force finding of all rows before FindRecord loop execution.
property integer piForceLimit //> If you don't care what the query analyzer says, you may force the table scan to be performed by this index (-1 means no force)
property integer piLImit //> Limits the amount of rows returned by the loop
property string psForceSQLTableName //> Use this table name when generating ESQL (over the default value from filelist.cfg)
property string psForceSQLStatement //> DANGER! Use this to completely overtake the SQL generated by TQ. It's your responsability then that the columns of the result set are in synch with what TQ expects (qua the result set specification).
property integer _pbOutputColumnsFixated
property integer pbRestoreRecordBuffersOnQueryCompletion
object _ostrCompiled is an Array NO_IMAGE
end_object
object _ostrControlBlock is an Array NO_IMAGE
end_object
object _ostrResultSet is an Array NO_IMAGE
end_object
end_procedure
end_class
//> The supreme struct!
Struct tTableQuery
Integer iTable
Boolean bNoESQL //> If true: Do not use ESQL even if SQL table.
Boolean bForcePrefetch //> If true: Force finding of all rows before FindRecord loop execution.
Integer iForceIndex //> If you don't care what the query analyzer says, you may force the table scan to be performed by this index (-1 means no force)
Integer iLimit //> Limits the amount of rows returned by the loop
String sForceSQLTableName //> Use this table name when generating ESQL (over the default value from filelist.cfg)
String sForceSQLStatement //> DANGER! Use this to completely overtake the SQL generated by TQ. It's your responsability then that the columns of the result set are in synch with what TQ expects (qua the result set specification).
// Select:
_tTQValueSource[] aColumnSources //> If this array is empty TQ will by default activate the iTable buffer.
_tTQValueSource[] aOutputColumns //> This array contains a copy of the items added to aColumnSources that are column specific. The order of the columns will be identical to the order in which they were added
Boolean _bOutputColumnsFixated
Boolean bRestoreRecordBuffersOnQueryCompletion
// Where:
tTQFilterSegment[] aFilters //> Segments are and'ed
// Where (for filterexpressions):
tTQFilterExpression[] aFilterExpressions //> are of course also and'ed if there are more of them
// Order by:
tTQOrderBySegment[] aOrdering
// House keeping:
tTSQueryCompiled _strCompiled
tTSQueryControlBlock _strControlBlock
tTQResultSet strResultSet
tTQTableRelation[] aTableRelations //> Defines relations to parent tables
End_Struct
Struct _tTQIndexRating
Integer iIndex
Number nExpectedNumberRows // With the filters supplied, how many rows can we expect to find by this index.
Boolean bOutputAgree // Does the index agree with the output order specified?
Integer iKeyLevels
Integer iKeyLength // A short key length might rate better if everything else was equal.
End_Struct
Struct _tTQIndexAnalysis
Number[] aSegmentReductions
End_Struct
Struct tTQTableAnalysis
Boolean bInitialized
DateTime dtAnalysisDateTime
Number nNumberOfRecords
_tTQIndexAnalysis[] aIndices
String sTimeToComplete
End_Struct
Struct tTQTableMeta
Integer _iIsInitialized
Integer[] aIndices // Indices to scan
End_Struct
Global_Variable tTQTableAnalysis[] _gTQaTableAnalysis
Global_Variable tTQTableMeta[] _gTQaTableMeta
Global_Variable Integer _giTQLogState // 0=No logging, 1=Log when LogTag'ed, 2=Log always
Global_Variable Integer[] _gaTQLogTables
Procedure Set TableQuery_LogState Global Integer iState
Move iState to _giTQLogState
End_Procedure
Class cTableQuerySQL is a cObject // Generates an SQL statement
Procedure Construct_Object
Forward Send Construct_Object
Property String psCollation "Latin1_General_BIN" // Or maybe "SQL_Latin1_CP850_BIN"? Who knows?
End_Procedure
Function _SQL_ColumnName tTableQuery strQ Integer hoTQ Integer iTable Integer iColumn Returns String
String sValue
Get ColumnName of hoTQ strQ iTable iColumn True to sValue
Function_Return sValue
End_Function
Function _SQL_TableName tTableQuery strQ Integer hoTQ Integer iTable Returns String
String sValue
Get TableName of hoTQ strQ iTable to sValue
Move ("["+sValue+"]") to sValue
Function_Return sValue
End_Function
Function _SQL_SelectColumns tTableQuery strQ Integer hoTQ Returns String
Integer iMax iItem iTable iColumn
String sColumnList sColumnName sTableName
Move (SizeOfArray(strQ.aColumnSources)-1) to iMax
If (iMax>=0) Begin
For iItem from 0 to iMax
If (iItem<>0) Begin
Move (sColumnList+",") to sColumnList
End
Move strQ.aColumnSources[iItem].iTable to iTable
Move strQ.aColumnSources[iItem].iColumn to iColumn
If (iColumn<>0) Begin
Get _SQL_ColumnName strQ hoTQ iTable iColumn to sColumnName
End
Else Begin
Get TableName of hoTQ strQ iTable to sTableName
Move (sTableName+".*") to sColumnName
End
Move (sColumnList+sColumnName) to sColumnList
Loop
End
Else Begin
Move "*" to sColumnList
End
Function_Return sColumnList
End_Function
Procedure _SQL_OuterLeftJoin_AddSegments tTableQuery strQ Integer hoTQ String ByRef sFromTables Integer iRelateItem
Integer iItem iItemMax
String sColumnName sComp
tTQTableRelation strRelation
Move strQ.aTableRelations[iRelateItem] to strRelation
Move (SizeOfArray(strRelation.aSegments)-1) to iItemMax
For iItem from 0 to iItemMax
If (iItem<>0) Begin
Move (sFromTables+" AND") to sFromTables
End
Get _SQL_ColumnName strQ hoTQ strRelation.aSegments[iItem].iSourceTable strRelation.aSegments[iItem].iSourceColumn to sColumnName
Move (sFromTables*sColumnName) to sFromTables
Get _SQL_WhereClauseComperator strRelation.aSegments[iItem].iInnerJoinComp False to sComp
Move (sFromTables*sComp) to sFromTables
Get _SQL_ColumnName strQ hoTQ strRelation.iTargetTable strRelation.aSegments[iItem].iTargetColumn to sColumnName
Move (sFromTables*sColumnName) to sFromTables
Loop
End_Procedure
//select f.foo
// , b.bar
// , q.qux
// from footable as f
//left outer
// join bartable as b
// on f.id = b.fid
//left outer
// join quxtable as q
// on b.id = q.bid
Function _SQL_OuterLeftJoins tTableQuery strQ Integer hoTQ Returns String
Integer iItem iItemMax
String sFromTables sTableName
Get TableName of hoTQ strQ strQ.iTable to sFromTables
Move (SizeOfArray(strQ.aTableRelations)-1) to iItemMax
For iItem from 0 to iItemMax
If (not(strQ.aTableRelations[iItem].bFixed)) Begin
Get _SQL_TableName strQ hoTQ strQ.aTableRelations[iItem].iTargetTable to sTableName
Move (sFromTables+" left outer join"*sTableName+" on (") to sFromTables
Send _SQL_OuterLeftJoin_AddSegments strQ hoTQ (&sFromTables) iItem
Move (sFromTables+")") to sFromTables
End
Loop
Function_Return sFromTables
End_Function
Function _SQL_InnerJoins tTableQuery strQ Integer hoTQ Returns String
Integer iItem iItemMax
String sFromTables sTableName
Move (SizeOfArray(strQ.aTableRelations)-1) to iItemMax
For iItem from 0 to iItemMax
If (strQ.aTableRelations[iItem].bInnerJoin) Begin
Get _SQL_TableName strQ hoTQ strQ.aTableRelations[iItem].iTargetTable to sTableName
Move (sFromTables+" inner join"*sTableName+" on (") to sFromTables
Send _SQL_OuterLeftJoin_AddSegments strQ hoTQ (&sFromTables) iItem
Move (sFromTables+")") to sFromTables
End
Loop
Function_Return sFromTables
End_Function
Function _SQL_WhereClauseComperator Integer iComp Boolean bIn Returns String
If (bIn) Begin
Function_Return " in "
End
If (iComp=tqLT) Function_Return "<"
If (iComp=tqLE) Function_Return "<="
If (iComp=tqEQ) Function_Return "="
If (iComp=tqGE) Function_Return ">="
If (iComp=tqGT) Function_Return ">"
If (iComp=tqNE) Function_Return "<>"
If (iComp=tqMATCH) Function_Return " like " // This can't be guaranteed on SQL. or what?
If (iComp=tqUCMATCH) Function_Return " like "
// If (iComp=tqCONTAINS) Function_Return "contains"
Function_Return "error"
End_Function
Function EscapeStringConstant String sValue Returns String
Function_Return (Replaces("'",sValue,"''")) // cross fingers
End_Function
Function _SQL_WhereClauseValue tTableQuery strQ Integer hoTQ Integer iComp _tTQValueSource strValue Returns String
Boolean bFixed bDoValue
Integer iMax iItem
String sValue sColumnName sSQLdate
If (iComp=tqEQ and SizeOfArray(strValue.aOrList)<>0) Begin // IN
Move (SizeOfArray(strValue.aOrList)-1) to iMax
Move "(" to sValue
For iItem from 0 to iMax
If (iItem>0) Begin
Move (sValue+",") to sValue
End
If (strValue.iType=tqSTRING or strValue.iType=tqTEXT) Begin
Move (sValue+"'"+EscapeStringConstant(Self,strValue.aOrList[iItem])+"'") to sValue
End
Else If (strValue.iType=tqDATE or strValue.iType=tqDATETIME) Begin
Get DFDateToSQLDate of strQ._strControlBlock.strSQLStatementHandle.hHandle (Date(strValue.aOrList[iItem])) to sSQLdate
Move (sValue+"'"+sSQLdate+"'") to sValue
End
Else Begin
Move (sValue+strValue.aOrList[iItem]) to sValue
End
Loop
Move (sValue+")") to sValue
End
Else Begin
Move True to bDoValue
If (strValue.iTable<>0) Begin
Get _IsTableFixed of hoTQ strQ strValue.iTable to bFixed
If (not(bFixed)) Begin
Get _SQL_ColumnName strQ hoTQ strValue.iTable strValue.iColumn to sColumnName
Move sColumnName to sValue
Move False to bDoValue
End
End
If (bDoValue) Begin // Either it's a constant or it's a fixed table:
Get _ValueSourceValue of hoTQ (&strQ) strValue to sValue
If (iComp=tqMATCH or iComp=tqUCMATCH) Begin
Move (Replaces("%",sValue,"\%")) to sValue // Escape all SQL wildcard patterns
Move (Replaces("_",sValue,"\_")) to sValue // that happens to be in the string
Move (Replaces("[",sValue,"\[")) to sValue // by chance.
Move (Replaces("]",sValue,"\]")) to sValue
Move (Replaces("*",sValue,"%")) to sValue // Replace our own * and ? wildcards
Move (Replaces("?",sValue,"_")) to sValue // with their SQL counterparts.
If (iComp=tqUCMATCH) Begin
Move (Uppercase(sValue)) to sValue
End
End
If (strValue.iType=tqSTRING or strValue.iType=tqTEXT) Begin
Move ("'"+EscapeStringConstant(Self,sValue)+"'") to sValue
End
Else If (strValue.iType=tqDATE or strValue.iType=tqDATETIME) Begin
Get DFDateToSQLDate of strQ._strControlBlock.strSQLStatementHandle.hHandle (Date(sValue)) to sValue
Move ("'"+sValue+"'") to sValue
End
If (iComp=tqMATCH or iComp=tqUCMATCH) Begin
Move (sValue*"ESCAPE '\'") to sValue // tell mssql that the pattern has been escaped with the "\" character.
End
End
End
Function_Return sValue
End_Function
Function _SQL_WhereClauseSegment tTableQuery strQ tTQFilterSegment strSegment Integer hoTQ Returns String
Integer iTable iColumn
Boolean bIn
String sValue
Move strSegment.strLeftValue.iTable to iTable
Move strSegment.strLeftValue.iColumn to iColumn
Get _SQL_ColumnName strq hoTQ iTable iColumn to sValue
Move (SizeOfArray(strSegment.strRightValue.aOrList)<>0) to bIn
Move (sValue+_SQL_WhereClauseComperator(Self,strSegment.iComp,bIn)) to sValue
Move (sValue+_SQL_WhereClauseValue(Self,strQ,hoTQ,strSegment.iComp,strSegment.strRightValue)) to sValue
Function_Return sValue
End_Function
Function _SQL_WhereClauseExpressionSegment tTableQuery strQ tTQFilterExpression[] aSegments Integer hoTQ Returns String
Integer iItem iItemMax
String sValue
Move (SizeOfArray(aSegments)-1) to iItemMax
For iItem from 0 to iItemMax
If (iItem>0) Begin
If (aSegments[iItem-1].iAndOr=tqKeyAND) Begin
Move (sValue*"AND") to sValue
End
Else If (aSegments[iItem-1].iAndOr=tqKeyOR) Begin
Move (sValue*"OR") to sValue
End
Else Begin
Send OnError 738 "Illegal operator in where clause expression"
End
End
If (aSegments[iItem].bNot) Begin
Move (sValue*"NOT") to sValue
End
If (SizeOfArray(aSegments[iItem].aSubExpressions)>0) Begin
Move (sValue*_SQL_WhereClauseExpressionSegment(Self,strQ,aSegments[iItem].aSubExpressions,hoTQ)) to sValue
End
Else Begin
Move (sValue*_SQL_WhereClauseSegment(Self,strQ,aSegments[iItem].strOperand,hoTQ)) to sValue
End
Loop
Function_Return ("("+Trim(sValue)+")")
End_Function
Function _SQL_WhereClause tTableQuery strQ Integer hoTQ Returns String
Integer iMax iItem
Boolean bAddAND
String sValue
Move (SizeOfArray(strQ.aFilters)-1) to iMax
For iItem from 0 to iMax
If (iItem<>0) Begin
Move (sValue*"and") to sValue
End
Move (sValue*_SQL_WhereClauseSegment(Self,strQ,strQ.aFilters[iItem],hoTQ)) to sValue
Loop
Move (iMax>=0) to bAddAND
Move (SizeOfArray(strQ.aFilterExpressions)-1) to iMax
For iItem from 0 to iMax
If (iItem>0 or bAddAND) Begin
Move (sValue*"and") to sValue
End
Move (sValue*_SQL_WhereClauseExpressionSegment(Self,strQ,strQ.aFilterExpressions[iItem].aSubExpressions,hoTQ)) to sValue
Loop
Function_Return sValue
End_Function
Function _SQL_OrderByClause tTableQuery strQ Integer hoTQ Returns String
Integer iItem iMax iTable iColumn
String sValue sColumnName
Move (SizeOfArray(strQ.aOrdering)-1) to iMax
For iItem from 0 to iMax
If (iItem<>0) Begin
Move (sValue+", ") to sValue
End
Move strQ.aOrdering[iItem].strValueSource.iTable to iTable
Move strQ.aOrdering[iItem].strValueSource.iColumn to iColumn
Get _SQL_ColumnName strQ hoTQ iTable iColumn to sColumnName
Move (sValue+sColumnName) to sValue
If (strQ.aOrdering[iItem].strValueSource.iType=tqSTRING) Begin
Move (sValue+" collate "+psCollation(Self)) to sValue
End
If (strQ.aOrdering[iItem].bDescending) Begin
Move (sValue*"DESC") to sValue
End
// ToDo: uppercase ordering
// If (strQ.aOrdering[iItem].bUppercase) Begin
// // Somethings wrong here. And it's not the 'lower' instead of 'upper'
// Move (sValue*"lower("+sValue+")") to sValue
// End
Loop
Function_Return sValue
End_Function
Function _SQLSelectStatement tTableQuery strQ Integer hoTQ Returns String
String sStatement
Move "select" to sStatement
If (strQ.iLimit>0) Begin
Move (sStatement*"top "+String(strQ.iLimit)) to sStatement
End
If (strQ.iLimit<0) Begin
Move (sStatement*"bottom "+String(0-strQ.iLimit)) to sStatement
End
Move (sStatement*_SQL_SelectColumns(Self,strQ,hoTQ)) to sStatement
Move (sStatement*" from") to sStatement
Move (sStatement*_SQL_OuterLeftJoins(Self,strQ,hoTQ)) to sStatement // related records
Move (sStatement*_SQL_InnerJoins(Self,strQ,hoTQ)) to sStatement // find child records for each parent
If (SizeOfArray(strQ.aFilters)<>0 or SizeOfArray(strQ.aFilterExpressions)<>0) Begin
Move (sStatement*" where") to sStatement
Move (sStatement*_SQL_WhereClause(Self,strQ,hoTQ)) to sStatement
End
If (SizeOfArray(strQ.aOrdering)<>0) Begin
Move (sStatement*" order by") to sStatement
Move (sStatement*_SQL_OrderByClause(Self,strQ,hoTQ)) to sStatement
End
Function_Return sStatement
End_Function
Function _SQLDeleteStatement tTableQuery strQ Integer hoTQ Returns String
String sStatement
Move "delete" to sStatement
// If (strQ.iLimit<>0) Begin
// Send OnError 742 "Limit not allowed on delete statement"
// Function_Return "" // Error
// End
//
// If (SizeOfArray(strQ.aColumnSources)>0) Begin
// Send OnError 743 "Result column not allowed on delete statement"
// Function_Return "" // Error
// End
Move (sStatement*" from") to sStatement
Move (sStatement*_SQL_OuterLeftJoins(Self,strQ,hoTQ)) to sStatement // related records
Move (sStatement*_SQL_InnerJoins(Self,strQ,hoTQ)) to sStatement // find child records for each parent
If (SizeOfArray(strQ.aFilters)<>0 or SizeOfArray(strQ.aFilterExpressions)<>0) Begin
Move (sStatement*" where") to sStatement
Move (sStatement*_SQL_WhereClause(Self,strQ,hoTQ)) to sStatement
End
// If (SizeOfArray(strQ.aOrdering)>0) Begin
// Send OnError 744 "Ordering not allowed on delete statement"
// Function_Return "" // Error
// End
Function_Return sStatement
End_Function
End_Class
Class cTableQueryFunctions is a cObject
Procedure Construct_Object
Forward Send Construct_Object
Property Number _pnPrefetchAvoidanceFactor // Temporary property used by index rating sort algorithm
Object oSQLGeneratorMSSQLDRV is a cTableQuerySQL
End_Object
Property Handle phoSQLGeneratorMSSQLDRC (oSQLGeneratorMSSQLDRV(Self))
End_Procedure
Function Version Returns String
// Function_Return "0.96" // 9-05-2012
// Fixed error in MSSQLDRV SQL generating when an ordering segment is both ASCII and descending
// Removed timing and track of current-record
// Function_Return "0.97" // 10-05-2012
// Addded methods ResetResultSetColumns and ResetOrdering
// Fixed seeding error on backwards ASCII columns
// Bug in ResultColumnValue function fixed
// Bug in _FindFirstRecordAux function fixed (related to 'VFind GE')
// SQL handling of dates repaired
// Parent column filter bugfix. No longer ends up in jump-in/out filters by accident
// Fixed _ValidateQuery to correctly complain about over-constrain on parent table columns
// function SQLStatement renamed to SQLSelectStatement
// Function_Return "0.98" // 29-11-2012
// SQL date handling is now also effective if column type is DF_DATETIME (may go wrong if value also has a time part)
// Implementation of private function _DeleteConstraint changed (old one still there for 15.1)
// Method DeleteRecords added (ignore, not tested)
// Function_Return "0.99" // 04-04-2013
// Fixed bug in AddFilterTableColumn method.
// Comperators tqCONTAINS and tqIN have been removed.
// Filter evaluating for index seeding capacitance has been fixed. Until now filters on parent tables may have wronged the index selection process.
// Error handling changed. All errors are now declared as DFERR_PROGRAM. If cTableQueryFunctions is subclassed procedure OnError may be augmented to do otherwise.
// Function HasData was designed to check for data before the main finding loop was entered. It may now also meaningfully be called after the main loop to query if the loop had any data at all.
// Function FixedTables added. Returns an array of tables that are referenced as "fixed" during the execution of a query. So if I want to repeat the find loop of a particular tTableQuery variable at a later time, the record buffers of these fixed tables must be restored to that of the original query. Does this make sense?
// Similarly NotFixedTables has been added.
// Function Belongs added. Returns true if the record currently in the buffer of the query main table evaluates true against the filters of query variable passed.
// Procedure RestoreRecordBuffersOnQueryCompletion added. Use to have TQ restore all affected record buffers on find loop completion.
// Function_Return "0.991" // 25-09-2013
Function_Return "0.992"
End_Function
Function NewQuery Integer iTable Returns tTableQuery
Integer iFilterTable iArg
tTableQuery strQuery
Move iTable to strQuery.iTable
Move -1 to strQuery.iForceIndex
Move False to strQuery.bForcePrefetch
Move False to strQuery.bNoESQL
Move False to strQuery.bRestoreRecordBuffersOnQueryCompletion
Move False to strQuery._strControlBlock.bInitialized
Move False to strQuery._strControlBlock.bCompiled
Move -1 to strQuery._strControlBlock.iQueryMethod
Move 0.1 to strQuery._strControlBlock.nPrefetchAvoidanceFactor
Move False to strQuery._strControlBlock.strInnerJoinCB.bEnabled // (CB=Control Block)
Move False to strQuery._strControlBlock.strInnerJoinCB.bInitialized
Move False to strQuery._strControlBlock.bFilterExpressionActive
Move True to strQuery._strCompiled.bIndexSeedSharpeningEnabled
Move False to strQuery._strCompiled.bBackwards
Move -1 to strQuery._strCompiled.nENR
Move False to strQuery._bOutputColumnsFixated
Send ReadTableDefinition of oOverlapColumnFunctions iTable // Make sure that oOverlapColumnFunctions knows about this table
If (num_arguments>1) Begin // For a while multiple parameters was allowed. Therefore this. Also for a while.
Error 343 "TQ: Illegal call to NewQuery"
End
Function_Return strQuery
End_Function
Procedure OnError Integer iErrNo String sErrorText
Error DFERR_PROGRAM ("TQ-"+String(iErrNo)+": "+sErrorText)
End_Procedure
//> If you have a 2 level scan (scanning orders, and order lines for each order) you can save execution time by creating
//> the sub-query only once and then call ReUse to reinitialise it.
//>
//> This is how you might go about it without the ReUse method:
//>
//> Open Order
//> Open OrderLin
//> tTableQuery strOrderQuery strOrderlineQuery
//>
//> Get NewQuery of oTQ Order.file_number to strOrderQuery
//>
//> While (FindRecord(oTQ,&strOrderQuery))
//> Get NewQuery of oTQ OrderLin.file_number to strOrderlineQuery // New query for each order
//> Send AddFilterRelate of oTQ (&strOrderlineQuery) Order.file_number
//> While (FindRecord(oTQ,&strOrderlineQuery))
//> Loop
//> Loop
//>
//> With the ReUse method you can skip the repeated "compilation" of the inner query:
//>
//> Open Order
//> Open OrderLin
//> tTableQuery strOrderQuery strOrderlineQuery
//> Get NewQuery of oTQ Order.file_number to strOrderQuery
//> Get NewQuery of oTQ OrderLin.file_number to strOrderlineQuery
//> Send AddFilterRelate of oTQ (&strOrderlineQuery) Order.file_number
//>
//> While (FindRecord(oTQ,&strOrderQuery))
//> Send ReUse of oTQ (&strOrderlineQuery)
//> While (FindRecord(oTQ,&strOrderlineQuery))
//> Loop
//> Loop
//>
Procedure ReUse tTableQuery ByRef strQuery
Move False to strQuery._strControlBlock.bInitialized
End_Procedure
//> The scanning algorithm may transparantly to the FindRecord loop decide
//> to find all the rows in advance (if for example the desired output order
//> is incompatible with the optimum search order). But if you call ForcePrefind
//> you can force the algorithm to pre-find the rows regardless of whether it
//> is necessary (or not?).
//> This is relevant if editing indexed columns of the rows found during the
//> FindRecord loop.
//
//> ForcePrefetch has no relevance (but does no harm) when run on a SQL table because the
//> algorithm in this case generates an SQL statement via the driver. In this
//> mode way the rows are "pre-found" anyway.
Procedure ForcePrefetch tTableQuery ByRef strQuery // Records will be prefetched.
Move True to strQuery.bForcePrefetch
End_Procedure
//> Even if we are querying an SQL table we may have reasons to force TQ to use
//> the record oriented driver interface. This call does that:
//>
//> Send ForcePrefetch of oTQ (&strQ)
//>
//> If in addition we also want to specify the cache parameters used by the
//> driver we can also specify prefetch count and cache timeout (for the
//> duration of the query:
//>
//> Send ForcePrefetch of oTQ (&strQ) 1000 2000
//>
//> This sets the prefetch cache to 1000 records and cache timeout to 2 seconds
//> and this will most certaimly give you a boost compared to the default values
//> which are 10/records/10ms.
Procedure ForceNoESQL tTableQuery ByRef strQuery Integer iPrefetchSize Integer iCacheTimeoutMS // ESQL will not be applied
Move True to strQuery.bNoESQL
If (num_arguments>1) Begin
Move True to strQuery._strControlBlock.strSQLCacheControl.bUseSettings
Move iPrefetchSize to strQuery._strControlBlock.strSQLCacheControl.iPrefetchRecordCount
If (num_arguments>2) Begin
Move iCacheTimeoutMS to strQuery._strControlBlock.strSQLCacheControl.iPrefetchRecordTimeout
End
Else Begin
Move 0 to strQuery._strControlBlock.strSQLCacheControl.iPrefetchRecordTimeout
End
End
Else Begin
Move False to strQuery._strControlBlock.strSQLCacheControl.bUseSettings
End
End_Procedure
//> Don't evaluate, just use iIndex I tell you. Ignored by ESQL
Procedure ForceIndex tTableQuery ByRef strQuery Integer iIndex
Move iIndex to strQuery.iForceIndex
End_Procedure
//> Limit number of rows in the result set.
Procedure SetLimit tTableQuery ByRef strQuery Integer iLimit
Move iLimit to strQuery.iLimit
End_Procedure
// Procedure SetExpectedNumberOfRecords tTableQuery ByRef strQuery Integer iResultSetSize // Just a thought
// End_Procedure
//> Use this table name when generating ESQL rather than the default one. Because I tell you to!
Procedure ForceSQLTableName tTableQuery ByRef strQuery String sSqlTableName
Move sSqlTableName to strQuery.sForceSQLTableName
End_Procedure
//> DANGER! Completely overtakes the scene. Make sure to "select" the columns expected by TQ
Procedure ForceSQLStatement tTableQuery ByRef strQuery String sSQLStatement
Move sSQLStatement to strQuery.sForceSQLStatement
End_Procedure
Procedure RestoreRecordBuffersOnQueryCompletion tTableQuery ByRef strQuery Boolean bState
Move bState to strQuery.bRestoreRecordBuffersOnQueryCompletion
End_Procedure
//> Use LogTag to distinguish between different FindRecord loops in the log. Or
//> between different situations in different runs of the same FindLoop. When this is set, TQ
//> will create a record in an un-listed log table by the name of (LogTableName(oTQ,strQuery))
Procedure SetLogTag tTableQuery ByRef strQuery String sTag
Move sTag to strQuery._strControlBlock.sLogTag
End_Procedure
//> Call BreakScan to terminate the FindRecord loop on next iteration. Note that when (explicitly or implicitly) we have set
//> the bForcePrefetch attribute, this will not be effective until we enter the actual find-loop. Which in that case will be
//> after all the rows have been found.
Procedure BreakScan tTableQuery ByRef strQuery
Move True to strQuery._strControlBlock.bBreakScan
End_Procedure
//> After query execution this will return an (english) string: "So many selected rows in so many finds"
Function StatisticsString tTableQuery strQuery Returns String
Integer iFound iSelect
Move strQuery._strControlBlock.iFindCount to iFound
Move strQuery._strControlBlock.iSelectCount to iSelect
Function_Return (String(iSelect)+" selected rows (in "+String(iFound)+" finds)")
End_Function
//> Returns the number of milliseconds it takes to make an "empty" round-trip to the SQL server. It makes at least 4 server calls.
Function ZeroSelectTime Integer iTable Returns Number
tSystemTimeMS strStart strStop
tTableQuery strQuery
Boolean bFound
Get NewQuery iTable to strQuery
Send ForceSQLStatement (&strQuery) "select 1"
Repeat
Get FindRecord (&strQuery) to bFound
Until (not(bFound))
Send ReUse (&strQuery)
Get SystemTimeMilliSeconds of oDateFunctions to strStart // We time the second loop (in case the first loop had to set up a connection)
Repeat
Get FindRecord (&strQuery) to bFound
Until (not(bFound))
Get SystemTimeMilliSeconds of oDateFunctions to strStop
Function_Return (SystemTimeMilliSecondsElapsed(oDateFunctions,strStart, strStop))
End_Function
//> Returns the table name of iTable as used (in SQL staments) in the strQuery context.
Function TableName tTableQuery strQuery Integer iTable Returns String
Integer iRelateItem
String sValue
If (iTable=strQuery.iTable) Begin
Move strQuery.sForceSQLTableName to sValue
End
Else Begin
Get _FindTableRelation strQuery iTable to iRelateItem
If (iRelateItem>=0) Begin
Move strQuery.aTableRelations[iRelateItem].sForceSQLTableName to sValue
End
End
If (sValue="") Begin
Get_Attribute DF_FILE_LOGICAL_NAME of iTable to sValue
End
If (sValue="") Begin
Send OnError 701 ("Table name could not be found for table "+String(iTable))
End
Function_Return sValue
End_Function
//> Returns column name of iTable.iColumn as used (in SQL staments) in the strQuery context.
Function ColumnName tTableQuery strQuery Integer iTable Integer iColumn Boolean bIncludeTableName Returns String
String sColumnName sTableName
Get_Attribute DF_FIELD_NAME of iTable iColumn to sColumnName
If (sColumnName<>"") Begin
If (bIncludeTableName) Begin
Get TableName strQuery iTable iColumn to sTableName
Function_Return (sTableName+"."+sColumnName)
End
Else Begin
Function_Return sColumnName
End
End
Function_Return ""
End_Function
Function ComperatorID2String Integer iComp Returns String
If (iComp=tqLT) Function_Return "<"
If (iComp=tqLE) Function_Return "<="
If (iComp=tqEQ) Function_Return "="
If (iComp=tqGE) Function_Return ">="
If (iComp=tqGT) Function_Return ">"
If (iComp=tqNE) Function_Return "<>"
// If (iComp=tqIN) Function_Return "in"
// If (iComp=tqCONTAINS) Function_Return "contains"
If (iComp=tqMATCH) Function_Return "match"
If (iComp=tqUCMATCH) Function_Return "ucmatch"
Function_Return "error"
End_Function
Function ComperatorID2ComperatorString Integer iComp Returns String
If (iComp=tqLT) Function_Return "tqLT"
If (iComp=tqLE) Function_Return "tqLE"
If (iComp=tqEQ) Function_Return "tqEQ"
If (iComp=tqGE) Function_Return "tqGE"
If (iComp=tqGT) Function_Return "tqGT"
If (iComp=tqNE) Function_Return "tqNE"
// If (iComp=tqIN) Function_Return "tqIN"
// If (iComp=tqCONTAINS) Function_Return "tqCONTAINS"
If (iComp=tqMATCH) Function_Return "tqMATCH"
If (iComp=tqUCMATCH) Function_Return "tqUCMATCH"
End_Function
Function ComperatorString2ID String sComp Returns Integer
Integer iMax iItem
Move (_tqComparisonMax-1) to iMax
Move (Lowercase(sComp)) to sComp
For iItem from 0 to iMax
If (sComp=Lowercase(ComperatorID2String(Self,iItem))) Begin
Function_Return iItem
End
Loop
Function_Return -1 // not found
End_Function
Procedure AddOrderBy tTableQuery ByRef strQuery Integer iTable Integer iColumn Boolean bDescending Boolean bUppercase
Integer iCount iRelateItem
Move (SizeOfArray(strQuery.aOrdering)) to iCount
Move iTable to strQuery.aOrdering[iCount].strValueSource.iTable
Move iColumn to strQuery.aOrdering[iCount].strValueSource.iColumn
Get _ColumnType iTable iColumn to strQuery.aOrdering[iCount].strValueSource.iType
If (num_arguments>=4) Begin
Move bDescending to strQuery.aOrdering[iCount].bDescending
End
Else Begin
Move False to strQuery.aOrdering[iCount].bDescending
End
If (num_arguments>=5) Begin
Move bUppercase to strQuery.aOrdering[iCount].bUppercase
End
Else Begin
Move False to strQuery.aOrdering[iCount].bUppercase
End
If (iTable<>strQuery.iTable) Begin
// We will also check if this parent table is accessible via the known table relations:
Get _FindTableRelation strQuery iTable to iRelateItem
If (iRelateItem=-1) Begin
Send OnError 702 ("Unknown table relation to table "+String(iTable))
End
Else Begin
// We're ordering by a parent table column which makes us have to prefetch the result:
Move True to strQuery.bForcePrefetch
End
End
End_Procedure
//> Delete all ordering information from the strQuery variable
Procedure ResetOrdering tTableQuery ByRef strQuery
// Check that the query is not already executing
If (strQuery._strControlBlock.bInitialized) Begin
Send OnError 740 "ResetOrdering called while query is executing"
End
Else Begin
Move (ResizeArray(strQuery.aOrdering,0)) to strQuery.aOrdering
End
End_Procedure
Function _FindOrderBySegment tTableQuery strQuery Integer iTable Integer iColumn Returns Integer
Integer iItem iItemMax
Move (SizeOfArray(strQuery.aOrdering)-1) to iItemMax
For iItem from 0 to iItemMax
If (iTable=strQuery.aOrdering[iItem].strValueSource.iTable and iColumn=strQuery.aOrdering[iItem].strValueSource.iColumn) Begin
Function_Return iItem
End
Loop
Function_Return -1 // Not found
End_Function
//> Shorthand for sending the AddOrdering a number of times to match the factual search order of a particular index. It's hard to explain why you'd do it, but I have.
Procedure SetOrderByToIndex tTableQuery ByRef strQuery Integer iIndex
tOCIndex strIndex
Integer iSegment iSegments
Get IndexDefinition of oOverlapColumnFunctions strQuery.iTable iIndex to strIndex
Move (SizeOfArray(strIndex.aSegments)-1) to iSegments
For iSegment from 0 to iSegments
Send AddOrderBy (&strQuery) strQuery.iTable strIndex.aSegments[iSegment].iColumn strIndex.aSegments[iSegment].bDescending strIndex.aSegments[iSegment].bUppercase
Loop
End_Procedure
Function _ColumnType Integer iTable Integer iColumn Returns Integer
Integer iVdfType
Get_Attribute DF_FIELD_TYPE of iTable iColumn to iVdfType
If (iVdfType=DF_ASCII) Function_Return tqSTRING
If (iVdfType=DF_OVERLAP) Function_Return tqSTRING
If (iVdfType=DF_BCD) Function_Return tqNUMBER
If (iVdfType=DF_DATE) Function_Return tqDATE
If (iVdfType=DF_DATETIME) Function_Return tqDATETIME
If (iVdfType=DF_TEXT) Function_Return tqTEXT
Function_Return -1
End_Function
Procedure AddFilter tTableQuery ByRef strQuery Integer iTable Integer iColumn Integer iComp String sConstant
Integer iConstraintIndex iRelateItem
tTQFilterSegment strSegment
Move iTable to strSegment.strLeftValue.iTable
Move iColumn to strSegment.strLeftValue.iColumn
Get _ColumnType iTable iColumn to strSegment.strLeftValue.iType
Move iComp to strSegment.iComp
Move sConstant to strSegment.strRightValue.sConstant
Get _ColumnType iTable iColumn to strSegment.strRightValue.iType
If (iTable<>strQuery.iTable) Begin
// Check if this parent table is accessible via the known table relations:
Get _FindTableRelation strQuery iTable to iRelateItem
If (iRelateItem=-1) Begin
Send OnError 703 ("Unknown table relation to table "+String(iTable))
End
End
If (strQuery._strControlBlock.bFilterExpressionActive) Begin
Send FilterExpressionAddOperand (&strQuery) strSegment
End
Else Begin
Move (SizeOfArray(strQuery.aFilters)) to iConstraintIndex
Move strSegment to strQuery.aFilters[iConstraintIndex]
End
End_Procedure
Procedure AddFilterOrList tTableQuery ByRef strQuery Integer iTable Integer iColumn String[] aOrList
Integer iConstraintIndex iRelateItem
tTQFilterSegment strSegment
Move iTable to strSegment.strLeftValue.iTable
Move iColumn to strSegment.strLeftValue.iColumn
Get _ColumnType iTable iColumn to strSegment.strLeftValue.iType
Move tqEQ to strSegment.iComp
Move aOrList to strSegment.strRightValue.aOrList
Move 0 to strSegment.strRightValue._iOrListPointer
Get _ColumnType iTable iColumn to strSegment.strRightValue.iType
If (iTable<>strQuery.iTable) Begin
// Check if this parent table is accessible via the known table relations:
Get _FindTableRelation strQuery iTable to iRelateItem
If (iRelateItem=-1) Begin
Send OnError 704 ("Unknown table relation to table "+String(iTable))
End
End
If (strQuery._strControlBlock.bFilterExpressionActive) Begin
Send FilterExpressionAddOperand (&strQuery) strSegment
End
Else Begin
Move (SizeOfArray(strQuery.aFilters)) to iConstraintIndex
Move strSegment to strQuery.aFilters[iConstraintIndex]
End
End_Procedure
Procedure AddFilterTableColumn tTableQuery ByRef strQuery Integer iTable Integer iColumn Integer iComp Integer iFilterTable Integer iFilterColumn
Integer iConstraintIndex iRelateItem
tTQFilterSegment strSegment
Move iTable to strSegment.strLeftValue.iTable
Move iColumn to strSegment.strLeftValue.iColumn
Get _ColumnType iTable iColumn to strSegment.strLeftValue.iType
Move iComp to strSegment.iComp
Move iFilterTable to strSegment.strRightValue.iTable
Move iFilterColumn to strSegment.strRightValue.iColumn
Get _ColumnType iFilterTable iFilterColumn to strSegment.strRightValue.iType
If (strSegment.strLeftValue.iTable<>strQuery.iTable) Begin
// Check if left hand table is accessible via the known table relations:
Get _FindTableRelation strQuery strSegment.strLeftValue.iTable to iRelateItem
If (iRelateItem=-1) Begin
Send OnError 705 ("Unknown table relation to table "+String(strSegment.strLeftValue.iTable))
End
End
If (strQuery._strControlBlock.bFilterExpressionActive) Begin
Send FilterExpressionAddOperand (&strQuery) strSegment
End
Else Begin
Move (SizeOfArray(strQuery.aFilters)) to iConstraintIndex
Move strSegment to strQuery.aFilters[iConstraintIndex]
End
End_Procedure
Procedure _AddConstrainRelateSegments tTableQuery ByRef strQuery Integer iTable Integer[] aColumns Integer iRelTable Integer[] aRelColumns
Integer iColumns iItem
Move (SizeOfArray(aColumns)-1) to iColumns
If ((iColumns+1)<>SizeOfArray(aRelColumns)) Begin
Send OnError 706 "Number of segments do not match in AddFilterRelate method"
End
For iItem from 0 to iColumns
If (iItem<>0 and strQuery._strControlBlock.bFilterExpressionActive) Begin
Send FilterPressKey (&strQuery) tqKeyAND // Put and between the segments
End
Send AddFilterTableColumn (&strQuery) iTable aColumns[iItem] tqEQ iRelTable aRelColumns[iItem]
Loop
End_Procedure
Procedure AddFilterRelate tTableQuery ByRef strQuery Integer iParentTable
Integer iTable iColumn iColumns iRelTable iRelColumn
Boolean bAnyColumnsAtAll
Send ReadTableDefinition of oOverlapColumnFunctions iParentTable
Move strQuery.iTable to iTable
Move False to bAnyColumnsAtAll
Get_Attribute DF_FILE_NUMBER_FIELDS of iTable to iColumns
For iColumn from 1 to iColumns
Get_Attribute DF_FIELD_RELATED_FILE of iTable iColumn to iRelTable
If (iRelTable=iParentTable) Begin
Get_Attribute DF_FIELD_RELATED_FIELD of iTable iColumn to iRelColumn
Send _AddConstrainRelateSegments (&strQuery) strQuery.iTable (TranslateOverlap(oOverlapColumnFunctions,iTable,iColumn)) iRelTable (TranslateOverlap(oOverlapColumnFunctions,iRelTable,iRelColumn))
Move True to bAnyColumnsAtAll
End
Loop
If (bAnyColumnsAtAll) Begin // If there were any segments relating we assume that the relation is good. If not we consider it an error of the table definitions.
// Tell the relationship handler that it should not worry about iParentTable. It is automatically and constantly related.
Send AddTableRelationFixedRecord (&strQuery) iParentTable
End
Else Begin
Send OnError 707 "Main query table does not relate to the parent table passed"
End
End_Procedure
//> AddFilterRelateFixedRecord is equivalent to AddFilterRelate but it defines the
//> filter based on constant filters rather than dynamic "table column filters".
Procedure AddFilterRelateFixedRecord tTableQuery ByRef strQuery Integer iParentTable
Integer iTable iColumn iColumns iRelTable iRelColumn iIndex iMax
Integer[] aColumns aRelColumns
String sValue
Boolean bAnythingAtAll
Send ReadTableDefinition of oOverlapColumnFunctions iParentTable
Move strQuery.iTable to iTable
Move False to bAnythingAtAll
Get_Attribute DF_FILE_NUMBER_FIELDS of iTable to iColumns
For iColumn from 1 to iColumns
Get_Attribute DF_FIELD_RELATED_FILE of iTable iColumn to iRelTable
If (iRelTable=iParentTable) Begin
Get_Attribute DF_FIELD_RELATED_FIELD of iTable iColumn to iRelColumn
Get TranslateOverlap of oOverlapColumnFunctions iTable iColumn to aColumns
Get TranslateOverlap of oOverlapColumnFunctions iRelTable iRelColumn to aRelColumns
Move (SizeOfArray(aColumns)-1) to iMax
If ((iMax+1)=SizeOfArray(aRelColumns)) Begin
Get_Field_Value iRelColumn aRelColumns[iIndex] to sValue
Send AddFilter (&strQuery) iTable aColumns[iIndex] tqEQ sValue
Move True to bAnythingAtAll
End
Else Begin
Send OnError 745 "Number of segments do not match in AddFilterRelateAsConstant method"
End
End
Loop
If (not(bAnythingAtAll)) Begin // If there were any segments relating we assume that the relation is good. If not we consider it an error of the table definitions.
Send OnError 746 "Main query table does not relate to the parent table passed"
End
End_Procedure
Procedure FilterKeyPadOn tTableQuery ByRef strQuery
Integer iItem
If (strQuery._strControlBlock.bFilterExpressionActive) Begin
Send OnError 708 "Filter expression already active (keypad is already on)"
End
Else Begin
Move (SizeOfArray(strQuery.aFilterExpressions)) to iItem
Move True to strQuery._strControlBlock.bFilterExpressionActive
Move False to strQuery.aFilterExpressions[iItem]._bOpen
End
End_Procedure
Procedure _FilterExpressionAndOr tTQFilterExpression[] ByRef aSegments Integer iKey
Integer iItem
Move (SizeOfArray(aSegments)-1) to iItem
If (iItem>=0 and aSegments[iItem].iAndOr=_tqKeyNULL) Begin
If (aSegments[iItem]._bOpen) Begin
Send _FilterExpressionAndOr (&aSegments[iItem].aSubExpressions) iKey
End
Else Begin
Move iKey to aSegments[iItem].iAndOr
End
End
Else Begin
Send OnError 709 "Logical operator (AND/OR) not allowed"
End
End_Procedure
Procedure _FilterExpressionNot tTQFilterExpression[] ByRef aSegments
Integer iItem
Move (SizeOfArray(aSegments)-1) to iItem
If (iItem>=0) Begin
If (aSegments[iItem]._bOpen) Begin
Send _FilterExpressionNot (&aSegments[iItem].aSubExpressions)
End
Else Begin
If (aSegments[iItem].iAndOr=_tqKeyNULL) Begin
Move True to aSegments[iItem].bNot
End
Else Begin
Send OnError 710 "Logical operator (NOT) not allowed"
End
End
End
Else Begin
Send OnError 711 "Logical operator (NOT) not allowed"
End
End_Procedure
Procedure _FilterExpressionLeftParenthesis tTQFilterExpression[] ByRef aSegments
Integer iItem
Move (SizeOfArray(aSegments)-1) to iItem
If (iItem>=0) Begin
If (aSegments[iItem]._bOpen) Begin
Send _FilterExpressionLeftParenthesis (&aSegments[iItem].aSubExpressions)
End
Else Begin
If (aSegments[iItem].iAndOr=_tqKeyNULL) Begin
Move True to aSegments[iItem]._bOpen
End
Else Begin
Move True to aSegments[iItem+1]._bOpen
End
End
End
Else Begin
Send OnError 713 "'(' not allowed"
End
End_Procedure
Procedure _FilterExpressionRightParenthesis tTQFilterExpression[] ByRef aSegments
Integer iItem
Move (SizeOfArray(aSegments)-1) to iItem
If (iItem>=0) Begin
If (aSegments[iItem]._bOpen) Begin
If (SizeOfArray(aSegments[iItem].aSubExpressions)=0 or not(aSegments[iItem].aSubExpressions[SizeOfArray(aSegments[iItem].aSubExpressions)-1]._bOpen)) Begin
Move False to aSegments[iItem]._bOpen
End
Else Begin
Send _FilterExpressionRightParenthesis (&aSegments[iItem].aSubExpressions)
End
End
Else Begin
Send OnError 714 "')' not allowed"
End
End
Else Begin
Send OnError 715 "Empty sub-expression, ')' not allowed"
End
End_Procedure
Procedure _FilterPressKey tTableQuery ByRef strQuery Integer iKey
Integer iActiveFilterExpression
If (strQuery._strControlBlock.bFilterExpressionActive) Begin
Move (SizeOfArray(strQuery.aFilterExpressions)-1) to iActiveFilterExpression
If (iKey=tqKeyAND or iKey=tqKeyOR) Begin
Send _FilterExpressionAndOr (&strQuery.aFilterExpressions[iActiveFilterExpression].aSubExpressions) iKey
End
Else If (iKey=tqKeyNOT) Begin
Send _FilterExpressionNot (&strQuery.aFilterExpressions[iActiveFilterExpression].aSubExpressions)
End
Else If (iKey=tqKeyPARLEFT or iKey=tqKeyPARRIGHT) Begin
If (iKey=tqKeyPARLEFT) Begin
Send _FilterExpressionLeftParenthesis (&strQuery.aFilterExpressions[iActiveFilterExpression].aSubExpressions)
End
Else If (iKey=tqKeyPARRIGHT) Begin
Send _FilterExpressionRightParenthesis (&strQuery.aFilterExpressions[iActiveFilterExpression].aSubExpressions)
End
End
Else Begin
Send OnError 716 "Unknown 'key' passed to FilterPressKey method"
End
End
Else Begin
Send OnError 717 "No active filter expression"
End
End_Procedure
Procedure FilterPressKey tTableQuery ByRef strQuery Integer iKey
If (strQuery._strControlBlock.bFilterExpressionActive) Begin
Send _FilterPressKey (&strQuery) iKey
End
Else Begin
Send OnError 718 "No active filter expression"
End
End_Procedure
Procedure _FilterExpressionAddOperand tTableQuery ByRef strQuery tTQFilterExpression[] ByRef aFilterSegments tTQFilterSegment strConstrainSegment
Integer iItem
// Boolean bHasSubExp
Move (SizeOfArray(aFilterSegments)-1) to iItem
If (iItem>=0) Begin
If (aFilterSegments[iItem]._bOpen) Begin
Send _FilterExpressionAddOperand (&strQuery) (&aFilterSegments[iItem].aSubExpressions) strConstrainSegment
End
Else Begin
// Move (SizeOfArray(aFilterSegments[iItem].aSubExpressions)>0) to bHasSubExp
If (aFilterSegments[iItem].iAndOr=_tqKeyNULL) Begin // if operator has not been added
Move strConstrainSegment to aFilterSegments[iItem].strOperand
End
Else Begin // Operator has been added, well take the next segment
Move strConstrainSegment to aFilterSegments[iItem+1].strOperand
End
End
End
Else Begin
Move strConstrainSegment to aFilterSegments[0].strOperand
End
End_Procedure
Procedure FilterExpressionAddOperand tTableQuery ByRef strQuery tTQFilterSegment strFilterSegment
Integer iActiveFilterExpression
Move (SizeOfArray(strQuery.aFilterExpressions)-1) to iActiveFilterExpression
If (iActiveFilterExpression>=0) Begin
Send _FilterExpressionAddOperand (&strQuery) (&strQuery.aFilterExpressions[iActiveFilterExpression].aSubExpressions) strFilterSegment
End
Else Begin
Send OnError 719 "No active expression filter"
End
End_Procedure
Procedure FilterEnterOverAndOut tTableQuery ByRef strQuery
If (strQuery._strControlBlock.bFilterExpressionActive) Begin
Move False to strQuery._strControlBlock.bFilterExpressionActive
End
Else Begin
Send OnError 720 "No active filter expression to close"
End
End_Procedure
Function _ValidateQuery tTableQuery strQuery Returns Boolean
// Check that no more than 1 constraints is put on each column (allow for interval checking though)
Integer iConstrainMax iConstrainIndex iComp
Integer iNewLoad iCurrentLoad iColumn
Integer iTable
Integer[][] aaConstrainLoads
Move (SizeOfArray(strQuery.aFilters)-1) to iConstrainMax
For iConstrainIndex from 0 to iConstrainMax
Move strQuery.aFilters[iConstrainIndex].strLeftValue.iTable to iTable
If (iTable>=SizeOfArray(aaConstrainLoads)) Begin
Move 0 to aaConstrainLoads[iTable][0]
End
// Check that no more than 1 constraints is put on each column (allow for interval checking though)
Move strQuery.aFilters[iConstrainIndex].iComp to iComp
If (iComp=tqGT or iComp=tqGE) Move 1 to iNewLoad // 01
Else If (iComp=tqLT or iComp=tqLE) Move 2 to iNewLoad // 10
Else Move 3 to iNewLoad // 11
Move strQuery.aFilters[iConstrainIndex].strLeftValue.iColumn to iColumn
If (iColumn>=SizeOfArray(aaConstrainLoads[iTable])) Begin
Move 0 to iCurrentLoad
End
Else Begin
Move aaConstrainLoads[iTable][iColumn] to iCurrentLoad
End
If (iNewLoad iand iCurrentLoad) Begin
Send OnError 739 (Replace("#","Illegal filter. Column # is over-constrained",ColumnName(Self,strQuery,iTable,iColumn,True)))
Function_Return False
End
Move (iCurrentLoad+iNewLoad) to aaConstrainLoads[iTable][iColumn]
// ToDo: (maybe) Check that tables used for AddFilterRelate are not used as relate tables also.
Loop
Function_Return True
End_Function
Function _IsTableFixed tTableQuery strQ Integer iTable Returns Boolean
Integer iRelateItem
If (iTable=strQ.iTable) Begin
Function_Return False // If it's the main table of the query it is definitely not fixed.
End
Get _FindTableRelation strQ iTable to iRelateItem
If (iRelateItem<>-1) Begin
Function_Return strQ.aTableRelations[iRelateItem].bFixed
End
Send OnError 735 ("Table not related: "+String(iTable))
End_Function
Function _ColumnRatings tTableQuery strQuery Returns Number[]
// Calculate an array of columns that can be used for jump-in /jump-out.
// If a column has a jump-in value or a jump-out value it is rated (apr) 0.5.
// If a column has both it is rated 2.0.
Integer iColumn
Integer iIndex iIndices
Integer iSegment iSegments
Integer iConstraint iConstraints
Integer iTable iComp
Number[] aColumnRatings
tOCTable strTable
Move strQuery.iTable to iTable
Get TableDefinition of oOverlapColumnFunctions iTable to strTable
Move (SizeOfArray(strTable.aIndices)-1) to iIndices
Move (SizeOfArray(strQuery.aFilters)-1) to iConstraints
Move (ResizeArray(aColumnRatings,SizeOfArray(strTable.aColumns))) to aColumnRatings
For iIndex from 1 to iIndices
Move (SizeOfArray(strTable.aIndices[iIndex].aSegments)-1) to iSegments
For iSegment from 0 to iSegments
Move strTable.aIndices[iIndex].aSegments[iSegment].iColumn to iColumn
If (aColumnRatings[iColumn]=0) Begin // To avoid the same column getting added ratings because appearing in multiple indices
For iConstraint from 0 to iConstraints
// Only evaluate filters that:
// 1. (left side) filters on a column in the query main table
// 2. (right side) does not filter on a column from a table that changes on each new main table record
If ( strQuery.aFilters[iConstraint].strLeftValue.iTable=iTable and ;
( strQuery.aFilters[iConstraint].strRightValue.iTable=0 or ;
_IsTableFixed(Self,strQuery,strQuery.aFilters[iConstraint].strRightValue.iTable) )) Begin
Move strQuery.aFilters[iConstraint].iComp to iComp
If (iColumn=strQuery.aFilters[iConstraint].strLeftValue.iColumn) Begin
If (iComp=tqEQ) Begin
Move 2 to aColumnRatings[iColumn]
End
Else If (iComp=tqGE or iComp=tqGT) Begin
Move (aColumnRatings[iColumn]+0.49) to aColumnRatings[iColumn]
End
Else If (iComp=tqLT or iComp=tqLE) Begin
Move (aColumnRatings[iColumn]+0.51) to aColumnRatings[iColumn]
End
// Note: Using the values 0.49 and 0.51 is a trick to allow the consumer
// of these data to determine whether the constraining operator
// was Lx or Gx (or both). It means nothing to the final ranking
// of the indices. They are both weighed by 0.5.
End
End
Loop
End
Loop
Loop
Function_Return aColumnRatings
End_Function
// If this returns true column iColumn is constrained to a fixed value. (If it is, the
// column may be discarded if appearing as a most-significant index segment)
Function _ColumnFixedByEqConstraint tTableQuery strQuery Integer iColumn Returns Boolean
Integer iConstraintIndex iConstraintMax
tTQFilterSegment strConstraint
Move (SizeOfArray(strQuery.aFilters)-1) to iConstraintMax
For iConstraintIndex from 0 to iConstraintMax
Move strQuery.aFilters[iConstraintIndex] to strConstraint
If (strConstraint.iComp=tqEQ and ;
strConstraint.strLeftValue.iTable=strQuery.iTable and ;
strConstraint.strLeftValue.iColumn=iColumn and ;
SizeOfArray(strConstraint.strRightValue.aOrList)<2) Begin
Function_Return True
End
Loop
Function_Return False
End_Function
Function _IndexSegmentAgreement tTQOrderBySegment strOrderSegment Integer iTable tOCIndexSegment[] aIndexSegments Integer iIndexSegment Boolean bBackwards Boolean bEQ Returns Boolean
If (iIndexSegment0) Begin
Move (CopyArray(aIndexSegments,iSkipSegments,iSegments)) to aRemainingSegments
End
Function_Return aRemainingSegments
End_Function
Function _TrimOrderBySegments tTableQuery strQuery tTQOrderBySegment[] aOrderingBySegments Returns tTQOrderBySegment[]
Integer iTable
Integer iSegments iSkipSegments
tTQOrderBySegment[] aRemainingSegments
Move strQuery.iTable to iTable
Move 0 to iSkipSegments
Move (SizeOfArray(aOrderingBySegments)-1) to iSegments
If (iSegments>=0) Begin
While (iSkipSegments<=iSegments and aOrderingBySegments[iSkipSegments].strValueSource.iTable=iTable and _ColumnFixedByEqConstraint(Self,strQuery,aOrderingBySegments[iSkipSegments].strValueSource.iColumn))
Increment iSkipSegments
Loop
Move (CopyArray(aOrderingBySegments,iSkipSegments,iSegments)) to aRemainingSegments
End
Function_Return aRemainingSegments
End_Function
Function _IndexAgreement tTableQuery strQuery Integer iIndex Boolean bFinalDecision Returns Boolean
// Is the index in agreement with the specified output order?
//
// For each segment in the desired output order it must be the case that it is a
// table column based segment and that either:
//
// 1) its value is fixed by a eq-constraint and all segments before it has been
// satisfied the same way (if the column happens to be the same as the
// "next segment" in the index we're testing, then we must disregard that
// segment from then on).
// or
// 2) it is satisfied by the "next segment" in the index we're testing
//
tOCIndex strIndex
tOCIndexSegment[] aIndexSegments
tTQOrderBySegment[] aOrderingSegments
_tTQValueSource strValueSource
Boolean bOrderSegmentOK bIndexSegmentMatch
Boolean bBackwards bEq
Integer iIndexSegment iIndexSegments
Integer iOrderingSegment iOrderingSegments
Integer iConstraint
If (strQuery._strCompiled.nENR=0) Begin // If index is fully specified all indices agree
Function_Return True
End
If (bFinalDecision) Begin
Move strQuery._strCompiled.bBackwards to bBackwards
End
Else Begin
If (iIndex>1000) Begin
Move (iIndex-1000) to iIndex
Move True to bBackwards
End
Else Begin
Move False to bBackwards
End
End
Get IndexDefinition of oOverlapColumnFunctions strQuery.iTable iIndex to strIndex
Get _TrimIndexSegments strQuery strIndex.aSegments to aIndexSegments
Get _TrimOrderBySegments strQuery strQuery.aOrdering to aOrderingSegments
Move (SizeOfArray(aIndexSegments)-1) to iIndexSegments
Move (SizeOfArray(aOrderingSegments)-1) to iOrderingSegments
Move 0 to iIndexSegment
For iOrderingSegment from 0 to iOrderingSegments
Move False to bOrderSegmentOK
Move aOrderingSegments[iOrderingSegment].strValueSource to strValueSource
If (iOrderingSegment<=iIndexSegments and strValueSource.iTable=strQuery.iTable) Begin // If the order-segment is main-table-based
Get _FindConstraintEQ strQuery.aFilters strQuery.iTable strValueSource.iColumn False to iConstraint
Move (iConstraint<>-1) to bEq
Get _IndexSegmentAgreement aOrderingSegments[iOrderingSegment] strQuery.iTable aIndexSegments iIndexSegment bBackwards bEq to bIndexSegmentMatch
If (bIndexSegmentMatch) Begin
Move True to bOrderSegmentOK
Increment iIndexSegment
End
Else Begin
// If the segment is constrained by an EQ we are still in agreement
If (_ColumnFixedByEqConstraint(Self,strQuery,strValueSource.iColumn)) Begin
Move True to bOrderSegmentOK
End
End
End
If (not(bOrderSegmentOK)) Begin
Function_Return False
End
Loop
Function_Return True
End_Function
Function _IndexExpectedNumberRows tTableQuery strQuery Integer iIndex Number[] aColumnRatings _tTQIndexAnalysis strIndexA Returns Number
Boolean bAccept
Integer iTable iColumn
Integer iSegmentIndex iSegmentMax iPushIndex
Number nExpectedNumberRows
tOCIndex strIndex
Number nColumnRating
If (iIndex>=1000) Begin
Move (iIndex-1000) to iIndex
End
Move strQuery.iTable to iTable
Get_Attribute DF_FILE_RECORDS_USED of iTable to nExpectedNumberRows
Get IndexDefinition of oOverlapColumnFunctions iTable iIndex to strIndex
Move (SizeOfArray(strIndex.aSegments)) to iSegmentMax
Move True to bAccept
Move 0 to iSegmentIndex
While (bAccept and iSegmentIndex=1 or nColumnRating=0.49) to bAccept
If (bAccept) Begin
Move (nExpectedNumberRows*0.5) to nExpectedNumberRows
Increment iSegmentIndex
End
Loop
Move iPushIndex to iSegmentIndex
Move True to bAccept
While (bAccept and iSegmentIndex=1 or nColumnRating=0.51) to bAccept
If (bAccept) Begin
Move (nExpectedNumberRows*0.5) to nExpectedNumberRows
Increment iSegmentIndex
End
Loop
Function_Return nExpectedNumberRows
End_Function
Function _IndexRating tTableQuery strQuery Integer iIndex Number[] aColumnRatings _tTQIndexAnalysis strIndexA Returns _tTQIndexRating
Integer iTable
_tTQIndexRating strRating
Move strQuery.iTable to iTable
Move iIndex to strRating.iIndex
Get _IndexExpectedNumberRows strQuery iIndex aColumnRatings strIndexA to strRating.nExpectedNumberRows
If (strRating.nExpectedNumberRows=0) Begin
Move True to strRating.bOutputAgree
End
Else Begin
Get _IndexAgreement strQuery iIndex False to strRating.bOutputAgree
End
If (iIndex>=1000) Begin // find lt by index iIndex-1000
Move (iIndex-1000) to iIndex
Get_Attribute DF_INDEX_LEVELS of iTable iIndex 0 to strRating.iKeyLevels
Get_Attribute DF_INDEX_KEY_LENGTH of iTable iIndex 0 to strRating.iKeyLength
Move (strRating.iKeyLength+1) to strRating.iKeyLength // Always prefer find gt to find lt when all else is equal
End
Else Begin
Get_Attribute DF_INDEX_LEVELS of iTable iIndex 0 to strRating.iKeyLevels
Get_Attribute DF_INDEX_KEY_LENGTH of iTable iIndex 0 to strRating.iKeyLength
End
Function_Return strRating
End_Function
Function _IndexRatings tTableQuery strQuery Returns _tTQIndexRating[]
// Create an array of defined indices on the table and for each index assign a rating
// Sort the array of indices according to rating and pick the one with the highest one.
Integer iItem iMax
Integer iIndex
Integer[] aIndices
Number[] aColumnRatings
tTQTableAnalysis strAnalysis
_tTQIndexRating strRating
_tTQIndexRating[] aIndexRatings
Get TableAnalysis strQuery.iTable to strAnalysis
Get _ColumnRatings strQuery to aColumnRatings
Get TableIndices strQuery.iTable to aIndices // All on-line indices
Move (SizeOfArray(aIndices)-1) to iMax
For iItem from 0 to iMax
Move aIndices[iItem] to iIndex
Get _IndexRating strQuery iIndex aColumnRatings strAnalysis.aIndices[iIndex] to strRating
Move strRating to aIndexRatings[SizeOfArray(aIndexRatings)]
// Also check the reverse index:
Get _IndexRating strQuery (1000+iIndex) aColumnRatings strAnalysis.aIndices[iIndex] to strRating
Move strRating to aIndexRatings[SizeOfArray(aIndexRatings)]
Loop
Function_Return aIndexRatings
End_Function
Function _LowestIndexNumber Integer iTable Returns Integer
Integer iIndex iIndexMax iSegments
Get_Attribute DF_FILE_LAST_INDEX_NUMBER of iTable to iIndexMax
Move 1 to iIndex
While (iIndex<=iIndexMax)
Get_Attribute DF_INDEX_NUMBER_SEGMENTS of iTable iIndex to iSegments
If (iSegments<>0) Begin
Function_Return iIndex
End
Loop
Function_Return 0 // None found
End_Function
Function _CompareIndexRating _tTQIndexRating strRatingLeft _tTQIndexRating strRatingRight Returns Integer
Number nFactorLeft nFactorRight
Number nPrefetchAvoidanceFactor
Get _pnPrefetchAvoidanceFactor to nPrefetchAvoidanceFactor
// This means that we accept an index in agreement with the output order if it is more
// than 10 times less efficient (assuming that nPrefetchAvoidanceFactor is 0.1 (which it is (by default)))
Move (If(strRatingLeft.bOutputAgree,nPrefetchAvoidanceFactor,1)) to nFactorLeft
Move (If(strRatingRight.bOutputAgree,nPrefetchAvoidanceFactor,1)) to nFactorRight
Move (strRatingLeft.nExpectedNumberRows*nFactorLeft) to nFactorLeft
Move (strRatingRight.nExpectedNumberRows*nFactorRight) to nFactorRight
If (nFactorLeft > nFactorRight) Function_Return (GT) // Right is best
If (nFactorLeft < nFactorRight) Function_Return (LT) // Left is best
If (strRatingLeft.iKeyLevels>strRatingRight.iKeyLevels) Function_Return (GT) // Right is best
If (strRatingLeft.iKeyLevelsstrRatingRight.iKeyLength) Function_Return (GT) // Right is best
If (strRatingLeft.iKeyLength=0) Begin
If (strQuery.bForcePrefetch) Begin
// If prefetch is forced anyway, we will not consider prefetch an "extra" cost.
Set _pnPrefetchAvoidanceFactor to 1
End
Else Begin
Set _pnPrefetchAvoidanceFactor to strQuery._strControlBlock.nPrefetchAvoidanceFactor
End
Move (SortArray(aIndexRatings,Self,GET__CompareIndexRating)) to aIndexRatings
Move aIndexRatings[0].iIndex to iIndex
Move aIndexRatings[0].nExpectedNumberRows to nENR
End
Else Begin
Move 0 to iIndex
Move -1 to nENR // No idea
End
If (iIndex>=1000) Begin
Move (iIndex-1000) to iIndex
Move (not(strQuery._strCompiled.bBackwards)) to strQuery._strCompiled.bBackwards // Flip
End
Move iIndex to strQuery._strCompiled.iIndex
Move nENR to strQuery._strCompiled.nENR
Function_Return aIndexRatings
End_Function
Function IndexRatingText tTableQuery strQuery _tTQIndexRating[] aIndexRatings Returns String
Boolean bBackwards
Integer iItem iMax iTable iIndex
Number nRecCount nFactor
String sValue sIndex sTemp
Move strQuery.iTable to iTable
Get_Attribute DF_FILE_RECORDS_USED of iTable to nRecCount
If (strQuery.bForcePrefetch) Begin
Move 1 to nFactor
End
Else Begin
Move strQuery._strControlBlock.nPrefetchAvoidanceFactor to nFactor
End
If (strQuery.iForceIndex=-1) Begin
Move "Index Ratings\n\nRatings are based on expected number of rows (ENR). If the use of an index implies prefetching and sorting data then ENR is multiplied by # (but not shown).\n\nIndex ratings, best to worst (there is # rows in table #):\n" to sValue
Move (Replace("#",sValue,String(1.0/nFactor))) to sValue
Move (Replace("#",sValue,NumberToString(oStringFunctions,nRecCount,0))) to sValue
Move (Replace("#",sValue,TableName(Self,strQuery,iTable))) to sValue
Move (SizeOfArray(aIndexRatings)-1) to iMax
For iItem from 0 to iMax
Move "Index ##: ENR=#, #, Levels/length=#/#" to sIndex
Move aIndexRatings[iItem].iIndex to iIndex
If (iIndex>1000) Begin
Move (iIndex-1000) to iIndex
Move True to bBackwards
End
Else Begin
Move False to bBackwards
End
Move (Replace("#",sIndex,String(iIndex))) to sIndex
If (bBackwards) Begin
Move (Replace("#",sIndex," (backwards)")) to sIndex
End
Else Begin
Move (Replace("#",sIndex,"")) to sIndex
End
Move (Replace("#",sIndex,String(aIndexRatings[iItem].nExpectedNumberRows))) to sIndex
Move (If(aIndexRatings[iItem].bOutputAgree,"Ordering ok","Prefetch needed")) to sTemp
Move (Replace("#",sIndex,sTemp)) to sIndex
Move (Replace("#",sIndex,String(aIndexRatings[iItem].iKeyLevels))) to sIndex
Move (Replace("#",sIndex,String(aIndexRatings[iItem].iKeyLength))) to sIndex
Move (sValue+"\n"+sIndex) to sValue
Loop
End
Else Begin
Move "Indices have not been rated (index was forced)" to sValue
End
Function_Return sValue
End_Function
Procedure OnIndexCalculated tTableQuery strQuery _tTQIndexRating[] aIndexRatings
End_Procedure
Function _FindConstraintEQ tTQFilterSegment[] aFilters Integer iTable Integer iColumn Boolean bOrListOnly Returns Integer
Integer iConstraint iConstraints
Move (SizeOfArray(aFilters)-1) to iConstraints
For iConstraint from 0 to iConstraints
If (iTable=aFilters[iConstraint].strLeftValue.iTable and ;
iColumn=aFilters[iConstraint].strLeftValue.iColumn and aFilters[iConstraint].iComp=tqEQ) Begin
If (not(bOrListOnly) or SizeOfArray(aFilters[iConstraint].strRightValue.aOrList)>0) Begin
Function_Return iConstraint
End
End
Loop
Function_Return -1
End_Function
Procedure _AppendConstraint tTQFilterSegment[] ByRef aConstraint tTQFilterSegment strConstraint
Integer iIndex
Move (SizeOfArray(aConstraint)) to iIndex
Move strConstraint to aConstraint[iIndex]
End_Procedure
Procedure _DeleteConstraint tTQFilterSegment[] ByRef aFilters Integer iConstraint
#IF (FMAC_VERSION<15)
Integer iIndex iMax iCount
tTQFilterSegment[] aResultConstraints
Move 0 to iCount
Move (SizeOfArray(aFilters)-1) to iMax
For iIndex from 0 to iMax
If (iIndex<>iConstraint) Begin // If not the one we delete
Move aFilters[iIndex] to aResultConstraints[iCount]
Increment iCount
End
Loop
Move aResultConstraints to aFilters
#ELSE
Move (RemoveFromArray(aFilters,iConstraint)) to aFilters
#ENDIF
End_Procedure
Procedure _CalculateJumpInOutConditionsStep01 tTableQuery ByRef strQuery Integer ByRef iSegment Integer iSegments tOCIndex strIndex
// 1: As long as we can consequetively provide EQ constraints on the segment, we copy the constraint to
// both the JumpInValues and JumpOutValues (and disregard that constraint from then on)
Integer iTable iIndex iColumn iConstraint
Boolean bCont
Move strQuery.iTable to iTable
Move strQuery._strCompiled.iIndex to iIndex
Move True to bCont
While (bCont and iSegment<=iSegments)
Move strIndex.aSegments[iSegment].iColumn to iColumn
Get _FindConstraintEQ strQuery.aFilters iTable iColumn False to iConstraint
Move (iConstraint<>-1) to bCont
If (bCont) Begin
Send _AppendConstraint (&strQuery._strCompiled.aJumpInValues) strQuery.aFilters[iConstraint]
Send _AppendConstraint (&strQuery._strCompiled.aJumpOutValues) strQuery.aFilters[iConstraint]
Send _DeleteConstraint (&strQuery.aFilters) iConstraint
Increment iSegment
End
Loop
End_Procedure
Function _FindConstraintGEGT tTQFilterSegment[] aFilters Integer iTable Integer iColumn Boolean bAlsoEQ Returns Integer
Integer iConstraint iConstraints iComp
Move (SizeOfArray(aFilters)-1) to iConstraints
For iConstraint from 0 to iConstraints
If (iTable=aFilters[iConstraint].strLeftValue.iTable and iColumn=aFilters[iConstraint].strLeftValue.iColumn) Begin
Move aFilters[iConstraint].iComp to iComp
If (iComp=tqGT or iComp=tqGE or (bAlsoEQ and iComp=tqEQ)) Begin
Function_Return iConstraint
End
End
Loop
Function_Return -1
End_Function
Function _FindConstraintLELT tTQFilterSegment[] aFilters Integer iTable Integer iColumn Boolean bAlsoEQ Returns Integer
Integer iConstraint iConstraints iComp
Move (SizeOfArray(aFilters)-1) to iConstraints
For iConstraint from 0 to iConstraints
If (iTable=aFilters[iConstraint].strLeftValue.iTable and iColumn=aFilters[iConstraint].strLeftValue.iColumn) Begin
Move aFilters[iConstraint].iComp to iComp
If (iComp=tqLT or iComp=tqLE or (bAlsoEQ and iComp=tqEQ)) Begin
Function_Return iConstraint
End
End
Loop
Function_Return -1
End_Function
Procedure _CalculateJumpInOutConditionsStep02 tTableQuery ByRef strQuery Integer ByRef iJumpInSegment Integer ByRef iJumpOutSegment Integer iSegments tOCIndex strIndex
// 2. If we can then provide a LT,LE,GE,GT type constraint (for JumpInValues and JumpOutValues
// indepentdantly) we move the constraint to JumpInValues/JumpOutValues (and disregard that
// constraint from then on *unless* it's a jump-in constraint and the comperator is GT (or LT
// if it's a descending index segment))
Integer iTable iIndex iColumn iConstraint
Boolean bCont
Move strQuery.iTable to iTable
Move strQuery._strCompiled.iIndex to iIndex
// Jump in
If (iJumpInSegment<=iSegments) Begin
Move strIndex.aSegments[iJumpInSegment].iColumn to iColumn
If (strIndex.aSegments[iJumpInSegment].bDescending) Begin
Get _FindConstraintLELT strQuery.aFilters iTable iColumn False to iConstraint
If (iConstraint<>-1) Begin
Send _AppendConstraint (&strQuery._strCompiled.aJumpInValues) strQuery.aFilters[iConstraint]
If (strQuery.aFilters[iConstraint].iComp=tqLE) Begin
Send _DeleteConstraint (&strQuery.aFilters) iConstraint
End
Increment iJumpInSegment
End
Else Begin
Move -1 to iJumpInSegment
End
End
Else Begin
Get _FindConstraintGEGT strQuery.aFilters iTable iColumn False to iConstraint
If (iConstraint<>-1) Begin
Send _AppendConstraint (&strQuery._strCompiled.aJumpInValues) strQuery.aFilters[iConstraint]
If (strQuery.aFilters[iConstraint].iComp=tqGE) Begin
Send _DeleteConstraint (&strQuery.aFilters) iConstraint
End
Increment iJumpInSegment
End
Else Begin
Move -1 to iJumpInSegment
End
End
End
// Jump out
If (iJumpOutSegment<=iSegments) Begin
Move strIndex.aSegments[iJumpOutSegment].iColumn to iColumn
If (strIndex.aSegments[iJumpOutSegment].bDescending) Begin
Get _FindConstraintGEGT strQuery.aFilters iTable iColumn False to iConstraint
If (iConstraint<>-1) Begin
Send _AppendConstraint (&strQuery._strCompiled.aJumpOutValues) strQuery.aFilters[iConstraint]
Send _DeleteConstraint (&strQuery.aFilters) iConstraint
Increment iJumpOutSegment
End
Else Begin
Move -1 to iJumpOutSegment
End
End
Else Begin
Get _FindConstraintLELT strQuery.aFilters iTable iColumn False to iConstraint
If (iConstraint<>-1) Begin
Send _AppendConstraint (&strQuery._strCompiled.aJumpOutValues) strQuery.aFilters[iConstraint]
Send _DeleteConstraint (&strQuery.aFilters) iConstraint
Increment iJumpOutSegment
End
Else Begin
Move -1 to iJumpOutSegment
End
End
End
End_Procedure
Procedure _CalculateJumpInOutConditions tTableQuery ByRef strQuery
Integer iSegment iSegments iTable iIndex iTempIndex
Integer iJumpInSegment iJumpOutSegment
Boolean bBackwards
tTQFilterSegment[] aBackupConstraints
tOCIndex strIndex
Move strQuery.iTable to iTable
Move strQuery._strCompiled.iIndex to iIndex
Move strQuery._strCompiled.bBackwards to bBackwards
Move strQuery.aFilters to aBackupConstraints
// If the scan index is not compatible with the output index we are forced to
// prefetch the record set in order to sort it before we "output" it:
If (not(_IndexAgreement(Self,strQuery,iIndex,True))) Begin
// If the index is the exact uppersite we still consider the index in agreement.
// Therefore we do not pass the information that the index is scanned backwards to
// the _IndexAgreement function.
Move True to strQuery._strCompiled.bPrefetch
Move strQuery._strCompiled._iLimit to strQuery._strCompiled._iPushLimit
If (strQuery._strCompiled.bBackwards) Begin // set to the opposite of what was tested above _IndexAgreement call
Move strQuery._strCompiled.iIndex to iTempIndex
End
Else Begin
Move (strQuery._strCompiled.iIndex+1000) to iTempIndex
End
If (strQuery.iForceIndex=-1 and strQuery.iLimit<>0 and _IndexAgreement(Self,strQuery,iTempIndex,False)) Begin
// If tail and negated index is ok then we do _not_ reset the limit.
End
Else Begin
Move 0 to strQuery._strCompiled._iLimit
End
End
Else Begin
Move 0 to strQuery._strCompiled._iPushLimit
End
// Initialize:
Move (ResizeArray(strQuery._strCompiled.aJumpInValues,0)) to strQuery._strCompiled.aJumpInValues
Move (ResizeArray(strQuery._strCompiled.aJumpOutValues,0)) to strQuery._strCompiled.aJumpOutValues
Move (ResizeArray(strQuery._strCompiled.aOtherConditions,0)) to strQuery._strCompiled.aOtherConditions
Get IndexDefinition of oOverlapColumnFunctions strQuery.iTable iIndex to strIndex
Move (SizeOfArray(strIndex.aSegments)-1) to iSegments
Move 0 to iSegment // Used to be 1
// Calculating the JumpInValues:
// Starting with the most significant segment we go through the segments of the iScanIndex
// 1. As long as we can consequetively provide EQ constraints on the segment, we copy the
// constraint to both the JumpInValues and JumpOutValues (and disregard that constraint
// from then on)
Send _CalculateJumpInOutConditionsStep01 (&strQuery) (&iSegment) iSegments strIndex
// 2. If we can then provide a LT,LE,GE,GT type constraint (for JumpInValues and JumpOutValues
// respectively) we move the constraint to JumpInValues/JumpOutValues (and disregard that
// constraint from then on)
Move iSegment to iJumpInSegment
Move iSegment to iJumpOutSegment
Send _CalculateJumpInOutConditionsStep02 (&strQuery) (&iJumpInSegment) (&iJumpOutSegment) iSegments strIndex
// 3. The constraints that are then left are copied to the aOtherConstraints array
Move strQuery.aFilters to strQuery._strCompiled.aOtherConditions
Move aBackupConstraints to strQuery.aFilters
End_Procedure
Procedure _IncrementJumpInOutCondition _tTQValueSource ByRef stValue Integer ByRef bIncrement Integer ByRef bCarry
Integer iMax
Move (SizeOfArray(stValue.aOrList)-1) to iMax
If (iMax>=0) Begin // If it's an or-list
If (bIncrement or bCarry) Begin
Increment stValue._iOrListPointer
If (stValue._iOrListPointer>iMax) Begin
Move True to bCarry
Move 0 to stValue._iOrListPointer
End
Else Begin
Move False to bCarry
End
Move False to bIncrement
End
End
End_Procedure
Procedure _ResetOrListPointer _tTQValueSource ByRef stValue
If (SizeOfArray(stValue.aOrList)>0) Begin
Move 0 to stValue._iOrListPointer
End
End_Procedure
Function _IncrementJumpInOutConditions tTableQuery ByRef strQuery Integer iJumpOutTriggerIndex Returns Boolean
// This function increments jump-in/out pointers and returns false if pointers could not
// be incremented. The pointers are into arrays of or-list values
// Parameter iJumpOutTriggerIndex points to the constrain segment that triggered the jump-out
// that in turn triggered the call of this function.
// The or-list pointers to the left of (and including) iJumpOutTriggerIndex must be incremented
// while the or-list pointers to the right must be reset.
Integer iItem iMax iIndex
Boolean bCarry bIncrement bSuccess bReturnFalse
Move (SizeOfArray(strQuery._strCompiled.aJumpOutValues)-1) to iMax
Move False to bReturnFalse
If (iJumpOutTriggerIndex>=0) Begin
// Increment pointers to the left
Move False to bCarry
Move True to bIncrement
If (SizeOfArray(strQuery._strCompiled.aJumpOutValues)>0) Begin // Only of there are any jumpout values to increment upon
For iItem from 0 to iJumpOutTriggerIndex
Move (iJumpOutTriggerIndex-iItem) to iIndex // Go throught it backwards (least significant first)
Send _IncrementJumpInOutCondition (&strQuery._strCompiled.aJumpOutValues[iIndex].strRightValue) (&bIncrement) (&bCarry)
Loop
End
Move (not(bCarry) and not(bIncrement)) to bSuccess
End
Else Begin
Move True to bSuccess
Move True to bReturnFalse
End
// Reset the or-lists to the lessor significant side of iJumpOutTriggerIndex
If (bSuccess) Begin
For iItem from (iJumpOutTriggerIndex+1) to iMax
Send _ResetOrListPointer (&strQuery._strCompiled.aJumpOutValues[iItem].strRightValue)
Loop
End
If (bReturnFalse) Begin
// The point is that no-way will we return true if nothing was actually incremented
Function_Return False
End
Function_Return bSuccess
End_Function
Procedure _SortVariantArray String[] ByRef aValues Integer iType Boolean bReverseArray
Integer iMax iItem
String[] aStringValues
Number[] aNumberValues
Date[] aDateValues
DateTime[] aDateTimeValues
Move (SizeOfArray(aValues)-1) to iMax
If (iType=tqSTRING or iType=tqTEXT) Begin
Move (SortArray(aValues)) to aValues
End
Else If (iType=tqNUMBER) Begin
For iItem from 0 to iMax
Move aValues[iItem] to aNumberValues[iItem]
Loop
Move (SortArray(aNumberValues)) to aNumberValues
For iItem from 0 to iMax
Move aNumberValues[iItem] to aValues[iItem]
Loop
End
Else If (iType=tqDATE) Begin
For iItem from 0 to iMax
Move aValues[iItem] to aDateValues[iItem]
Loop
Move (SortArray(aDateValues)) to aDateValues
For iItem from 0 to iMax
Move aDateValues[iItem] to aValues[iItem]
Loop
End
Else If (iType=tqDATETIME) Begin
For iItem from 0 to iMax
Move aValues[iItem] to aDateTimeValues[iItem]
Loop
Move (SortArray(aDateTimeValues)) to aDateTimeValues
For iItem from 0 to iMax
Move aDateTimeValues[iItem] to aValues[iItem]
Loop
End
If (bReverseArray) Begin
Move (ReverseArray(aValues)) to aValues
End
End_Procedure
Function _SortSegmentReversed tTableQuery strQ _tTQValueSource strLeftValue Returns Boolean
Integer iItem iMax
Move (SizeOfArray(strQ.aOrdering)-1) to iMax
For iItem from 0 to iMax
If (strQ.aOrdering[iItem].strValueSource.iTable=strQ.iTable and ;
strQ.iTable=strLeftValue.iTable and ;
strQ.aOrdering[iItem].strValueSource.iColumn=strLeftValue.iColumn) Begin
Function_Return strQ.aOrdering[iItem].bDescending
End
Loop
End_Function
Procedure _SortOrListValues tTableQuery ByRef strQ
// Sorting the OrList values serves two purposes:
// 1: The rows will be found in the order specified by the scan index
// 2: The mechanism that determines which OrList to "increment" will be confused
// if the OrList values do not appear in sequence (it's the break level
// computation that will go wrong)
Boolean bReverseArray
Integer iItem iMax
Move (SizeOfArray(strQ._strCompiled.aJumpInValues)-1) to iMax
For iItem from 0 to iMax
// Should we sort backwards?
Get _SortSegmentReversed strQ strQ._strCompiled.aJumpInValues[iItem].strLeftValue to bReverseArray
Send _SortVariantArray (&strQ._strCompiled.aJumpInValues[iItem].strRightValue.aOrList) strQ._strCompiled.aJumpInValues[iItem].strLeftValue.iType bReverseArray //(Integer(bReverseArray)+Integer(strQ._strCompiled.bBackwards)=1)
Loop
Move (SizeOfArray(strQ._strCompiled.aJumpOutValues)-1) to iMax
For iItem from 0 to iMax
// Should we sort backwards?
Get _SortSegmentReversed strQ strQ._strCompiled.aJumpOutValues[iItem].strLeftValue to bReverseArray
Send _SortVariantArray (&strQ._strCompiled.aJumpOutValues[iItem].strRightValue.aOrList) strQ._strCompiled.aJumpOutValues[iItem].strLeftValue.iType bReverseArray //(Integer(bReverseArray)+Integer(strQ._strCompiled.bBackwards)=1)
Loop
End_Procedure
Function _CanFindIndexInRatings _tTQIndexRating[] aIndexRatings Integer iIndex Boolean ByRef bBackwards Number ByRef nENR Returns Boolean
Integer iItem iMax
Move (SizeOfArray(aIndexRatings)-1) to iMax
For iItem from 0 to iMax
If (iIndex=aIndexRatings[iItem].iIndex or (iIndex+1000)=aIndexRatings[iItem].iIndex) Begin
Move (aIndexRatings[iItem].iIndex>1000) to bBackwards
Move aIndexRatings[iItem].nExpectedNumberRows to nENR
Function_Return True
End
Loop
Function_Return False
End_Function
Function SQLSelectStatement tTableQuery strQuery Returns String
Handle hoSQLGenerator
String sStatement
Send _TableRelateStatesReset (&strQuery)
If (strQuery.sForceSQLStatement<>"") Begin
Function_Return strQuery.sForceSQLStatement
End
Get phoSQLGeneratorMSSQLDRC to hoSQLGenerator
Get _SQLSelectStatement of hoSQLGenerator strQuery Self to sStatement
Function_Return sStatement
End_Function
Function SQLDeleteStatement tTableQuery strQuery Returns String
Handle hoSQLGenerator
String sStatement
Send _TableRelateStatesReset (&strQuery)
Get phoSQLGeneratorMSSQLDRC to hoSQLGenerator
Get _SQLDeleteStatement of hoSQLGenerator strQuery Self to sStatement
Function_Return sStatement
End_Function
// Used by TableQueryStructureFunctions.pkg
Procedure _ReadParentSortValues tTableQuery ByRef strParentQuery tTableQuery ByRef strChildQuery
Integer iItem iItemMax
String sValue
String[] aValues
Move (SizeOfArray(strParentQuery.aOrdering)-1) to iItemMax
For iItem from 0 to iItemMax
Get ResultColumnValue (&strParentQuery) strParentQuery.aOrdering[iItem].strValueSource.iTable strParentQuery.aOrdering[iItem].strValueSource.iColumn to sValue
If (strParentQuery.aOrdering[iItem].bUppercase) Begin
Move (Uppercase(sValue)) to sValue
End
Move sValue to aValues[iItem]
Loop
Move aValues to strChildQuery._strControlBlock.strInnerJoinCB.aCurrentParentSortValues
End_Procedure
// Global_Variable Integer _giSQLHandleCheckIndent
// Move 0 to _giSQLHandleCheckIndent
// Procedure ShowInit tSQLStatementHandle strStatement Integer iTable
// Showln (Repeat(" ",_giSQLHandleCheckIndent)) "Initialize using connection " (String(strStatement.strConnectionHandle.hHandle)) ". Statement handle is: " (String(strStatement.hHandle)) " (Table " (String(iTable)) ")"
// Increment _giSQLHandleCheckIndent
// End_Procedure
// Procedure ShowDeInit tSQLStatementHandle strStatement Integer iTable
// Decrement _giSQLHandleCheckIndent
// Showln (Repeat(" ",_giSQLHandleCheckIndent)) "Deinitialize using connection " (String(strStatement.strConnectionHandle.hHandle)) ". Statement handle is: " (String(strStatement.hHandle)) " (Table " (String(iTable)) ")"
// End_Procedure
Function _InitializeQuery tTableQuery ByRef strQuery Returns Boolean
Integer iTable iPrefetchCount iPrefetchTimeout
Integer iDecimalSeparator iDriverIndex
String sDriver sStatement
tSQLStatementHandle strStatement
If (not(strQuery._strControlBlock.bInitialized)) Begin
Move True to strQuery._strControlBlock.bInitialized
If (strQuery.bRestoreRecordBuffersOnQueryCompletion) Begin
Send _PushNoneFixedBuffers strQuery
End
Move strQuery.iTable to iTable
Get_Attribute DF_FILE_DRIVER of iTable to sDriver
If (strQuery._strControlBlock.iQueryMethod=tqSQLPKG) Begin
If (not(strQuery._strControlBlock.strInnerJoinCB.bEnabled) or not(strQuery._strControlBlock.strInnerJoinCB.bInitialized)) Begin
If (OpenStatementOnTable(oSQLStatementHandles,strQuery.iTable,&strStatement)) Begin
Move strStatement to strQuery._strControlBlock.strSQLStatementHandle
Send _TableRelateStatesReset (&strQuery)
Get SQLSelectStatement strQuery Self to sStatement
Move (ToANSI(sStatement)) to sStatement
Send SQLExecDirect of strStatement.hHandle sStatement
Send SQLFetchActivatesBuffer of strStatement.hHandle strQuery.iTable True // True: DF_FILE_ACTIVATE will be true
If (strQuery._strControlBlock.strInnerJoinCB.bEnabled) Begin
Move True to strQuery._strControlBlock.strInnerJoinCB.bInitialized
Move False to strQuery._strControlBlock.strInnerJoinCB.bSkipNextFetch
Move False to strQuery._strControlBlock.strInnerJoinCB.bEndOfResultSet
End
// Send ShowInit strStatement strQuery.iTable
End
End
End
Else Begin
If (sDriver<>"DATAFLEX") Begin // We're on an SQL backend
// This means that we have been 'forced' to use the record oriented
// interface but we are in fact an SQL table and therefore we might
// might want to tamper with the prefetch cache settings:
If (strQuery._strControlBlock.strSQLCacheControl.bUseSettings) Begin
Move strQuery._strControlBlock.strSQLCacheControl.iPrefetchRecordCount to iPrefetchCount
Move strQuery._strControlBlock.strSQLCacheControl.iPrefetchRecordTimeout to iPrefetchTimeout
If (iPrefetchCount<>0) Begin
Get_Attribute DF_FILE_BLOCK_SIZE of iTable to strQuery._strControlBlock._strPreviousSQLCacheControl.iPrefetchRecordCount
Set_Attribute DF_FILE_BLOCK_SIZE of iTable to iPrefetchCount
End
If (iPrefetchTimeout<>0) Begin
Get TableCacheTimeout of oTableDriverFunctions iTable to strQuery._strControlBlock._strPreviousSQLCacheControl.iPrefetchRecordTimeout
Set TableCacheTimeout of oTableDriverFunctions iTable to iPrefetchTimeout
End
End
End
End
End
Function_Return (strQuery._strControlBlock.bInitialized)
End_Function
Procedure _DeinitializeQuery tTableQuery ByRef strQuery
Integer iTable iPrefetchCount iPrefetchTimeout
String sDriver
If (strQuery._strControlBlock.iQueryMethod=tqSQLPKG) Begin
If (not(strQuery._strControlBlock.strInnerJoinCB.bInitialized)) Begin // inner join => don't close the statement
// Send ShowDeInit strQuery._strControlBlock.strSQLStatementHandle strQuery.iTable
Send CloseStatement of oSQLStatementHandles strQuery._strControlBlock.strSQLStatementHandle
End
End
Else Begin
Move strQuery.iTable to iTable
Get_Attribute DF_FILE_DRIVER of iTable to sDriver
If (sDriver<>"DATAFLEX") Begin // We're on an SQL backend
// This means that we have been 'forced' to use the record oriented
// interface but we are in fact an SQL table and therefore we want
// to restore the prefetch cache settings (if they were originally set):
If (strQuery._strControlBlock.strSQLCacheControl.bUseSettings) Begin
Move strQuery._strControlBlock.strSQLCacheControl.iPrefetchRecordCount to iPrefetchCount
Move strQuery._strControlBlock.strSQLCacheControl.iPrefetchRecordTimeout to iPrefetchTimeout
If (iPrefetchCount<>0) Begin // Only then did we change it in the first place
// Reset prefetch setting
Set_Attribute DF_FILE_BLOCK_SIZE of iTable to strQuery._strControlBlock._strPreviousSQLCacheControl.iPrefetchRecordCount
End
If (iPrefetchTimeout<>0) Begin // Only then did we change it in the first place
// Restore cache time out setting
Set TableCacheTimeout of oTableDriverFunctions iTable to strQuery._strControlBlock._strPreviousSQLCacheControl.iPrefetchRecordTimeout
End
End
End
End
End_Procedure
Function _QueryMethodSQL tTableQuery strQuery Returns Boolean
String sDriver
Integer iQueryMethod
Move tqDATAFLEX to iQueryMethod
If (not(strQuery.bNoESQL)) Begin
Get_Attribute DF_FILE_DRIVER of strQuery.iTable to sDriver
If (sDriver="MSSQLDRV") Begin
Move tqSQLPKG to iQueryMethod
End
End
Function_Return (iQueryMethod<>tqDATAFLEX)
End_Function
Function _CompileQuery tTableQuery ByRef strQuery Returns Boolean
Boolean bBackwards
Boolean[] aSortDescending
Number nENR
Number[] aColumnRatings
String sDriver sFormat
_tTQIndexRating strRating
_tTQIndexRating[] aIndexRatings
tTQFilterSegment[] aTemp
If (not(strQuery._strControlBlock.bCompiled)) Begin
If (_ValidateQuery(Self,strQuery)) Begin
// Determine query method:
Move tqDATAFLEX to strQuery._strControlBlock.iQueryMethod
If (not(strQuery.bNoESQL)) Begin
Get_Attribute DF_FILE_DRIVER of strQuery.iTable to sDriver
If (sDriver="MSSQLDRV") Begin
Move tqSQLPKG to strQuery._strControlBlock.iQueryMethod
Get_Attribute DF_FILE_TABLE_CHARACTER_FORMAT of strQuery.iTable to sFormat
Move (sFormat<>"OEM") to strQuery._strControlBlock.bSQLTableIsAnsi
End
End
If (strQuery._strControlBlock.iQueryMethod=tqDATAFLEX) Begin
Move False to strQuery._strCompiled.bBackwards
Move strQuery.bForcePrefetch to strQuery._strCompiled.bPrefetch
Get _CalculateIndex (&strQuery) to aIndexRatings
If (strQuery.iForceIndex<>-1) Begin
If (_CanFindIndexInRatings(Self,aIndexRatings,strQuery.iForceIndex,&bBackwards,&nENR)) Begin
Move strQuery.iForceIndex to strQuery._strCompiled.iIndex
Move bBackwards to strQuery._strCompiled.bBackwards
Move nENR to strQuery._strCompiled.nENR
End
Else Begin
Send OnError 721 "Illegal index forced"
End
End
If (strQuery.iLimit>=0) Begin
Move strQuery.iLimit to strQuery._strCompiled._iLimit
End
Else Begin
// If limit is -3 for example, we only want the 3 last rows of the
// set. In this case we instruct TableQuery to reverse the ordering
// and return the first three. Hokus pokus. (and then of course they
// will be occurring in the wrong order). ((deal with that later))
Move (-strQuery.iLimit) to strQuery._strCompiled._iLimit
Move (not(strQuery._strCompiled.bBackwards)) to strQuery._strCompiled.bBackwards // reverse index search
End
Send OnIndexCalculated strQuery aIndexRatings
Send _CalculateJumpInOutConditions (&strQuery)
Send _SortOrListValues (&strQuery) // Make sure that values occur in the sequence they would be if occurring in an index
If (strQuery._strCompiled.bBackwards) Begin // If backwards, we swap jump-in and jump-out conditions
Move strQuery._strCompiled.aJumpInValues to aTemp
Move strQuery._strCompiled.aJumpOutValues to strQuery._strCompiled.aJumpInValues
Move aTemp to strQuery._strCompiled.aJumpOutValues
End
End
Move True to strQuery._strControlBlock.bCompiled
End
Move False to strQuery._strControlBlock.bInitialized
End
Function_Return (strQuery._strControlBlock.bCompiled)
End_Function
//> This will analyse the query and calculate a query strategy. The strategy may be
//> inspected by inspecting the values of strQuery members
Function CompileQuery tTableQuery ByRef strQuery Returns Boolean
Move False to strQuery._strControlBlock.bCompiled
Function_Return (_CompileQuery(Self,&strQuery))
End_Function
Function _ValueSourceValue tTableQuery ByRef strQuery _tTQValueSource strValue Returns String
Integer iColumn
String sValue
If (strValue.iTable<>0) Begin
If (strValue.iTable<>strQuery.iTable) Begin // Only called when dataflex handling
Send _RelateToTable (&strQuery) strValue.iTable
End
Move strValue.iColumn to iColumn
If (iColumn=0) Begin
Move (SerializeRowID(GetRowID(strValue.iTable))) to sValue
End
Else Begin
Get_Field_Value strValue.iTable iColumn to sValue
Move (Rtrim(sValue)) to sValue
End
End
Else If (strValue.hFunction<>0) Begin
Get strValue.hFunction of strValue.hObject strValue.iColumn to sValue
End
Else If (SizeOfArray(strValue.aOrList)>0) Begin
Move strValue.aOrList[strValue._iOrListPointer] to sValue
End
Else Begin
Move strValue.sConstant to sValue
End
If (strValue.iType=tqNUMBER and sValue="") Begin
Move "0" to sValue
End
Function_Return sValue
End_Function
Function _ValueSourceScriptValueOrListFormat String[] aOrList Integer iType Returns String
Integer iItem iMax
Boolean bOk
String sValue
If (iType=tqSTRING or iType=tqTEXT) Begin
Move (SizeOfArray(aOrList)-1) to iMax
Move True to bOk
For iItem from 0 to iMax
If (bOk) Begin
Get AddQuotes of oStringFunctions (&aOrList[iItem]) "'" to bOk
End
Loop
If (bOk) Begin
Get JoinString aOrList "," 0 0 to sValue
Move ("("+sValue+")") to sValue
End
Else Begin
Move "" to sValue // Signals an error
End
End
Else Begin
Get JoinString aOrList "," 0 0 to sValue
Move ("("+sValue+")") to sValue
End
Function_Return sValue
End_Function
Function _ValueSourceDisplayValue tTableQuery ByRef strQ _tTQValueSource strValue Returns String // Called by TQ test facility in VDFxray
Integer iMax iItem
String sValue
Move (SizeOfArray(strValue.aOrList)-1) to iMax
If (iMax>=0) Begin
For iItem from 0 to iMax
Move (sValue+strValue.aOrList[iItem]) to sValue
If (iItem<>iMax) Begin
Move (sValue+" ; ") to sValue
End
Loop
End
Else Begin
Get _ValueSourceValue (&strQ) strValue to sValue
End
Function_Return sValue
End_Function
Procedure _AssignConstraintValues tTableQuery ByRef strQuery tTQFilterSegment[] ByRef aFilters
Integer iMax iIndex
Move (SizeOfArray(aFilters)-1) to iMax
For iIndex from 0 to iMax
// If it's a contraint by a table column value and if the table is not fixed we do not bother to assign
// initial values. Instead we "calculate" them on each record.
If (aFilters[iIndex].strRightValue.iTable=0 or _IsTableFixed(Self,strQuery,aFilters[iIndex].strRightValue.iTable)) Begin
Get _ValueSourceValue (&strQuery) aFilters[iIndex].strRightValue to aFilters[iIndex].strRightValue._sValue
If (aFilters[iIndex].iComp=tqMATCH) Begin
Get WildCardEvalSequence of oStringFunctions aFilters[iIndex].strRightValue._sValue to aFilters[iIndex].strMatchEvalSequence
End
If (aFilters[iIndex].iComp=tqUCMATCH) Begin
Get WildCardEvalSequence of oStringFunctions (Uppercase(aFilters[iIndex].strRightValue._sValue)) to aFilters[iIndex].strMatchEvalSequence
End
End
Loop
End_Procedure
Procedure _AssignFilterExpressionValue tTableQuery ByRef strQuery tTQFilterExpression ByRef strFilterExpression
Integer iItem iItemMax
Get _ValueSourceValue (&strQuery) strFilterExpression.strOperand.strRightValue to strFilterExpression.strOperand.strRightValue._sValue
If (strFilterExpression.strOperand.iComp=tqMATCH) Begin
Get WildCardEvalSequence of oStringFunctions strFilterExpression.strOperand.strRightValue._sValue to strFilterExpression.strOperand.strMatchEvalSequence
End
If (strFilterExpression.strOperand.iComp=tqUCMATCH) Begin
Get WildCardEvalSequence of oStringFunctions (Uppercase(strFilterExpression.strOperand.strRightValue._sValue)) to strFilterExpression.strOperand.strMatchEvalSequence
End
Move (SizeOfArray(strFilterExpression.aSubExpressions)-1) to iItemMax
For iItem from 0 to iItemMax
Send _AssignFilterExpressionValue (&strQuery) (&strFilterExpression.aSubExpressions[iItem])
Loop
End_Procedure
Procedure _AssignFilterExpressionValues tTableQuery ByRef strQuery
Integer iItem iItemMax
tTQFilterExpression strFilterExpression
Move (SizeOfArray(strQuery.aFilterExpressions)-1) to iItemMax
For iItem from 0 to iItemMax
Send _AssignFilterExpressionValue (&strQuery) (&strQuery.aFilterExpressions[iItem])
Loop
End_Procedure
Procedure _ReassignJumpInOutConstraintValues tTableQuery ByRef strQuery tTQFilterSegment[] ByRef aJumpInConstraints tTQFilterSegment[] ByRef aJumpOutConstraints
Integer iTable iColumn iJumpOutItem
_tTQValueSource stSource
String sValue
Integer iItem iMax
Move (SizeOfArray(aJumpOutConstraints)-1) to iMax
For iItem from 0 to iMax
If (SizeOfArray(aJumpOutConstraints[iItem].strRightValue.aOrList)>0) Begin
Move aJumpOutConstraints[iItem].strLeftValue.iTable to iTable
Move aJumpOutConstraints[iItem].strLeftValue.iColumn to iColumn
Move aJumpOutConstraints[iItem].strRightValue to stSource
Get _ValueSourceValue (&strQuery) stSource to sValue
Move sValue to aJumpOutConstraints[iItem].strRightValue._sValue
Get _FindConstraintEQ aJumpInConstraints iTable iColumn True to iJumpOutItem
If (iJumpOutItem>-1) Begin
Move sValue to aJumpInConstraints[iJumpOutItem].strRightValue._sValue
End
Else Begin
Send OnError 722 "Unmatched jumpin or-list (should have been matched in jump-out constraints)"
End
End
Loop
End_Procedure
Function _SharpenJumpInValue String sValue Integer iTable Integer iColumn Boolean bDescending Returns String
// NOTE: If the function does not succeed in sharpening the value, no harm
// is done (other than some potentially unnescessary finds).
Integer iType iLen iDecs
Number nValue
Date dValue
DateTime dtValue
Get_Attribute DF_FIELD_TYPE of iTable iColumn to iType
Get_Attribute DF_FIELD_LENGTH of iTable iColumn to iLen
Get_Attribute DF_FIELD_PRECISION of iTable iColumn to iDecs
If (iType=DF_BCD) Begin
Move sValue to nValue
// OBS! Here we need a check that we do not in- or decrement the value out of range
If (bDescending) Begin
If (iDecs=0) Move (nValue-1) to nValue
Else If (iDecs=2) Move (nValue-0.01) to nValue
Else If (iDecs=4) Move (nValue-0.0001) to nValue
Else If (iDecs=6) Move (nValue-0.000001) to nValue
Else If (iDecs=8) Move (nValue-0.00000001) to nValue
End
Else Begin
If (iDecs=0) Move (nValue+1) to nValue
Else If (iDecs=2) Move (nValue+0.01) to nValue
Else If (iDecs=4) Move (nValue+0.0001) to nValue
Else If (iDecs=6) Move (nValue+0.000001) to nValue
Else If (iDecs=8) Move (nValue+0.00000001) to nValue
End
Move nValue to sValue
End
Else If (iType=DF_DATE) Begin
Move sValue to dValue
If (bDescending) Begin
If (Integer(dValue>0)) Begin
Get DateIncrement of oDateFunctions dValue DS_DAY -1 to dValue
End
End
Else Begin
If (dValue0 or bBackwards) Begin // Don't seed a negative recnum !!!!
Send SeedMinValue of oIndexFunctions strQ.iTable strIndex.aSegments[iIndex].iColumn (bBackwards)
End
Loop
End_Procedure
Function _TestNumber Number nValueLeft Integer iComp Number nValueRight Returns Boolean
If (iComp=tqEQ) Function_Return (nValueLeft = nValueRight)
If (iComp=tqLT) Function_Return (nValueLeft < nValueRight)
If (iComp=tqLE) Function_Return (nValueLeft <= nValueRight)
If (iComp=tqGE) Function_Return (nValueLeft >= nValueRight)
If (iComp=tqGT) Function_Return (nValueLeft > nValueRight)
If (iComp=tqNE) Function_Return (nValueLeft <> nValueRight)
Function_Return False
End_Function
Function _TestString String sValueLeft Integer iComp tWildCardEvalSequence strMatchSequence String sValueRight Returns Boolean
If (iComp=tqEQ) Function_Return (sValueLeft = sValueRight)
If (iComp=tqLT) Function_Return (sValueLeft < sValueRight)
If (iComp=tqLE) Function_Return (sValueLeft <= sValueRight)
If (iComp=tqGE) Function_Return (sValueLeft >= sValueRight)
If (iComp=tqGT) Function_Return (sValueLeft > sValueRight)
If (iComp=tqNE) Function_Return (sValueLeft <> sValueRight)
// If (iComp=tqCONTAINS) Function_Return (sValueLeft contains sValueRight)
// If (iComp=tqIN) Begin
// If sValueLeft in sValueRight Begin
// Function_Return True
// End
// End
If (iComp=tqMATCH) Begin
Function_Return (WildCardMatch(oStringFunctions,sValueLeft,strMatchSequence))
End
If (iComp=tqUCMATCH) Begin
Function_Return (WildCardMatch(oStringFunctions,Uppercase(sValueLeft),strMatchSequence))
End
Function_Return False
End_Function
Function _TestText String sValueLeft Integer iComp tWildCardEvalSequence stMatchSequence String sValueRight Returns Boolean
Function_Return (_TestString(Self,sValueLeft,iComp,stMatchSequence,sValueRight))
End_Function
Function _TestDate Date dValueLeft Integer iComp Date dValueRight Returns Boolean
If (iComp=tqEQ) Function_Return (dValueLeft = dValueRight)
If (iComp=tqLT) Function_Return (dValueLeft < dValueRight)
If (iComp=tqLE) Function_Return (dValueLeft <= dValueRight)
If (iComp=tqGE) Function_Return (dValueLeft >= dValueRight)
If (iComp=tqGT) Function_Return (dValueLeft > dValueRight)
If (iComp=tqNE) Function_Return (dValueLeft <> dValueRight)
Function_Return False
End_Function
Function _TestDateTime DateTime dtValueLeft Integer iComp DateTime dtValueRight Returns Boolean
If (iComp=tqEQ) Function_Return (dtValueLeft = dtValueRight)
If (iComp=tqLT) Function_Return (dtValueLeft < dtValueRight)
If (iComp=tqLE) Function_Return (dtValueLeft <= dtValueRight)
If (iComp=tqGE) Function_Return (dtValueLeft >= dtValueRight)
If (iComp=tqGT) Function_Return (dtValueLeft > dtValueRight)
If (iComp=tqNE) Function_Return (dtValueLeft <> dtValueRight)
Function_Return False
End_Function
Function _EvaluateFilter tTableQuery ByRef strQuery tTQFilterSegment strFilter Boolean bOrlistAllItems Returns Boolean
Integer iType iOrListMax iOrListIndex
Boolean bEval bFoundInOrList
String sLeftValue sRightValue
Get _ValueSourceValue (&strQuery) strFilter.strLeftValue to sLeftValue
Move strFilter.strLeftValue.iType to iType
Move (SizeOfArray(strFilter.strRightValue.aOrList)-1) to iOrListMax
If (bOrlistAllItems and iOrListMax>=0) Begin
Move False to bFoundInOrList
For iOrListIndex from 0 to iOrListMax
If (not(bFoundInOrList)) Begin
If (iType=tqSTRING) Get _TestString sLeftValue strFilter.iComp strFilter.strMatchEvalSequence strFilter.strRightValue.aOrList[iOrListIndex] to bFoundInOrList
Else If (iType=tqNUMBER) Get _TestNumber sLeftValue strFilter.iComp strFilter.strRightValue.aOrList[iOrListIndex] to bFoundInOrList
Else If (iType=tqTEXT) Get _TestText sLeftValue strFilter.iComp strFilter.strMatchEvalSequence strFilter.strRightValue.aOrList[iOrListIndex] to bFoundInOrList
Else If (iType=tqDATE) Get _TestDate sLeftValue strFilter.iComp strFilter.strRightValue.aOrList[iOrListIndex] to bFoundInOrList
Else If (iType=tqDATETIME) Get _TestDateTime sLeftValue strFilter.iComp strFilter.strRightValue.aOrList[iOrListIndex] to bFoundInOrList
End
Loop
Move bFoundInOrList to bEval
End
Else Begin
If (strFilter.strRightValue.iTable<>0 and not(_IsTableFixed(Self,strQuery,strFilter.strRightValue.iTable))) Begin
Get _ValueSourceValue (&strQuery) strFilter.strRightValue to sRightValue
If (strFilter.iComp=tqMATCH or strFilter.iComp=tqUCMATCH) Begin
Get WildCardEvalSequence of oStringFunctions sRightValue to strFilter.strMatchEvalSequence
End
end
Else Begin
Move strFilter.strRightValue._sValue to sRightValue
End
If (iType=tqSTRING) Get _TestString sLeftValue strFilter.iComp strFilter.strMatchEvalSequence sRightValue to bEval
Else If (iType=tqNUMBER) Get _TestNumber sLeftValue strFilter.iComp sRightValue to bEval
Else If (iType=tqTEXT) Get _TestText sLeftValue strFilter.iComp strFilter.strMatchEvalSequence sRightValue to bEval
Else If (iType=tqDATE) Get _TestDate sLeftValue strFilter.iComp sRightValue to bEval
Else If (iType=tqDATETIME) Get _TestDateTime sLeftValue strFilter.iComp sRightValue to bEval
End
Function_Return bEval
End_Function
Function _EvaluateConstraints tTableQuery ByRef strQuery tTQFilterSegment[] aFilters Boolean bOrlistAllItems Integer ByRef iFailedConstrainIndex Returns Boolean
Integer iMax iIndex
Boolean bEval
Move (SizeOfArray(aFilters)-1) to iMax
For iIndex from 0 to iMax
Get _EvaluateFilter (&strQuery) aFilters[iIndex] bOrlistAllItems to bEval
If (not(bEval)) Begin
Move iIndex to iFailedConstrainIndex
Function_Return False
End
Loop
Move -1 to iFailedConstrainIndex // Nothing failed
Function_Return True
End_Function
Function _EvaluateFilterExpression tTableQuery ByRef strQuery tTQFilterExpression[] aExpressions Returns Boolean
Integer iItem iItemMax
Boolean bRvalDetermined bEvalResult bAtomExpression
Move False to bRvalDetermined
Move (SizeOfArray(aExpressions)-1) to iItemMax
Move 0 to iItem
While (iItem<=iItemMax)
If (iItem>0 and ((bEvalResult and aExpressions[iItem-1].iAndOr=tqKeyOR) or ;
(not(bEvalResult) and aExpressions[iItem-1].iAndOr=tqKeyAND))) Begin
// Do nothing! Evaluating the current item will not change the overall value
End
Else Begin
If (SizeOfArray(aExpressions[iItem].aSubExpressions)>0) Begin
Get _EvaluateFilterExpression (&strQuery) aExpressions[iItem].aSubExpressions to bAtomExpression
End
Else Begin
Get _EvaluateFilter (&strQuery) aExpressions[iItem].strOperand True to bAtomExpression
End
If (aExpressions[iItem].bNot) Begin
Move (not(bAtomExpression)) to bAtomExpression
End
If (iItem=0) Begin
Move bAtomExpression to bEvalResult
End
Else Begin
If (aExpressions[iitem-1].iAndOr=tqKeyAND) Begin
Move (bEvalResult and bAtomExpression) to bEvalResult
End
Else If (aExpressions[iitem-1].iAndOr=tqKeyOR) Begin
Move (bEvalResult or bAtomExpression) to bEvalResult
End
Else Begin
Send OnError 723 "Unknown operator (should be AND or OR)"
End
End
End
Increment iItem
Loop
// If (not(bRvalDetermined)) Begin
// Error 724 "Expression could not evaluate"
// End
Function_Return bEvalResult
End_Function
Function _EvaluateFilterExpressions tTableQuery ByRef strQuery Returns Boolean
Boolean bEvalResult
Integer iItem iItemMax
Move True to bEvalResult
Move (SizeOfArray(strQuery.aFilterExpressions)-1) to iItemMax
Move 0 to iItem
While (bEvalResult and iItem<=iItemMax)
Get _EvaluateFilterExpression (&strQuery) strQuery.aFilterExpressions[iItem].aSubExpressions to bEvalResult
Increment iItem
Loop
Function_Return bEvalResult
End_Function
//> The function returns true if the record currently in
//> the buffer of the query main table evaluates true
//> against the filters of strQuery.
Function Belongs tTableQuery strQuery Returns Boolean
Boolean bBelongs
Integer iGrb
// New record! Indicate that related buffers have to be reconsidered
// and found if needed:
Send _TableRelateStatesReset (&strQuery)
// Evaluate against all filters added to the query:
Get _EvaluateConstraints (&strQuery) strQuery.aFilters True (&iGrb) to bBelongs
Function_Return bBelongs
End_Function
// Procedure ExecuteQuery tTableQuery ByRef strQuery
// Boolean bFound
// Move True to strQuery.bForcePrefetch
// If (_CompileQuery(Self,&strQuery)) Begin
// If (_InitializeQuery(Self,&strQuery)) Begin
// // When bForcePrefetch is true finding the record will populate the
// // result array inside strQuery:
// Get FindRecord (&strQuery) to bFound // and that's all we need
// Get SystemTimeMilliSeconds of oDateFunctions to strQuery._strControlBlock.strStopTime
// End
// End
// End_Procedure
Procedure PurgeResultSet tTableQuery ByRef strQuery
Move strQuery.iTable to strQuery.strResultSet.iTable
Move (ResizeArray(strQuery.strResultSet.aRecords,0)) to strQuery.strResultSet.aRecords
Move -1 to strQuery.strResultSet._iCurrentRowPointer
End_Procedure
Procedure _ResultSetAddCurrentRecord tTableQuery ByRef strQuery
Integer iRow iSegment iMax iColumn
Integer iTable
String sValue sSortValue
tocIndexSegment stSegment
// Row ID
Move (SizeOfArray(strQuery.strResultSet.aRecords)) to iRow
Move (GetRowID(strQuery.iTable)) to strQuery.strResultSet.aRecords[iRow].riRowId
// Calculate sort value
Move "" to sSortValue
Move (SizeOfArray(strQuery.aOrdering)-1) to iMax
For iSegment from 0 to iMax
Move strQuery.aOrdering[iSegment].strValueSource.iTable to iTable
Move strQuery.aOrdering[iSegment].strValueSource.iColumn to stSegment.iColumn
Move strQuery.aOrdering[iSegment].bDescending to stSegment.bDescending
Move strQuery.aOrdering[iSegment].bUppercase to stSegment.bUppercase
If (iTable<>strQuery.iTable) Begin
Send _RelateToTable (&strQuery) iTable
End
Get IndexSegmentSortValue of oIndexFunctions iTable stSegment to sValue
Move (sSortValue+sValue) to sSortValue
Loop
Move sSortValue to strQuery.strResultSet.aRecords[iRow].sSortValue
// Read selected column values
Move (SizeOfArray(strQuery.aColumnSources)-1) to iMax
For iColumn from 0 to iMax
Get _ValueSourceValue (&strQuery) strQuery.aColumnSources[iColumn] to strQuery.strResultSet.aRecords[iRow].aColumnValues[iColumn]
Loop
End_Procedure
Function _ResultSetRecordsCompare tTQResultRow stVal1 tTQResultRow stVal2 Returns Integer
If (stVal1.sSortValuestVal2.sSortValue) Function_Return (GT)
Function_Return (EQ)
End_Function
Procedure _ResultSetSort tTableQuery ByRef strQuery
If (SizeOfArray(strQuery.aOrdering)>0) Begin
Move (SortArray(strQuery.strResultSet.aRecords,Self,GET__ResultSetRecordsCompare)) to strQuery.strResultSet.aRecords
End
End_Procedure
Procedure _ResultSetActivateCurrentRecord tTableQuery ByRef strQuery
Boolean bFound
Integer iTable
RowID riRowID
Move strQuery.iTable to iTable
Move strQuery.strResultSet.aRecords[strQuery.strResultSet._iCurrentRowPointer].riRowId to riRowID
Move (FindByRowID(iTable,riRowID)) to bFound
Send _TableRelateStatesReset (&strQuery)
If (bFound) Begin
Send _RelateMainRecord (&strQuery)
End
Else Begin
Send OnError 725 "Result set record not found"
End
End_Procedure
Function _FindFirstRecordAux tTableQuery ByRef strQuery Integer ByRef iFailedConstrainIndex Returns Boolean
Boolean bFound bEval
Integer iTable iIndex iJunk
Move strQuery.iTable to iTable
Move strQuery._strCompiled.iIndex to iIndex
Clear iTable
Send _TableRelateStatesReset (&strQuery) // Reset information about what related records have already been found
Send _SeedRecordBuffer strQuery
If (strQuery._strCompiled.bBackwards) Begin
Vfind iTable iIndex LE
End
Else Begin
Vfind iTable iIndex GE
If (Found) Begin // Problem and fix pointed out by Russell McDougall
// From the on-line help:
// "A Find GE always finds a record unless the table is empty. A find GE after
// the last record in the table will still return the last record. It always sets
// the Predefined Indicators Found to True and FindErr to False). Note that this
// behavior is different than for LE. A Find LE before the first record in the table
// will not return a record, and will set the predefined indicators Found to False
// and FindErr to True."
// This feature of the find and vfind commands forces us to check
// that the record found is not a record that it really shouldn't have found in the
// first place:
Get _EvaluateConstraints (&strQuery) strQuery._strCompiled.aJumpInValues False (&iFailedConstrainIndex) to bEval
Move bEval to Found
End
End
Move (Found) to bFound
Increment strQuery._strControlBlock.iFindCount
If (bFound) Begin
Repeat
// Jump out?
Get _EvaluateConstraints (&strQuery) strQuery._strCompiled.aJumpOutValues False (&iFailedConstrainIndex) to bEval
If (not(bEval)) Begin // If jump-out criteria does not evaluate: jump out
Function_Return False
End
// Evaluate 'other' criteria
Get _EvaluateConstraints (&strQuery) strQuery._strCompiled.aOtherConditions True (&iJunk) to bEval
If (bEval) Begin // Evaluate filter expressions
Get _EvaluateFilterExpressions (&strQuery) to bEval
End
If (not(bEval)) Begin
If (strQuery._strCompiled.bBackwards) Begin
Vfind iTable iIndex LT
End
Else Begin
Vfind iTable iIndex GT
End
Move (Found) to bFound
Increment strQuery._strControlBlock.iFindCount
Send _TableRelateStatesReset (&strQuery)
If (not(bFound)) Begin // end of table
Move (SizeOfArray(strQuery._strCompiled.aJumpOutValues)-1) to iFailedConstrainIndex
Function_Return False
End
End
Until (bEval)
End
// Move (GetRowID(iTable)) to strQuery._strControlBlock.rCurrentRecord
If (bFound) Begin
Increment strQuery._strControlBlock.iSelectCount
End
Function_Return bFound
End_Function // _FindFirstRecordAux
Procedure _SQLBindResultTables tTableQuery ByRef strQuery
Integer iItem iItemMax
Move (SizeOfArray(strQuery.aColumnSources)-1) to iItemMax
If (iItemMax>=0) Begin
For iItem from 0 to iItemMax
If (strQuery.aColumnSources[iItem].iColumn=0) Begin
Send SQLBindFile of strQuery._strControlBlock.strSQLStatementHandle.hHandle strQuery.aColumnSources[iItem].iTable
End
Loop
End
Else Begin
Send SQLBindFile of strQuery._strControlBlock.strSQLStatementHandle.hHandle strQuery.iTable
End
End_Procedure
Function _SQLCheckAgainstParentRecord_CompareSortingSegmentValues String sValue1 String sValue2 Integer iType Returns Integer
Date dValue1 dValue2
DateTime dtValue1 dtValue2
Number nValue1 nValue2
If (iType=tqNUMBER) Begin
Move sValue1 to nValue1
Move sValue2 to nValue2
If (nValue1>nValue2) Function_Return tqGT
If (nValue1dValue2) Function_Return tqGT
If (dValue1dtValue2) Function_Return tqGT
If (dtValue1sValue2) Function_Return tqGT
If (sValue1 child query must catch up
Move True to bCatchUpWithParent
Function_Return False
End
Else If (iCompareResult=tqLT) Begin // Child is greater which means we must fake a end-of-result set
Move False to bCatchUpWithParent
Function_Return False
End
Loop
Move False to bCatchUpWithParent
Function_Return True
End_Function
Function _SQLFindNextRecord tTableQuery ByRef strQuery Returns Boolean
Integer iFetchResult iItem iItemMax
Boolean bFound bSelect bInnerJoin bCatchUpWithParent bBelongsToCurrentParent
Move (SizeOfArray(strQuery._strControlBlock.strInnerJoinCB.aCurrentParentSortValues)-1) to iItemMax
Move (iItemMax>=0) to bInnerJoin // We are an inner join and should be prepared to fake a 'record not found'
If (bInnerJoin) Begin
If (strQuery._strControlBlock.strInnerJoinCB.bEndOfResultSet) Begin
Function_Return False
End
Repeat
If (strQuery._strControlBlock.strInnerJoinCB.bSkipNextFetch) Begin
Move False to strQuery._strControlBlock.strInnerJoinCB.bSkipNextFetch
Move True to bFound
End
Else Begin
Get SQLFetch of strQuery._strControlBlock.strSQLStatementHandle.hHandle to iFetchResult
Move (iFetchResult<>0) to bFound
Increment strQuery._strControlBlock.iFindCount
End
If (bFound) Begin
Send _TableRelateStatesReset (&strQuery)
Send _SQLBindResultTables (&strQuery)
Get _SQLCheckAgainstParentRecord (&strQuery) (&bCatchUpWithParent) to bBelongsToCurrentParent
If (bBelongsToCurrentParent) Begin
Move True to bSelect
End
Else Begin
If (not(bCatchUpWithParent)) Begin
Move True to strQuery._strControlBlock.strInnerJoinCB.bSkipNextFetch
End
End
End
Else Begin
Move True to strQuery._strControlBlock.strInnerJoinCB.bEndOfResultSet
End
Until (not(bFound) or bSelect or not(bCatchUpWithParent))
If (bSelect) Begin
Increment strQuery._strControlBlock.iSelectCount
Function_Return True
End
End
Else Begin
Get SQLFetch of strQuery._strControlBlock.strSQLStatementHandle.hHandle to iFetchResult
Move (iFetchResult<>0) to bFound
If (bFound) Begin
Send _SQLBindResultTables (&strQuery)
Increment strQuery._strControlBlock.iSelectCount
End
Increment strQuery._strControlBlock.iFindCount
Function_Return bFound
End
Function_Return False
End_Function
Register_Function FindRecord tTableQuery ByRef strQuery Returns Boolean
Function _FindFirstRecord tTableQuery ByRef strQuery Returns Boolean
Boolean bFound bIncremented bTmp
Integer iLastIndex iSize iTable
Integer iFailedConstrainIndex
Integer iLogState iFetchResult
Move 0 to strQuery._strControlBlock.iFindCount
Move 0 to strQuery._strControlBlock.iSelectCount
Move False to strQuery._strControlBlock.bBreakScan
Send PurgeResultSet (&strQuery) // Zero the result set
If (strQuery._strControlBlock.iQueryMethod=tqSQLPKG) Begin
Get _SQLFindNextRecord (&strQuery) to bFound
End
Else Begin
// Makes the procedure reset all OrList pointers. Return value not used:
Get _IncrementJumpInOutConditions (&strQuery) -1 to bFound
Move strQuery.iTable to iTable
Clear iTable //
Send _TableRelateStatesReset (&strQuery)
Send _AssignConstraintValues (&strQuery) (&strQuery._strCompiled.aJumpInValues)
Send _AssignConstraintValues (&strQuery) (&strQuery._strCompiled.aJumpOutValues)
Send _AssignConstraintValues (&strQuery) (&strQuery._strCompiled.aOtherConditions)
Send _AssignFilterExpressionValues (&strQuery)
If (strQuery._strCompiled.bPrefetch) Begin
Move False to strQuery._strCompiled.bPrefetch // Temporarily set bPrefetch to false
Send ReUse (&strQuery) // resets strQuery.blabla.bInitialized
Move strQuery.bForcePrefetch to bTmp
// Here comes a dirty trick to avoid FindRecord deleting
// the ResultArray of the strQuery variable on completion. The
// default behavior is to reset the resultset on completion if
// prefetch was not forced. It's dirty because I am fooling
// FindRecord into not re-compiling the report. Here it goes:
Move True to strQuery.bForcePrefetch
Move _giTQLogState to iLogState
Move 0 to _giTQLogState // Do not log the "inner" loop
While (FindRecord(Self,&strQuery))
Send _ResultSetAddCurrentRecord (&strQuery)
Loop
Move bTmp to strQuery.bForcePrefetch
Move iLogState to _giTQLogState // Restore log-state
Send _ResultSetSort (&strQuery)
Move False to strQuery._strControlBlock.bBreakScan // Cancel effect of possible break imposed by limit
Move True to strQuery._strCompiled.bPrefetch // Restore bPrefetch
If (strQuery._strCompiled._iPushLimit<>0) Begin
If (SizeOfArray(strQuery.strResultSet.aRecords)>Abs(strQuery._strCompiled._iPushLimit)) Begin
// If the record set is greater than our limit we'll have to prune it.
If (strQuery.iLimit>0) Begin // We're supposed to return the head
Move (ResizeArray(strQuery.strResultSet.aRecords,strQuery._strCompiled._iPushLimit)) to strQuery.strResultSet.aRecords
End
Else Begin // We're supposed to take the tail
Move (SizeOfArray(strQuery.strResultSet.aRecords)-1) to iLastIndex
Move strQuery._strCompiled._iPushLimit to iSize
Move (CopyArray(strQuery.strResultSet.aRecords,iLastIndex-iSize+1,iLastIndex)) to strQuery.strResultSet.aRecords
End
Move (SizeOfArray(strQuery.strResultSet.aRecords)) to strQuery._strControlBlock.iSelectCount
End
If (SizeOfArray(strQuery.strResultSet.aRecords)>strQuery._strCompiled._iPushLimit) Begin
Move (ResizeArray(strQuery.strResultSet.aRecords,strQuery._strCompiled._iPushLimit)) to strQuery.strResultSet.aRecords
Move (SizeOfArray(strQuery.strResultSet.aRecords)) to strQuery._strControlBlock.iSelectCount
End
Move strQuery._strCompiled._iPushLimit to strQuery._strCompiled._iLimit
Move 0 to strQuery._strCompiled._iPushLimit
End
Move (SizeOfArray(strQuery.strResultSet.aRecords)>0) to bFound
If (bFound) Begin
Move 0 to strQuery.strResultSet._iCurrentRowPointer
Send _ResultSetActivateCurrentRecord (&strQuery)
End
End
Else Begin
Repeat
Get _FindFirstRecordAux (&strQuery) (&iFailedConstrainIndex) to bFound
If (not(bFound)) Begin
Get _IncrementJumpInOutConditions (&strQuery) iFailedConstrainIndex to bIncremented
If (bIncremented) Begin
Send _ReassignJumpInOutConstraintValues (&strQuery) (&strQuery._strCompiled.aJumpInValues) (&strQuery._strCompiled.aJumpOutValues)
End
End
Until (bFound or not(bIncremented))
End
End
Function_Return bFound
End_Function // _FindFirstRecord
Function _FindNextRecordAux tTableQuery ByRef strQuery Integer ByRef iFailedConstrainIndex Returns Boolean
Integer iTable iIndex iJunk
Boolean bFound bEval
Move strQuery.iTable to iTable
Move strQuery._strCompiled.iIndex to iIndex
If (strQuery._strCompiled.bBackwards) Begin
Vfind iTable iIndex LT
End
Else Begin
Vfind iTable iIndex GT
End
Move (Found) to bFound
Increment strQuery._strControlBlock.iFindCount
Send _TableRelateStatesReset (&strQuery)
If (bFound) Begin
Repeat
// Jump out?
Get _EvaluateConstraints (&strQuery) strQuery._strCompiled.aJumpOutValues False (&iFailedConstrainIndex) to bEval
If (not(bEval)) Begin // If jump-out criteria does not evaluate: jump out
Function_Return False
End
// Evaluate 'other' criteria
Get _EvaluateConstraints (&strQuery) strQuery._strCompiled.aOtherConditions True (&iJunk) to bEval
If (bEval) Begin // Evaluate filter expressions
Get _EvaluateFilterExpressions (&strQuery) to bEval
End
If (not(bEval)) Begin
If (strQuery._strCompiled.bBackwards) Begin
Vfind iTable iIndex LT
End
Else Begin
Vfind iTable iIndex GT
End
Increment strQuery._strControlBlock.iFindCount
Move (Found) to bFound
Send _TableRelateStatesReset (&strQuery)
If (not(bFound)) Begin // end of table
Move (SizeOfArray(strQuery._strCompiled.aJumpOutValues)-1) to iFailedConstrainIndex
Function_Return False
End
End
Until (bEval)
End
If (bFound) Begin
Increment strQuery._strControlBlock.iSelectCount
End
Function_Return bFound
End_Function
Function _FindNextRecord tTableQuery ByRef strQuery Returns Boolean
Boolean bFound bIncremented
Integer iFailedConstrainIndex iFetchResult
If (strQuery._strControlBlock.bBreakScan) Begin
Move False to bFound
End
Else Begin
If (strQuery._strControlBlock.iQueryMethod=tqSQLPKG) Begin
Get _SQLFindNextRecord (&strQuery) to bFound
End
Else Begin
If (strQuery._strCompiled.bPrefetch) Begin
Move ((strQuery.strResultSet._iCurrentRowPointer+1) Internal function used to find the index for the relation recip‚ for tabel iTable.
Function _FindTableRelation tTableQuery strQ Integer iTable Returns Integer
Integer iItem iMax
Move (SizeOfArray(strQ.aTableRelations)-1) to iMax
For iItem from 0 to iMax
If (strQ.aTableRelations[iItem].iTargetTable=iTable) Begin
Function_Return iItem
End
Loop
Function_Return -1 // not found
End_Function
// If bFixed is true it means that the parent record is fixed for the duration of the query. In turn, that means
// that the relation mechanism will not find records in that table (iTargetTable).
Procedure _AddTableRelation tTableQuery ByRef strQ Integer iTargetTable Boolean bFixed
Integer iItem
Get _FindTableRelation strQ iTargetTable to iItem
If (iItem=-1) Begin
Move (SizeOfArray(strQ.aTableRelations)) to iItem
Move iTargetTable to strQ.aTableRelations[iItem].iTargetTable
Move -1 to strQ.aTableRelations[iItem]._iEmbeddedIndex
Move bFixed to strQ.aTableRelations[iItem].bFixed
End
Else Begin
If (bFixed) Begin // If it's already there then bFixed=true wins.
Move bFixed to strQ.aTableRelations[iItem].bFixed
End
End
End_Procedure
Function _CompareTableRelationSegments _tTQTableRelationSegment strValue1 _tTQTableRelationSegment strValue2 Returns Integer
Number nValue1 nValue2
Move (strValue1.iInnerJoinComp*10000+strValue1.iSourceColumn*10000+strValue1.iSourceTable*10000+strValue1.iTargetColumn) to nValue1
Move (strValue2.iInnerJoinComp*10000+strValue2.iSourceColumn*10000+strValue2.iSourceTable*10000+strValue2.iTargetColumn) to nValue2
If (nValue1nValue2) Function_Return (GT)
Function_Return (EQ)
End_Function
Function _TableRelationsAreSegmentArraysIdentical _tTQTableRelationSegment[] aSegments1 _tTQTableRelationSegment[] aSegments2 Returns Boolean
Integer iItem iItemMax1 iItemMax2
Move (SizeOfArray(aSegments1)-1) to iItemMax1
Move (SizeOfArray(aSegments2)-1) to iItemMax2
If (iItemMax1<>iItemMax2) Begin
Function_Return False
End
// The arrays are not ordered so to tell if they are identical they need to be sorted first.
Move (SortArray(aSegments1,Self,GET__CompareTableRelationSegments)) to aSegments1
Move (SortArray(aSegments2,Self,GET__CompareTableRelationSegments)) to aSegments2
For iItem from 0 to iItemMax1
If (_CompareTableRelationSegments(Self,aSegments1[iItem],aSegments2[iItem])<>EQ) Begin
Function_Return False
End
Loop
End_Function
Function _TableRelationsAreIdentical tTQTableRelation strRel1 tTQTableRelation strRel2 Returns Boolean
If (strRel1.iTargetTable<>strRel2.iTargetTable or strRel1.bFixed<>strRel2.bFixed) Begin
Function_Return False
End
If (not(_TableRelationsAreSegmentArraysIdentical(Self,strRel1.aSegments,strRel2.aSegments))) Begin
Function_Return False
End
Function_Return True
End_Function
Function _AddTableRelationB2B tTableQuery ByRef strQ tTQTableRelation strRelation Returns Boolean
Integer iItem
Get _FindTableRelation strQ strRelation.iTargetTable to iItem
If (iItem=-1) Begin
Move strRelation to strQ.aTableRelations[SizeOfArray(strQ.aTableRelations)]
End
Else Begin
If (not(_TableRelationsAreIdentical(Self,strRelation,strQ.aTableRelations[iItem]))) Begin
Send OnError 727 "Table takes on more than one role"
Function_Return False
End
End
Function_Return True
End_Function
Procedure AddTableRelation tTableQuery ByRef strQ Integer iTargetTable
Send _AddTableRelation (&strQ) iTargetTable False
End_Procedure
// This is called from the AddFilterRelate procedure.
Procedure AddTableRelationFixedRecord tTableQuery ByRef strQ Integer iTargetTable
Send _AddTableRelation (&strQ) iTargetTable True
End_Procedure
Procedure _AddTableRelationSegment tTableQuery ByRef strQ Integer iSourceTable Integer iSourceColumn Integer iComp Integer iTargetTable Integer iTargetColumn
Integer iItem
tTQTableRelation strRelatation
_tTQTableRelationSegment strSegment
Get _FindTableRelation strQ iTargetTable to iItem
If (iItem>=0) Begin
// Check that iSourceTable is already available in the .aRelations member. If not: error!
If (iSourceTable=strQ.iTable or _FindTableRelation(Self,strQ,iSourceTable)<>-1) Begin
Move strQ.aTableRelations[iItem] to strRelatation
Move iSourceTable to strSegment.iSourceTable
Move iComp to strSegment.iInnerJoinComp
Move iSourceColumn to strSegment.iSourceColumn
Move iTargetColumn to strSegment.iTargetColumn
Move strSegment to strRelatation.aSegments[SizeOfArray(strRelatation.aSegments)]
Move strRelatation to strQ.aTableRelations[iItem]
End
Else Begin
Send OnError 728 ("No relation has been set up for table "+String(iSourceTable))
End
End
Else Begin
Send OnError 729 ("No relation has been set up for table "+String(iTargetTable))
End
End_Procedure
Procedure AddTableRelationSegment tTableQuery ByRef strQ Integer iSourceTable Integer iSourceColumn Integer iTargetTable Integer iTargetColumn
Send _AddTableRelationSegment (&strQ) iSourceTable iSourceColumn tqEQ iTargetTable iTargetColumn
End_Procedure
Procedure _AddTableRelationInnerJoinSegment tTableQuery ByRef strQ Integer iSourceTable Integer iSourceColumn Integer iComp Integer iTargetTable Integer iTargetColumn
Send _AddTableRelationSegment (&strQ) iSourceTable iSourceColumn iComp iTargetTable iTargetColumn
End_Procedure
//> Use this method over the AddTableRelation ditto to register a table relation by its generic relation
Procedure _AddTableRelationAuto tTableQuery ByRef strQ Integer iSourceTable Integer iTargetTable Boolean bFixed
Integer iItem iMax iRelateItem
Integer[] aSourceColumns aTargetColumns
Get _FindTableRelation strQ iTargetTable to iRelateItem
If (iRelateItem=-1) Begin
If (TableColumnsRelatingBetweenTables(oOverlapColumnFunctions,iSourceTable,iTargetTable,&aSourceColumns,&aTargetColumns)) Begin
Send AddTableRelation (&strQ) iTargetTable
Move (SizeOfArray(aSourceColumns)-1) to iMax
For iItem from 0 to iMax
Send AddTableRelationSegment (&strQ) iSourceTable aSourceColumns[iItem] iTargetTable aTargetColumns[iItem]
Loop
End
Else Begin
Send OnError 730 ("Tables do not relate ("+String(iSourceTable)+"->"+String(iTargetTable)+")")
End
End
Else Begin // If a relation is already there:
// ToDo: // Here we need to check if the current segment array is empty.
// If it is, we add the relation segments
// If it's not, we should check that it is currently set
// to the "plain df relation" and give an Error if it's not.
End
End_Procedure
Procedure AddTableRelationAuto tTableQuery ByRef strQ Integer iSourceTable Integer iTargetTable
Send _AddTableRelationAuto (&strQ) iSourceTable iTargetTable False
End_Procedure
Procedure AddTableRelationAutoFixed tTableQuery ByRef strQ Integer iSourceTable Integer iTargetTable
Send _AddTableRelationAuto (&strQ) iSourceTable iTargetTable True
End_Procedure
Function _TableRelationAlreadyOK tTableQuery ByRef strQ tTQTableRelation strRelation Returns Boolean
Integer iSegment iSegmentMax
String sSourceValue sTargetValue
If (not(strRelation.bFixed)) Begin
Move (SizeOfArray(strRelation.aSegments)-1) to iSegmentMax
For iSegment from 0 to iSegmentMax
Get ResultColumnValue (&strQ) strRelation.aSegments[iSegment].iSourceTable strRelation.aSegments[iSegment].iSourceColumn to sSourceValue
Get_Field_Value strRelation.iTargetTable strRelation.aSegments[iSegment].iTargetColumn to sTargetValue
If (sSourceValue<>sTargetValue) Begin
Function_Return (False)
End
Loop
End
Function_Return (True)
End_Function
Function _SQLNumericToDFNumeric Integer iTable String sValue Returns String
Integer iDriverDS iRuntimeDS iDriverIndex
String sDriver
Get_Attribute DF_FILE_DRIVER of iTable to sDriver
Get DriverIndex of oTableDriverFunctions sDriver to iDriverIndex
Get_Attribute DF_DRIVER_DRIVER_DECIMAL_SEPARATOR of iDriverIndex to iDriverDS
Get_Attribute DF_DECIMAL_SEPARATOR to iRuntimeDS
If (iRuntimeDS<>iDriverDS) Begin
Move (Replaces(Character(iDriverDS),sValue,Character(iRuntimeDS))) to sValue
End
If (Left(sValue,1)=Character(iRuntimeDS)) Begin
Move ("0"+sValue) to sValue
End
Function_Return sValue
End_Function
Function ResultColumnValue tTableQuery ByRef strQ Integer iTable Integer iColumn Returns String
Integer iRelateItem iColumnSourceItem iType
Integer iDriverDecimalPlaces iDecimalPlaces
String sValue
If (strQ._strControlBlock.iQueryMethod=tqDATAFLEX) Begin
Send _RelateToTable (&strQ) iTable
If (iColumn=0) Begin
Move (SerializeRowID(GetRowID(iTable))) to sValue
End
Else Begin
Get_Field_Value iTable iColumn to sValue
Move (Rtrim(sValue)) to sValue
End
End
Else Begin
If (SizeOfArray(strQ.aColumnSources)=0) Begin
If (iTable=strQ.iTable) Begin
Get_Field_Value iTable iColumn to sValue
Move (Rtrim(sValue)) to sValue
End
Else Begin
Send OnError 731 "Column not part of result set."
End
End
Else Begin
Get _ColumnSourceFind strQ iTable iColumn to iColumnSourceItem
If (iColumnSourceItem<>-1) Begin
If (strQ.aColumnSources[iColumnSourceItem].iColumn=0) Begin
Get_Field_Value iTable iColumn to sValue
Move (Rtrim(sValue)) to sValue
End
Else Begin
//
Get SQLColumnValue of strQ._strControlBlock.strSQLStatementHandle.hHandle (iColumnSourceItem+1) to sValue
Move strQ.aColumnSources[iColumnSourceItem].iType to iType
If (iType=tqDATE) Begin
Get SQLDateToDFDate of strQ._strControlBlock.strSQLStatementHandle.hHandle sValue to sValue
End
Else If (iType=tqNUMBER) Begin
Get _SQLNumericToDFNumeric iTable sValue to sValue
End
Else If (iType=tqSTRING or iType=tqTEXT) Begin
If (strQ._strControlBlock.bSQLTableIsAnsi) Begin
Move (ToOEM(sValue)) to sValue
End
Move (Rtrim(sValue)) to sValue
End
End
End
Else Begin
Send OnError 732 "Unknown result column"
End
End
End
Function_Return sValue
End_Function
Function ResultColumnValues tTableQuery ByRef strQ Boolean bIncludeRowIDs Returns String[]
Boolean bDone
Integer iItem iMax iTable iColumn
String[] aValues
Move (SizeOfArray(strQ.aColumnSources)-1) to iMax
Move 0 to iItem
Move False to bDone
While (not(bDone) and iItem<=iMax)
If (not(bIncludeRowIDs)) Begin
Move (strQ.aColumnSources[iItem].iColumn=0) to bDone // The ones that are Table.* are excluded
End
If (not(bDone)) Begin
Get ResultColumnValue (&strQ) strQ.aColumnSources[iItem].iTable strQ.aColumnSources[iItem].iColumn to aValues[iItem]
End
Increment iItem
Loop
Function_Return aValues
End_Function
Function OutputColumnValues tTableQuery ByRef strQ Boolean bIncludeRowIDs Returns String[]
Boolean bDone
Integer iItem iMax iTable iColumn
String[] aValues
Move (SizeOfArray(strQ.aOutputColumns)-1) to iMax
Move 0 to iItem
Move False to bDone
While (not(bDone) and iItem<=iMax)
If (not(bIncludeRowIDs)) Begin
Move (strQ.aOutputColumns[iItem].iColumn=0) to bDone // The ones that are Table.* are excluded
End
If (not(bDone)) Begin
Get ResultColumnValue (&strQ) strQ.aOutputColumns[iItem].iTable strQ.aOutputColumns[iItem].iColumn to aValues[iItem]
End
Increment iItem
Loop
Function_Return aValues
End_Function
Function __RelateSegmentsToColumns _tTQTableRelationSegment[] aSegments Returns Integer[]
Integer iItem iMax
Integer[] aColumns
Move (SizeOfArray(aSegments)-1) to iMax
For iItem from 0 to iMax
Move aSegments[iItem].iTargetColumn to aColumns[iItem]
Loop
Function_Return aColumns
End_Function
Procedure _RelateTablePerform tTableQuery ByRef strQ Integer iRelateItem // Is only called when i embedded mode
Integer iMax iItem iIndex
Integer iSourceTable iSourceColumn iTargetTable iTargetColumn
String sValue
Integer[] aColumns
tTQTableRelation strRelation
Move strQ.aTableRelations[iRelateItem] to strRelation
If (not(strRelation.bFixed)) Begin // If fixed we will just accept the record that is there.
Move (SizeOfArray(strRelation.aSegments)-1) to iMax
Move strRelation.iTargetTable to iTargetTable
Clear iTargetTable
For iItem from 0 to iMax
Move strRelation.aSegments[iItem].iSourceTable to iSourceTable
Move strRelation.aSegments[iItem].iSourceColumn to iSourceColumn
Move strRelation.aSegments[iItem].iTargetColumn to iTargetColumn
Get ResultColumnValue (&strQ) iSourceTable iSourceColumn to sValue
Set_Field_Value iTargetTable iTargetColumn to sValue
Loop
If (strRelation._iEmbeddedIndex=-1) Begin
Get __RelateSegmentsToColumns strRelation.aSegments to aColumns
Get FindEQIndex of oIndexFunctions iTargetTable aColumns to iIndex
If (iIndex=-1) Begin
Send OnError 733 ("Can't identify record in related table "+String(iTargetTable))
End
Move iIndex to strRelation._iEmbeddedIndex
Move strRelation to strQ.aTableRelations[iRelateItem]
End
Else Begin
Move strRelation._iEmbeddedIndex to iIndex
End
Vfind iTargetTable iIndex EQ
If (not(Found)) Begin
Clear iTargetTable
End
End
End_Procedure
Procedure _RelateToTable tTableQuery ByRef strQ Integer iTable // This is only called for embedded type queries (from the ResultColumnValue function)
Integer iRelateItem
Boolean bTableRelated
tTQTableRelation strRelation
If (iTable<>strQ.iTable) Begin // If iTable is not the main table of the query
Get _FindTableRelation strQ iTable to iRelateItem
If (iRelateItem<>-1) Begin
Move strQ._strControlBlock.aRelatedRecords[iRelateItem] to bTableRelated
If (not(bTableRelated)) Begin //
Move strQ.aTableRelations[iRelateItem] to strRelation
If (not(strRelation.bFixed)) Begin // If no items we will just accept the current record
If (IsNullRowID(GetRowID(strQ.iTable))) Begin
Clear iTable
End
Else Begin
If (not(_TableRelationAlreadyOK(Self,&strQ,strRelation))) Begin // If not already good by chance.
// It could already be OK by chance in which case it will not perform a relate. If the parent
// record is the same as for the previous child record. Or if it has been "fixated" by an
// outer loop in which case it would be constant. It is the responsability of the
// programmer to ensure that tables do have to take on double roles.
Send _RelateTablePerform (&strQ) iRelateItem
End
End
End
Move True to strQ._strControlBlock.aRelatedRecords[iRelateItem] // update TableRelateState to "performed"
End
End
Else Begin
Send OnError 734 "Table not related"
End
End
End_Procedure
// This is called in preparation for a new record to indicate the related buffers have
// to be reconsidered and rectified if needed.
Procedure _TableRelateStatesReset tTableQuery ByRef strQ
Boolean[] abEmpty
Move abEmpty to strQ._strControlBlock.aRelatedRecords
Move (ResizeArray(strQ._strControlBlock.aRelatedRecords,SizeOfArray(strQ.aTableRelations),False)) to strQ._strControlBlock.aRelatedRecords
End_Procedure
Function __CompareColumnSource _tTQValueSource strValue1 _tTQValueSource strValue2 Returns Integer
Boolean bCompleteRecord1 bCompleteRecord2
Move (strValue1.iColumn=0) to bCompleteRecord1
Move (strValue2.iColumn=0) to bCompleteRecord2
If (bCompleteRecord1=bCompleteRecord2) Begin
Function_Return (EQ)
End
If (bCompleteRecord1) Begin
Function_Return (GT)
End
Else Begin
Function_Return (LT)
End
End_Function
//> Add table column to the result set.
Procedure AddResultColumn tTableQuery ByRef strQ Integer iTable Integer iColumn Boolean bRejectIfAlreadyThere
Integer iColumnSourceItem
Boolean _bRejectIfAlreadyThere
If (num_arguments>=4) Begin
Move bRejectIfAlreadyThere to _bRejectIfAlreadyThere
End
Else Begin
Move False to _bRejectIfAlreadyThere
End
If (_bRejectIfAlreadyThere) Begin
Get _ColumnSourceFind strQ iTable iColumn to iColumnSourceItem
If (iColumnSourceItem>=0) Begin
Procedure_Return // The column is already there => skip it
End
End
If (iTable=strQ.iTable or _FindTableRelation(Self,strQ,iTable)<>-1) Begin // We accept only ourselves or one of our related.
Move (SizeOfArray(strQ.aColumnSources)) to iColumnSourceItem
Move iTable to strQ.aColumnSources[iColumnSourceItem].iTable
Move iColumn to strQ.aColumnSources[iColumnSourceItem].iColumn
Get _ColumnType iTable iColumn to strQ.aColumnSources[iColumnSourceItem].iType
If (not(strQ._bOutputColumnsFixated)) Begin
Move strQ.aColumnSources[iColumnSourceItem] to strQ.aOutputColumns[SizeOfArray(strQ.aOutputColumns)]
End
Move (SortArray(strQ.aColumnSources,Self,GET___CompareColumnSource)) to strQ.aColumnSources
End
Else Begin
Send OnError 736 ("Unknown table in result set ("+String(iTable)+")")
End
End_Procedure
Procedure FixateOutputColumns tTableQuery ByRef strQ
Move True to strQ._bOutputColumnsFixated
End_Procedure
Procedure AddResultTable tTableQuery ByRef strQ Integer iTable
Send AddResultColumn (&strQ) iTable 0
End_Procedure
Procedure ResetResultSetColumns tTableQuery ByRef strQuery
// Check that the query is not already executing
If (strQuery._strControlBlock.bInitialized) Begin
Send OnError 741 "ResetResultSetColumns called while query is executing"
End
Else Begin
Move (ResizeArray(strQuery.aColumnSources,0)) to strQuery.aColumnSources
Move (ResizeArray(strQuery.aOutputColumns,0)) to strQuery.aOutputColumns
End
End_Procedure
Function _IsColumnIncludedInResultSet tTableQuery strQ Integer iTable Integer iColumn Returns Boolean
Integer iItem iItemMax
Move (SizeOfArray(strQ.aColumnSources)-1) to iItemMax
If (strQ.iTable=iTable and iItemMax=-1) Begin
Function_Return True //
End
For iItem from 0 to iItemMax
If (iTable=strQ.aColumnSources[iItem].iTable) Begin
If (iColumn=strQ.aColumnSources[iItem].iColumn or strQ.aColumnSources[iItem].iColumn=0) Begin
Function_Return True
End
End
Loop
Function_Return False
End_Function
// *** End of TableRelation functions *** End of TableRelation functions *** End of TableRelation functions *** End of TableRelation functions *** End of TableRelation functions ***
// If iTable has a name in filelist.cfg we use that, other wise
// we try the SQL table name.
Function LogTableName tTableQuery strQuery Returns String
Integer iTable
String sTableName
Move strQuery.iTable to iTable
Get_Attribute DF_FILE_LOGICAL_NAME of iTable to sTableName
If (sTableName="") Begin
Move strQuery.sForceSQLTableName to sTableName
End
If (sTableName<>"") Begin
Move (sTableName+"_tqlog.dat") to sTableName
End
Function_Return sTableName
End_Function
Function LogTableNameFullPath tTableQuery strQuery Returns String
String sFolder sTablePath
Get VdfFolderPath of oFileFunctions VDF_FILELIST to sFolder
Get AppendPath of oFileFunctions sFolder "tq-logdata" to sFolder
Get AppendPath of oFileFunctions sFolder (LogTableName(Self,strQuery)) to sTablePath
Function_Return sTablePath
End_Function
Function LogTableCreate tTableQuery strQuery Returns Boolean
tTempTableDefinition strTable
Get EmptyTempTableDefinition of oTemporaryTablesFunctions to strTable
Move 20000 to strTable.iMaxRecords // Max number of logs
Get LogTableName strQuery to strTable.sFileName
Move "" to strTable.sFolder // blank means it goes in the data folder
Move "tq-logdata" to strTable.sSubFolder // means it goes in a subfolder of the data folder called "tq-logdata"
Send AddColumn of oTemporaryTablesFunctions (&strTable) "DateTime" DF_ASCII 23 0 // field no 1
Send AddColumn of oTemporaryTablesFunctions (&strTable) "ExecTime" DF_BCD 8 0 // field no 2
Send AddColumn of oTemporaryTablesFunctions (&strTable) "LogTag" DF_ASCII 20 0 // field no 3
Send AddColumn of oTemporaryTablesFunctions (&strTable) "User" DF_ASCII 20 0 // field no 4
Send AddColumn of oTemporaryTablesFunctions (&strTable) "Application" DF_ASCII 20 0 // field no 5
Send AddColumn of oTemporaryTablesFunctions (&strTable) "SessionStart" DF_ASCII 23 0 // field no 6
Send AddColumn of oTemporaryTablesFunctions (&strTable) "FullTableScan" DF_BCD 2 0 // field no 7
Send AddColumn of oTemporaryTablesFunctions (&strTable) "RecordsFound" DF_BCD 10 0 // field no 8
Send AddColumn of oTemporaryTablesFunctions (&strTable) "RecordsSelected" DF_BCD 10 0 // field no 9
Send AddColumn of oTemporaryTablesFunctions (&strTable) "UnusedFilters" DF_BCD 4 0 // field no 10
Send AddColumn of oTemporaryTablesFunctions (&strTable) "Index" DF_BCD 4 0 // field no 11
Send AddColumn of oTemporaryTablesFunctions (&strTable) "IndexForced" DF_BCD 2 0 // field no 12
Send AddColumn of oTemporaryTablesFunctions (&strTable) "PrefetchForced" DF_BCD 2 0 // field no 13
Send AddColumn of oTemporaryTablesFunctions (&strTable) "Limit" DF_BCD 6 0 // field no 14
Send AddColumn of oTemporaryTablesFunctions (&strTable) "ScanComplete" DF_BCD 2 0 // field no 15
Send AddColumn of oTemporaryTablesFunctions (&strTable) "TQserialised" DF_BINARY 2040 0 // field no 16
Move True to strTable.bCompression
If (CreateTable(oTemporaryTablesFunctions,strTable,True)) Begin
// Showln "LogTableCreate Success"
Function_Return True
End
Else Begin
// Showln "LogTableCreate Failure"
End
Function_Return False
End_Function
Function OpenLogTable tTableQuery strQuery Returns Integer
Integer iLogTable
String sLogTableName
Move 0 to iLogTable
Get LogTableNameFullPath strQuery to sLogTableName
// We do not expect the log file to have an entry in filelist.cfg
// so we open it "as".
If (sLogTableName<>"") Begin
Get OpenTableAsAutoHandle of oTableAccessFunctions sLogTableName DF_SHARE 0 to iLogTable
If (iLogTable=0) Begin // If the table isn't there, we create it
If (LogTableCreate(Self,strQuery)) Begin
Get OpenTableAsAutoHandle of oTableAccessFunctions sLogTableName DF_SHARE 0 to iLogTable
End
End
End
Else Begin
// No error! (I changed my mind)
// Error xxx ("Table name for log file could not be calculated (table: "+String(strQuery.iTable)+")")
End
Function_Return iLogTable
End_Function
Function LogTableHandle tTableQuery strQuery Returns Integer
Integer iLogTable
Move 0 to iLogTable
If (strQuery.iTable Returns an array of tables that are referenced as constants during the execution of a query.
//> So if you want to repeat the find loop of a particular tTableQuery variable, the record
//> buffers of fixed tables must be restored to that of the original query. Does this make sense?
Function _FixedTables tTableQuery strQuery Boolean bFixed Returns Integer[]
Integer iMax iIndex
Integer[] aTables
Move (SizeOfArray(strQuery.aTableRelations)-1) to iMax
For iIndex from 0 to iMax
If (strQuery.aTableRelations[iIndex].bFixed=bFixed) Begin
If (IntegerAddToSet(oStackFunctions,&aTables,strQuery.aTableRelations[iIndex].iTargetTable)) Begin
// Do nothing
End
End
Loop
Function_Return aTables
End_Function
Function FixedTables tTableQuery strQuery Returns Integer[]
Function_Return (_FixedTables(Self,strQuery,True))
End_Function
Function NotFixedTables tTableQuery strQuery Returns Integer[]
Boolean bGrb
Integer[] aTables
Get _FixedTables strQuery False to aTables
Get IntegerAddToSet of oStackFunctions (&aTables) strQuery.iTable to bGrb
Function_Return aTables
End_Function
Function FixedTablesRowIDs tTableQuery strQuery Returns RowID[]
Integer iItem iMax
Integer[] aFixedTables
RowID[] aRowIDs
Get FixedTables strQuery to aFixedTables
Move (SizeOfArray(aFixedTables)-1) to iItem
For iItem from 0 to iMax
Move (GetRowID(aFixedTables[iItem])) to aRowIDs[iItem]
Loop
Function_Return aRowIDs
End_Function
Function FixedTablesRowIDs_Serialized tTableQuery strQuery Returns String[]
Integer iItem iMax
Integer[] aFixedTables
String[] aRowIDs
Get FixedTables strQuery to aFixedTables
Move (SizeOfArray(aFixedTables)-1) to iItem
For iItem from 0 to iMax
Move (SerializeRowID(GetRowID(aFixedTables[iItem]))) to aRowIDs[iItem]
Loop
Function_Return aRowIDs
End_Function
Function FixedTablesRestore tTableQuery strQuery RowID[] aRowIDs Returns Boolean
Boolean bFound
RowID riRow
Integer iItem iMax
Integer[] aFixedTables
Get FixedTables strQuery to aFixedTables
Move (SizeOfArray(aFixedTables)-1) to iMax
For iItem from 0 to iMax
Move aRowIDs[iItem] to riRow
If (IsNullRowID(riRow)) Begin
Clear aFixedTables[iItem]
End
Else Begin
Move (FindByRowID(aFixedTables[iItem],aRowIDs[iItem])) to bFound
If (not(bFound)) Begin
Function_Return False
End
End
Loop
Function_Return True
End_Function
Function FixedTablesRestore_Serialized tTableQuery strQuery String[] asRowIDs Returns Boolean
Integer iItem iMax
RowID[] ariRowIDs
Move (SizeOfArray(asRowIDs)-1) to iMax
For iItem from 0 to iMax
Move (DeserializeRowID(asRowIDs[iItem])) to ariRowIDs[iItem]
Loop
Function_Return (FixedTablesRestore(Self,strQuery,ariRowIDs))
End_Function
Procedure _PushNoneFixedBuffers tTableQuery strQuery
Integer iItem iMax iTable
Integer[] aTables
Get NotFixedTables strQuery to aTables
Move (SizeOfArray(aTables)-1) to iMax
For iItem from 0 to iMax
Move aTables[iItem] to iTable
Send PushRecord of oRecordBufferFunctions iTable
Loop
End_Procedure
Procedure _PopNoneFixedBuffers tTableQuery strQuery
Integer iItem iMax iTable
Integer[] aTables
Get NotFixedTables strQuery to aTables
Move (ReverseArray(aTables)) to aTables
Move (SizeOfArray(aTables)-1) to iMax
For iItem from 0 to iMax
Move aTables[iItem] to iTable
Send PopRecord of oRecordBufferFunctions iTable
Loop
End_Procedure
Function TableQueryToString tTableQuery strQuery Returns String
String sTQ
Get VariantToString of oStructFunctions strQuery to sTQ
Function_Return sTQ
End_Function
Function StringToTableQuery String sTQ Returns tTableQuery
tTableQuery strQuery
tValueTree strValueTree
Get StringToValueTree of oStructFunctions sTQ to strValueTree
ValueTreeDeserializeParameter strValueTree to strQuery
Function_Return strQuery
End_Function
Function TableQuerySerializedForLog tTableQuery strQuery Returns String
String sTQ
Get TableQueryToString strQuery to sTQ
Function_Return ("V1.0,"+sTQ)
End_Function
Function _NextValueInArray String[] aValues Integer ByRef iPos Returns String
String sValue
Move aValues[iPos] to sValue
Increment iPos
Function_Return sValue
End_Function
Function _LogReadValueSource String[] aValues Integer ByRef iPos Returns _tTQValueSource
_tTQValueSource strValue
Integer iItem iMax
Get _NextValueInArray aValues (&iPos) to strValue.iTable
Get _NextValueInArray aValues (&iPos) to strValue.iColumn
Get _NextValueInArray aValues (&iPos) to strValue.iType
Get _NextValueInArray aValues (&iPos) to strValue.iLen
Get _NextValueInArray aValues (&iPos) to strValue.iDecimals
Get _NextValueInArray aValues (&iPos) to strValue.sConstant
Get _NextValueInArray aValues (&iPos) to strValue.sSQLResultSetColumnName
Get _NextValueInArray aValues (&iPos) to iMax
For iItem from 0 to iMax
Get _NextValueInArray aValues (&iPos) to strValue.aOrList[iItem]
Loop
Function_Return strValue
End_Function
Function _LogReadFilter String[] aValues Integer ByRef iPos Returns tTQFilterSegment
tTQFilterSegment strFilter
Get _LogReadValueSource aValues (&iPos) to strFilter.strLeftValue
Get _NextValueInArray aValues (&iPos) to strFilter.iComp
Get _LogReadValueSource aValues (&iPos) to strFilter.strRightValue
End_Function
Function _LogReadOrderingSegment String[] aValues Integer ByRef iPos Returns tTQOrderBySegment
tTQOrderBySegment strSegment
Get _NextValueInArray aValues (&iPos) to strSegment.bDescending
Get _NextValueInArray aValues (&iPos) to strSegment.bUppercase
Get _LogReadValueSource aValues (&iPos) to strSegment.strValueSource
End_Function
Function TableQueryDeserializedFromLog String sValue Returns tTableQuery
Integer iPos iItem iMax
tTableQuery strQuery
tTQFilterSegment strFilter
tTQOrderBySegment strSegment
String[] aValues
Send SplitString of oStringFunctions sValue (Character(10)) False False (&aValues)
If (SizeOfArray(aValues)>1) Begin
Move 1 to iPos
If (aValues[0]="V1.0") Begin
Get _NextValueInArray aValues (&iPos) to strQuery.iTable
Get _NextValueInArray aValues (&iPos) to strQuery.bNoESQL
Get _NextValueInArray aValues (&iPos) to strQuery.bForcePrefetch
Get _NextValueInArray aValues (&iPos) to strQuery.iForceIndex
Get _NextValueInArray aValues (&iPos) to strQuery.iLimit
// Filters:
Get _NextValueInArray aValues (&iPos) to iMax
For iItem from 0 to iMax
Get _LogReadFilter aValues (&iPos) to strFilter
Move strFilter to strQuery.aFilters[iItem]
Loop
// Ordering:
Get _NextValueInArray aValues (&iPos) to iMax
For iItem from 0 to iMax
Get _LogReadOrderingSegment aValues (&iPos) to strSegment
Move strSegment to strQuery.aOrdering[iItem]
Loop
End
End
Function_Return strQuery
End_Function
Procedure OnLogStatus tTableQuery strQuery
Boolean bVal
Integer iLogTable iSize iValue
String sModule sValue
Get LogTableHandle strQuery to iLogTable
If (iLogTable>0) Begin
Clear iLogTable
Get SystemTimeToString23 of oDateFunctions (SystemTimeMilliSeconds(oDateFunctions)) to sValue
Set_Field_Value iLogTable 1 to sValue
Set_Field_Value iLogTable 3 to strQuery._strControlBlock.sLogTag
Get TableQueryUser to sValue
Set_Field_Value iLogTable 4 to sValue // (TableQueryUser(Self))
Get Module_Name to sModule
Set_Field_Value iLogTable 5 to sModule
Get ModuleStartTime of oDateFunctions to sValue
Set_Field_Value iLogTable 6 to sValue
Move (SizeOfArray(strQuery._strCompiled.aJumpInValues)=0 and SizeOfArray(strQuery._strCompiled.aJumpOutValues)=0 and strQuery.iLimit=0) to bVal
Set_Field_Value iLogTable 7 to bVal // Full table scan
Set_Field_Value iLogTable 8 to strQuery._strControlBlock.iFindCount
Set_Field_Value iLogTable 9 to strQuery._strControlBlock.iSelectCount
Move (SizeOfArray(strQuery._strCompiled.aOtherConditions)) to iSize
Set_Field_Value iLogTable 10 to iSize // Unused filters
Set_Field_Value iLogTable 11 to strQuery._strCompiled.iIndex
Move (strQuery.iForceIndex<>-1) to iValue
Set_Field_Value iLogTable 12 to iValue
Set_Field_Value iLogTable 13 to strQuery.bForcePrefetch
Set_Field_Value iLogTable 14 to strQuery.iLimit
Set_Field_Value iLogTable 15 to (not(strQuery._strControlBlock.bBreakScan))
Get TableQuerySerializedForLog strQuery to sValue
Set_Field_Value iLogTable 16 to sValue
SaveRecord iLogTable
End
End_Procedure
Procedure OnComplete tTableQuery strQuery
End_Procedure
Procedure _RelateMainRecord tTableQuery ByRef strQuery
Integer iItem iItemMax
Move (SizeOfArray(strQuery.aColumnSources)-1) to iItemMax
For iItem from 0 to iItemMax
Send _RelateToTable (&strQuery) strQuery.aColumnSources[iItem].iTable
Loop
End_Procedure
Function FindRecord tTableQuery ByRef strQuery Returns Boolean
Boolean bFound bInitialize
Move False to bInitialize
If (strQuery._strControlBlock.bCompiled) Begin
If (not(strQuery._strControlBlock.bInitialized)) Begin
Move True to bInitialize // Initialize!
End
End
Else Begin
Get _CompileQuery (&strQuery) to bInitialize // If it compiles we will initialize it
End
If (bInitialize) Begin
If (_InitializeQuery(Self,&strQuery)) Begin
Move True to strQuery._strControlBlock.bInitialized
Get _FindFirstRecord (&strQuery) to bFound
End
Else Begin
Move False to bFound
End
Move True to strQuery._strControlBlock.bFirstRecord
End
Else Begin
// This is the mechanism that enforces iLimit. However, if the record set is pre-fetched
// this has already been taken care of.
if (strQuery._strCompiled._iLimit<>0) Begin
If (not(strQuery._strCompiled.bPrefetch) and ;
strQuery._strControlBlock.iSelectCount>=strQuery._strCompiled._iLimit) Begin
Move True to strQuery._strControlBlock.bBreakScan
End
End
Get _FindNextRecord (&strQuery) to bFound
Move False to strQuery._strControlBlock.bFirstRecord
End
If (bFound) Begin
If (strQuery._strControlBlock.iQueryMethod=tqDATAFLEX) Begin
// If we are in embedded mode we must find all parent related records specified by the aSelectedColumns member.
Send _RelateMainRecord (&strQuery)
End
Else Begin
// If we are in ESQL mode we expect the fetch-record mechnism to have already done this
End
End
Else Begin
If (not(strQuery.bForcePrefetch)) Begin
Send PurgeResultSet (&strQuery) // if we didn't ask for it we'll get rid of it here (if it's there at all)
End
If (_giTQLogState=2 or ( _giTQLogState=1 and strQuery._strControlBlock.sLogTag<>"")) Begin
Send OnLogStatus strQuery
End
Send _DeinitializeQuery (&strQuery) // Get rid of SQL handles (if emploid)
Send OnComplete strQuery
If (strQuery.bRestoreRecordBuffersOnQueryCompletion) Begin
Send _PopNoneFixedBuffers strQuery // restore buffers of tables that were possibly changed by the query
End
// This is where we _*ALWAYS*_ exit the FindRecord loop
End
Function_Return bFound
End_Function
Function IsFirstRecord tTableQuery strQuery Returns Boolean
Function_Return strQuery._strControlBlock.bFirstRecord
End_Function
//> Returns the current row number of the result set.
Function ResultRowNumber tTableQuery strQuery Returns Integer
If (strQuery._strCompiled.bPrefetch) Begin
Function_Return (strQuery.strResultSet._iCurrentRowPointer+1)
End
Function_Return strQuery._strControlBlock.iFindCount
End_Function
Function HasData tTableQuery strQuery Returns Boolean
Boolean bHasData
tTQOrderBySegment[] aEmpty
If (strQuery._strControlBlock.bInitialized) Begin
// If the query has already been initialized the function will return whether or
// not any records have been selected yet (this would only ever be relevant to a
// person subclassing the cTableQuery class). More relevant is the fact that this
// means that you can query HasData after loop completion and get to know if the
// loop was ever executed.
Function_Return (strQuery._strControlBlock.iSelectCount<>0)
End
Else Begin
// If the loop hasn't yet been initialized we will produce the simplest
// possible query to find out if the loop will have any data.
Move False to strQuery.bForcePrefetch // Prefetch would be stupid
Move aEmpty to strQuery.aOrdering // Do not dictate the ordering
Move 1 to strQuery.iLimit // This will prevent the complete set being built.
Move -1 to strQuery.iForceIndex // Do not force any particular index
// Dilemma! If there are no non-optimizable filters then it's faster to not use
// ESQL. But then I have to compile the query before executing the loop.
// I just haven't gotten around to that yet.
// Move True to strQuery.bNoESQL // (do not use ESQL)
Move False to bHasData
While (FindRecord(Self,&strQuery))
Move True to bHasData
Loop
End
Function_Return bHasData
End_Function
Function QueryToRowIdArray tTableQuery ByRef strQuery Returns RowID[]
Integer iTable iCount
RowID[] aRecords
Move strQuery.iTable to iTable
Move 0 to iCount
While (FindRecord(Self,&strQuery))
Move (GetRowID(iTable)) to aRecords[iCount]
Increment iCount
Loop
Function_Return aRecords
End_Function
Function DeleteRecords tTableQuery strQuery Returns Boolean
Integer iTable
String sStatement
tSQLStatementHandle strStatement
If (strQuery.iLimit<>0) Begin
Send OnError 742 "Limit not allowed on delete statement"
End
Else Begin
Send ResetOrdering (&strQuery) // Ordering irrelevant!
Send ResetResultSetColumns (&strQuery) // Resultset irrelevant!
If (_QueryMethodSQL(Self,strQuery)) Begin // ESQL
If (OpenStatementOnTable(oSQLStatementHandles,strQuery.iTable,&strStatement)) Begin
Move strStatement to strQuery._strControlBlock.strSQLStatementHandle // Is used during the generating of the SQL statement.
Send _TableRelateStatesReset (&strQuery)
Get SQLDeleteStatement strQuery Self to sStatement
Move (ToANSI(sStatement)) to sStatement
If (sStatement<>"") Begin
Send SQLExecDirect of strStatement.hHandle sStatement
End
Send CloseStatement of oSQLStatementHandles strStatement
End
End
Else Begin
Move strQuery.iTable to iTable
While (FindRecord(Self,&strQuery))
Delete iTable
Loop
End
End
Function_Return False
End_Function
Function _TableRootNameStripDriver String sRootName Returns String
Integer iPos
Move (Pos(":",sRootName)) to iPos
If (iPos>2) Begin
Move (Remove(sRootName,1,iPos)) to sRootName
End
Function_Return sRootName
End_Function
Function _TableAnalysisFileName Integer iTable Returns String
String sFolder sTableName
Get VdfFolderPath of oFileFunctions VDF_FILELIST to sFolder
Get_Attribute DF_FILE_ROOT_NAME of iTable to sTableName
Get _TableRootNameStripDriver sTableName to sTableName
Get PathStripType of oFileFunctions sTableName to sTableName
Get AppendPath of oFileFunctions sFolder (sTableName+".tq") to sTableName
Function_Return sTableName
End_Function
Function _TableAnalysisReadIndex Integer iChannel Returns _tTQIndexAnalysis
Integer iItem iMax
_tTQIndexAnalysis strIndexA
Readln iMax
For iItem from 0 to iMax
Get Readln of oFileFunctions iChannel to strIndexA.aSegmentReductions[iItem]
Loop
Function_Return strIndexA
End_Function
Function TableAnalysisRead Integer iTable tTQTableAnalysis ByRef strA Returns Boolean
Integer iChannel iItem iMax
String sFile
tTQTableAnalysis strEmpty
Move strEmpty to strA
Get _TableAnalysisFileName iTable to sFile
Get DirectInput of oFileFunctions sFile to iChannel
If (iChannel>=0) Begin
If (Readln(oFileFunctions,iChannel)="VER 1.0") Begin
Get StringToDateTime of oDateFunctions (Readln(oFileFunctions,iChannel)) DF_DATE_MILITARY to strA.dtAnalysisDateTime
Readln channel iChannel strA.nNumberOfRecords
Readln strA.sTimeToComplete
Readln iMax
For iItem from 0 to iMax
Get _TableAnalysisReadIndex iChannel to strA.aIndices[iItem]
Loop
Send CloseInput of oFileFunctions iChannel
Function_Return True
End
Else Begin
Send OnError 737 ("TQ cache file not recognized ("+sFile+")")
End
Send CloseInput of oFileFunctions iChannel
End
Function_Return False
End_Function
Procedure _TableAnalysisSaveIndex Integer iChannel _tTQIndexAnalysis strIndexA
Integer iMax iItem
Move (SizeOfArray(strIndexA.aSegmentReductions)-1) to iMax
Writeln channel iChannel iMax
For iItem from 0 to iMax
Writeln strIndexA.aSegmentReductions[iItem]
Loop
End_Procedure
Function TableAnalysisSave Integer iTable tTQTableAnalysis strA Returns Boolean
Integer iChannel iItem iMax
String sFile
Get _TableAnalysisFileName iTable to sFile
Get DirectOutput of oFileFunctions sFile to iChannel
If (iChannel>=0) Begin
Writeln channel iChannel "VER 1.0"
Writeln (DateTimeToString(oDateFunctions,strA.dtAnalysisDateTime,DF_DATE_MILITARY,True,"-"))
Writeln strA.nNumberOfRecords
Writeln strA.sTimeToComplete
Move (SizeOfArray(strA.aIndices)-1) to iMax
Writeln iMax
For iItem from 0 to iMax
Send _TableAnalysisSaveIndex iChannel strA.aIndices[iItem]
Loop
Send CloseOutput of oFileFunctions iChannel
Function_Return True
End
Function_Return False
End_Function
Function _TableAnalysisDefault Integer iTable Returns tTQTableAnalysis
Integer iIndex iItem iMax
Integer iSegment iSegments
Integer[] aIndices
Number nRecordCount nFactor
tocIndex strIndex
tTQTableAnalysis strA
Get IndexSet of oIndexFunctions iTable 3 to aIndices // 3 means all online indices
Get_Attribute DF_FILE_RECORDS_USED of iTable 0 0 to nRecordCount
Move (SizeOfArray(aIndices)-1) to iMax
For iItem from 0 to iMax
Move aIndices[iItem] to iIndex
Get IndexDefinition of oOverlapColumnFunctions iTable iIndex to strIndex
Move (SizeOfArray(strIndex.aSegments)) to iSegments
Move (nRecordCount^(1.0/iSegments)) to nFactor
Move (1.0/nFactor) to nFactor
Decrement iSegments
For iSegment from 0 to iSegments
Move nFactor to strA.aIndices[iIndex].aSegmentReductions[iSegment]
Loop
Loop
Function_Return strA
End_Function
//> The TableAnalysis returns a struct that contains information that helps the index analyzer
//> to make informed guesses about which index seeds the best. VDFxray can be used to generate
//> a small text file for each table with this information. If this is the case then function
//> TableAnalysis will read that file and return the by now much mentioned information. And if not
//> the best possible guess is calculated and returned. Actually, in addition to being returned
//> by the function it is also copied to a global cache so subsequent calls will take no time.
Function TableAnalysis Integer iTable Returns tTQTableAnalysis
tTQTableAnalysis strA
Move False to strA.bInitialized
If (SizeOfArray(_gTQaTableAnalysis)>iTable and _gTQaTableAnalysis[iTable].bInitialized) Begin
Move _gTQaTableAnalysis[iTable] to strA
End
Else Begin
If (not(TableAnalysisRead(Self,iTable,&strA))) Begin
Get _TableAnalysisDefault iTable to strA // Calculate one
End
Move True to strA.bInitialized
Move strA to _gTQaTableAnalysis[iTable]
End
Function_Return strA
End_Function
//> Information about configuration of indices for a table. A caching mechanism makes sure that
//> this configuration is not read on subsequent calls to the function.
Function TableMeta Integer iTable Returns tTQTableMeta
tTQTableMeta strM
Move 0 to strM._iIsInitialized
If (SizeOfArray(_gTQaTableMeta)>iTable and _gTQaTableMeta[iTable]._iIsInitialized<>0) Begin
Move _gTQaTableMeta[iTable] to strM
End
Else Begin
Get IndexSet of oIndexFunctions iTable 3 to strM.aIndices // 3 means all on-line indices
Move 1 to strM._iIsInitialized
Move strM to _gTQaTableMeta[iTable]
End
Function_Return strM
End_Function
Function TableIndices Integer iTable Returns Integer[]
tTQTableMeta strM
Get TableMeta iTable to strM
Function_Return strM.aIndices
End_Function
//> This majesticly named procedure clears all cached values and closes
//> any log tables that might have been opened.
Procedure ClearTableQueryMetaDataGlobalArraysAndCloseLogFiles
Integer iItem iMax iLogTable
Move (SizeOfArray(_gaTQLogTables)-1) to iMax
For iItem from 0 to iMax
Move _gaTQLogTables[iItem] to iLogTable
If (iLogTable>0) Begin
Send CloseTable of oTableAccessFunctions iLogTable
End
Loop
Move (ResizeArray(_gTQaTableMeta,0)) to _gTQaTableMeta
Move (ResizeArray(_gTQaTableAnalysis,0)) to _gTQaTableAnalysis
Move (ResizeArray(_gaTQLogTables,0)) to _gaTQLogTables
End_Procedure
End_Class // cTableQueryFunctions
Global_Variable Integer oTQ // Holder of the global singleton object.
Object _oTQ is a cTableQueryFunctions
Move Self to oTQ
// Is called by VDFXray when new WS is selected (TableQuery.pkg is used by VDFXray)
Procedure OnNewFilelistSelected
Send ClearTableQueryMetaDataGlobalArraysAndCloseLogFiles
End_Procedure
End_Object
Class cTableQueryAnalyzer is a cObject
Function _BreakLevel String[] aNewValues String[] aOldValues Returns Integer
Integer iMax iItem
Move (SizeOfArray(aNewValues)-1) to iMax
For iItem from 0 to iMax
If (aNewValues[iItem]<>aOldValues[iItem]) Begin
Function_Return iItem
End
Loop
Function_Return -1 // no break
End_Function
Procedure _IncrementBreakCounter Integer[] ByRef aBreakCounters Integer iLevel
Integer iMax iItem
If (iLevel>=0) Begin
Move (SizeOfArray(aBreakCounters)-1) to iMax
For iItem from 0 to iMax
If (iItem>=iLevel) Begin
Move (aBreakCounters[iItem]+1) to aBreakCounters[iItem]
End
Loop
End
End_Procedure
Procedure _AnalyseTableIndexAux _tTQIndexAnalysis ByRef strIndexA Integer iTable Integer iIndex Integer[] aBreakCounters
Integer iMax iItem
Number nFactor
Move (ResizeArray(strIndexA.aSegmentReductions,0)) to strIndexA.aSegmentReductions
Move (SizeOfArray(aBreakCounters)-1) to iMax
For iItem from 0 to iMax
If (iItem=0) Begin
Move (1.0/aBreakCounters[iItem]) to nFactor
End
Else Begin // 211 644 2200 11331 => 1/211, 211/644, 644/2200, 2200/11331
Move (Number(aBreakCounters[iItem-1])/aBreakCounters[iItem]) to nFactor
End
Move nFactor to strIndexA.aSegmentReductions[iItem]
Loop
End_Procedure
Procedure _AnalyseTableIndex _tTQIndexAnalysis ByRef strIndexA Integer iTable Integer iIndex
Integer iCount iLevel iMax
String[] aNewValues aOldValues
Integer[] aBreakCounters
tocIndex strIndex
_tTQIndexAnalysis strEmpty
tTableQuery strQ
Get IndexDefinition of oOverlapColumnFunctions iTable iIndex to strIndex
Move 0 to iCount
Get_Attribute DF_FILE_RECORDS_USED of iTable to iMax
Get NewQuery of oTQ iTable to strQ
Send SetOrderByToIndex of oTQ (&strQ) iIndex
While (FindRecord(oTQ,&strQ))
Increment iCount
Get IndexSegmentValueArray of oIndexFunctions iTable iIndex to aNewValues
If (iCount=1) Begin
Move (ResizeArray(aBreakCounters,SizeOfArray(aNewValues))) to aBreakCounters
Move aNewValues to aOldValues // No break on the first one
End
If (((iCount/5000)*5000)=iCount) Begin
Send OnAnalyseIndexProgress iTable iCount iMax
End
Get _BreakLevel aNewValues aOldValues to iLevel
If (iLevel>=0) Begin
Send _IncrementBreakCounter (&aBreakCounters) iLevel
End
Move aNewValues to aOldValues
Loop
Send OnAnalyseIndexProgress iTable iCount iCount
If (iCount<>0) Begin
Send _IncrementBreakCounter (&aBreakCounters) 0
End
Move strEmpty to strIndexA
Send _AnalyseTableIndexAux (&strIndexA) iTable iIndex aBreakCounters
End_Procedure
Procedure OnAnalyseTableIndex Integer iTable Integer iIndex Integer iIndexIndex Integer IndexCount
End_Procedure
Procedure OnAnalyseTable Integer iTable Integer iIndexCount
End_Procedure
Procedure OnAnalyseIndexProgress Integer iTable Integer iCount Integer iMax
End_Procedure
Procedure AnalyzeTable tTQTableAnalysis ByRef strTableA Integer iTable
Integer iItem iMax iIndex
Integer[] aIndices
Number nElapsed nRecords
tSystemTimeMS strStart strStop
tTQTableAnalysis strEmptyTable
_tTQIndexAnalysis strIndexA
Move strEmptyTable to strTableA
Get_Attribute DF_FILE_RECORDS_USED of iTable to nRecords
If (nRecords>100) Begin
Get SystemTimeMilliSeconds of oDateFunctions to strStart
Get_Attribute DF_FILE_RECORDS_USED of iTable to strTableA.nNumberOfRecords
Get IndexSet of oIndexFunctions iTable 3 to aIndices // All on-line indices
Move (SizeOfArray(aIndices)-1) to iMax
Send OnAnalyseTable iTable (iMax+1)
For iItem from 0 to iMax
Move aIndices[iItem] to iIndex
Send OnAnalyseTableIndex iTable iIndex iItem (iMax+1)
Send _AnalyseTableIndex (&strIndexA) iTable iIndex
Move strIndexA to strTableA.aIndices[iIndex]
Loop
Send OnAnalyseTableIndex iTable iIndex (iMax+1) (iMax+1)
Get SystemTimeMilliSeconds of oDateFunctions to strStop
Get SystemTimeMilliSecondsElapsed of oDateFunctions strStart strStop to nElapsed
Get MilliSecondsToTimeString of oDateFunctions nElapsed to strTableA.sTimeToComplete
Get SystemDateTime of oDateFunctions to strTableA.dtAnalysisDateTime
End
End_Procedure
Procedure DeleteAnalysis Integer[] aTables
Integer iItem iMax iFailure
String sFile
Move (SizeOfArray(aTables)-1) to iMax
For iItem from 0 to iMax
Get _TableAnalysisFileName of oTQ aTables[iItem] to sFile
If (FileExists(oFileFunctions,sFile)=1) Begin
Get DeleteFileNew of oFileFunctions sFile True to iFailure
End
Loop
End_Procedure
End_Class
Class cTableQueryTester is a cObject
// Based on a tTableQuery value strQ, TestSuite will return
// an array of all queries that should return the same resultset.
Function TestSuite tTableQuery strQ Returns tTableQuery[]
Integer iForcePrefetch iItem iMax
tTableQuery[] aQueries
Integer[] aIndices
Send ReUse of oTQ (&strQ)
Move False to strQ._strControlBlock.bCompiled
Get TableIndices of oTQ strQ.iTable to aIndices
Move (SizeOfArray(aIndices)-1) to iMax
For iForcePrefetch from 0 to 1
Move (iForcePrefetch=1) to strQ.bForcePrefetch
For iItem from 0 to iMax
Send ForceIndex of oTQ (&strQ) aIndices[iItem]
Move strQ to aQueries[SizeOfArray(aQueries)]
Loop
Loop
Function_Return aQueries
End_Function
Function CompareRowID RowID ri1 RowID ri2 Returns Integer
If (IsSameRowID(ri1,ri2)) Function_Return (EQ)
Function_Return (GT)
End_Function
Function ResultSetsIdentical RowID[] aResult1 RowID[] aResult2 Returns Boolean
Integer iMax iItem iItem2
Move (SizeOfArray(aResult1)-1) to iMax
If (iMax=(SizeOfArray(aResult2)-1)) Begin
For iItem from 0 to iMax
Move (SearchArray(aResult1[iItem],aResult2,Self,GET_CompareRowID)) to iItem2
If (iItem2=-1) Begin
Function_Return False
End
Loop
Function_Return True
End
Function_Return False
End_Function
Procedure OnNotIdentical tTableQuery strQ1 tTableQuery strQ2
End_Procedure
Function RunSuite tTableQuery[] ByRef aQueries Returns RowID[][]
Integer iItem iMax
Boolean bIdentical
RowID[] aResult
RowID[][] aMatrix // An array of result-arrays
Move (SizeOfArray(aQueries)-1) to iMax
For iItem from 0 to iMax
Get QueryToRowIdArray of oTQ (&aQueries[iItem]) to aResult
Move aResult to aMatrix[iItem]
Loop
Function_Return aMatrix
End_Function
End_Class // cTableQueryTester
//
//
//
//
//Struct tTQTestBedInput
// tTableQuery strPQ
// tTableQuery strCQ
// tTQTableRelation strRel
// String sName // Is used as part of a file name
// Boolean bNoESQL
// Boolean bRubQueries
//End_Struct
//
//Struct tTQTestBedRowID
// String sRowID
// String[] aColumnSourceValues
// tTQTestBedRowID[] aChildRowIDs
//End_Struct
//
//Struct tTQTestBedOutput
// tTQTestBedInput strInput
// tTQTestBedRowID[] aResultRowIDs
// Number nExecMs
//End_Struct
//
//Class cTableQueryTestBed is a cObject
// Function RunTest tTQTestBedInput strIn Returns tTQTestBedOutput
// Integer iChildResultIndex iParentResultIndex iParentMaxColumn iChildMaxColumn iItem
// tTQTestBedOutput strOut
// _tTQValueSource strValueSource
//
// Move strIn.bNoESQL to strIn.strPQ.bNoESQL
//
// Move strIn to strOut.strInput
// Move 0 to iParentResultIndex
//
// Move (SizeOfArray(strIn.strPQ.aColumnSources)-1) to iParentMaxColumn
// Move (SizeOfArray(strIn.strCQ.aColumnSources)-1) to iChildMaxColumn
//
// While (FindRecord(oTQ,&strIn.strPQ))
// Move (SerializeRowID(FileRowId(strIn.strPQ.iTable))) to strOut.aResultRowIDs[iParentResultIndex].sRowID
// For iItem from 0 to iParentMaxColumn
// Move strIn.strPQ.aColumnSources[iItem] to strValueSource
// Get ResultColumnValue of oTQ (&strIn.strPQ) strValueSource.iTable strValueSource.iColumn to strOut.aResultRowIDs[iParentResultIndex].aColumnSourceValues[iItem]
// Loop
//
// Move 0 to iChildResultIndex
// While (FindRecord(oTQ,&strIn.strCQ))
// Move (SerializeRowID(FileRowId(strIn.strCQ.iTable))) to strOut.aResultRowIDs[iParentResultIndex].aChildRowIDs[iChildResultIndex].sRowID
// For iItem from 0 to iChildMaxColumn
// Move strIn.strCQ.aColumnSources[iItem] to strValueSource
// Get ResultColumnValue of oTQ (&strIn.strCQ) strValueSource.iTable strValueSource.iColumn to strOut.aResultRowIDs[iParentResultIndex].aChildRowIDs[iChildResultIndex].aColumnSourceValues[iItem]
// Loop
// Increment iChildResultIndex
// Loop
// Increment iParentResultIndex
// Loop
//
// Get SystemTimeMilliSecondsElapsed of oDateFunctions strIn.strPQ._strControlBlock.strStartTime strIn.strPQ._strControlBlock.strStopTime to strOut.nExecMs
//
// Function_Return strOut
// End_Function
//
// Procedure AddAutoRelation tTQTestBedInput ByRef strIn
// Send AddTableRelationAutoFixed of oTQ (&strIn.strCQ) strIn.strCQ.iTable strIn.strPQ.iTable
// End_Procedure
//End_Class