* * COMPARATIVE.AQL: Converts Comparator AQL queries * Tony Gravagno, Nebula Research and Development * http://Nebula-RnD.com * * Open Source v1.0 for D3 : 25 june 2008 * * Please e-mail comments, enhancements, and other platform-specfic * versions to Freeware@Nebula-RnD.com. * * Updates may be available at http://Nebula-Rnd.com/freeware * * Responsibility for implementation and use of this program rests * entirely with the user. Nebula R&D will not be liable for any * time, data, financial, or other loss incurred as a result of * execution of this program. This software is distributed as-is * with no warranties or guarantees, explicit or implied. * *************************************************************** * Description: * This program allows D3 and possibly other MV environments to * compare the values of two Attribute Defining Items (ADI's) at * runtime as part of the selection criteria. Traditionally an * ADI can only be compared to a literal. This code creates new * ADI's that do the comparison, and it modifies the TCL command * to use the new comparitor definition. *************************************************************** * Usage: * Cverb filename WITH def1 op def2 ... *************************************************************** * Examples: * CSORT PRODUCT WITH SELLPRICE < COST BY NAME * CSSELECT PRODUCT WITH NEW.NAME # OLD.NAME BY NEW.NAME *************************************************************** * Possible Enhancements - or ideas for another module: * 1) Extend with EVAL function. Examples: * WITH PRICE > EVAL(PRICE.LAST.MONTH + 10%) * WITH EVAL(CONTACT[1,3]) = "BOB" * WITH EVAL(OCONV(SALESMAN,'G1 1')) = OWNER.LAST.NAME * 2) Check MD and DICT to see if text references an ADI, and if * not, consider it to be a literal which would otherwiser be * in quotes. * * Required Enhancements: * 1) Make sure programs with Capturing and Returning clauses * work properly under all conditions. * 2) Test in procs and other places where stack is pushed to * make sure that a BASIC program doesn't create issues where * an AQL-class command is expected. *************************************************************** * Setup: * Until DICT operations are supported add this item to * the MESSAGES (ERRMSG) file: * ID 'U9001' * Atb 1: * E Comparative AQL does not yet support operations on DICT. * * Compile-Catalog this program in any file. Copy the catalog * entry to the following verbs: * CSORT CLIST CSELECT CSSELECT CSORT-LABEL CLIST-LABEL * This code gets executed, then cuts off the first 'C' to * execute the actual TCL command. There is no need to replace * any standard MV verb. *************************************************************** * Notes: * The following command types will not be processed properly: * * 1) Multiple implied conditionals: * WITH PRICE < COST AND WITH STATE IA IL OH WI * * 2) Conditional output specs * This is valid: * WITH PRICE < "100" NAME PRICE COST > "10" * This is not: * WITH PRICE < COST NAME PRICE COST > COST.LAST.MONTH * * 3) Care must be taken to make sure that literals are enclosed * in double quotes, or not conflicting with this code. * For example: * WITH NAME > BOB * is not the same as: * WITH NAME > "BOB" *************************************************************** * * Please be sure to include comments for changes, note * conventions being used for Mod 1. * Author: Tony Gravagno (TG) * Contributors: xxxxxxxxxxxx * * Mod-000 TG 080626 Written * Mod-001 TG 080627 Stop parsing at defined connectives * This requires the selection criteria to be * the first in the statement. That is: * verb WITH ad BY ... * Sorting and output specs must follow. * Mod-00n XX yymmdd text.... * * *************************************************************** TCL = TRIM(SENTENCE()) FNAME = FIELD(TCL,' ',2) FPOS = 2 IF FNAME = 'DICT' THEN ; * access statement is against dict DNAME = 'MD' ; * definitions in md for dict file FNAME = FIELD(TCL,' ',3) FPOS = 3 * Using new definitions created in the MD doesn't seem to * be working. Operations on Dict are not yet supported. STOP "U9001" END ELSE DNAME = 'DICT ':FNAME ; * definitions in dict END FPOS += 1 ; * start with first word after file name OPEN DNAME TO DICT ELSE STOP 201,DNAME WORDS = DCOUNT(TCL,' ') *************************************************************** * * Create the list of operators that determine whether * any trio of values is a comparison. Example: * verb file WITH aaa LE bbb * We need to check for that LE * * This is the list of valid TCL operators TEMP = '< LT BEFORE' TEMP := ' > GT AFTER' TEMP := ' = EQ' TEMP := ' # NE NOT' TEMP := ' >= GE <= LE' OPERATORS = SWAP(TEMP,' ',@VM) * * All of those operators have an equivalent in the A-Correlative. * Just in case one of the values cannot be used in a correlative, * create a sublist of operators that is the SAME as the above. * TEMP = '< < <' TEMP := ' > > >' TEMP := ' = =' TEMP := ' # # #' TEMP := ' >= >= <= <=' SAME.OP1 = SWAP(TEMP,' ',@VM) * * Finally, we're creating dict items that include the operator * in the ID. We don't want to use the symbol so we use a more * verbose form. TEMP = '_LT_ _LT_ _LT_' TEMP := ' _GT_ _GT_ _GT_' TEMP := ' _EQ_ _EQ_' TEMP := ' _NE_ _NE_ _NE_' TEMP := ' _GE_ _GE_ _LE_ _LE_' SAME.OP2 = SWAP(TEMP,' ',@AM) * *************************************************************** * * MOD[1] START * * When we see these words we are done scanning DONE.WORDS = 'HEADING FOOTING SAMPLING' DONE.WORDS := ' COL-HDR-SUPP HDR-SUPP ID-SUPP' * Add more Done.Words here as required - there are lots. * The only harm in missing them is that we'll scan into them, * but this isn't a problem unless an Operator word is found * in a header/footer or in output specs. * Example of a problem header: * HEADING "Products where Type=1 PaGE 'PNCL'" DONE.WORDS = SWAP(DONE.WORDS,' ',@VM) * * MOD[1] END * *************************************************************** SUBS = '' DONE = 0 FOR W = FPOS TO WORDS UNTIL DONE ; * MOD[1] Check for Done WORD1 = FIELD(TCL,' ',W) * If we find a Done word then stop processing LOCATE(WORD1,DONE.WORDS,1;POS) THEN DONE = 1 END ELSE * We're not done, look at the block of Words 1,2,3 WORD2 = FIELD(TCL,' ',W+1) WORD3 = FIELD(TCL,' ',W+2) * Ignore normal literals IF WORD3[1,1] # \"\ THEN * Is the second word an operator between two ADI's? LOCATE(WORD2,OPERATORS,1;POS) THEN * Create a new dict item ADI = SWAP("S,0,,,,,,,R,1", "," ,@AM) CORR = "A;IF N(":WORD1:") " CORR := SAME.OP1<1,POS> CORR := " N(":WORD3:") THEN '1' ELSE '0'" * The comparator is in the Correlative = atb8 ADI<8> = CORR * Write the new dict item WRITE ADI ON DICT, WORD1:(SAME.OP2):WORD3 * Create a substitution string so that we can * replace the new ADI into the command line * when we're all done. TEMP = WORD1:' ':WORD2:' ':WORD3 TEMP<1,2> = WORD1:(SAME.OP2):WORD3:\ = "1"\ SUBS<-1> = TEMP * Skip from word1 to word after word3 W += 2 END END END NEXT W * * Replace all word1,2,3 sequences with new ADI's FOR W = 1 TO DCOUNT(SUBS,@AM) TCL = SWAP(TCL,SUBS,SUBS) NEXT W *************************************************************** * * Remove the leading "C" that got us into this mess TCL = TCL[2,99999] CRT TCL EXECUTE TCL RETURNING ERR * *************************************************************** * * Remove the dict items that we created, * only if there is no error. IF ERR # 166 THEN FOR W = 1 TO DCOUNT(SUBS,@AM) DELETE DICT,SUBS NEXT W END * ***************************************************************