Follow me

Most Recent Post

Upgrade 101

Please read the article posted on Medium using the link below: Upgrade 101

Popular Posts this year

Tuesday, May 1, 2018

Utilizing MOCA Query Hooks


RedPrairie/ JDA introduced Hooks in MOCA Toolset.  The Hook being described here is referred as the “QueryHook” (See MOCA Developer Guide for details).  This feature allows to look for a given SQL before it is sent to the database engine and manipulate the SQL itself or possibly its result set.

Caution: As mentioned in the MOCA Developer’s guide also, “This hook should be very heavily evaluated before its usage is implemented”.  As certainly a wrong implementation can have unpredictable results.

Oracular has introduced a solution that simplifies this process and allows a client’s development team to change an SQL at run-time to their advantage.  This solution takes the complexity out of writing a new Java method for the QueryHook and instead provides a simple Text File where Query Patterns can be stored.

Why do we need to change SQL this way?

JDA MOCA platform allows great flexibility in providing mechanism for overriding standard functionality and still keeping the upgrade path open.  However, many times the SQL performance of a given query requires it to be changed.  Introducing an index may not yield the performance plan that one is looking for and the only viable option is to be able to rewrite the query.
If the Query happens to be in a Local Syntax command, the user can easily copy the command to an LES folder and change the SQL to their liking.  In doing so, they have closed the door to this command being fixed by JDA (as this is a complete override), but the solution and performance goals have been achieved.

Many times however, the SQL that needs to be tuned is inside some C/ Java program or part of RDT code.  These SQL commands can only be manipulated using the QueryHook option.

Oracular's Solution

MOCA QueryHook can be implemented by anyone with knowledge of Java and they can write their own Moca Hook to take advantage of this.

Oracular provided a simple usage for this for our developers as well as our clients where a file sitting in $LES\data folder contains the Query Pattern to look for and its replacement.  This allows for a simple process where a new SQL pattern can be introduced with much ease.


The following section will explain how to use the Oracular Solution.

Basic Components

Anyone needing to take advantage of this feature from Oracular will need the Mocahooks.jar file to exist in their $LES\lib folder and query_replacements.txt file to exist in their $LES\data folder.

Structure of Query Replacement File

The Query Replacement File has three components for each SQL that needs to be manipulated:

  • Leading edge of the SQL to search for – This must be an exact match.  This string does not allow for any regular expressions for white space.  So, number of spaces, new line characters etc. are all important to be specified exactly
  • Regular Expression String to match the rest of the SQL – This Regular expression allows to match and parse the rest of the SQL.  This expression serves two purposes:

o   Fully qualify the SQL
o   Find Groups of data inside the SQL to be used during the replacement (important concept that will be explained in the example illustration)
  • SQL to replace with – This is the new SQL that is executed instead.  The Groups identified in the search can be used in the replacement SQL


The use and need of replacements is generally performance related.  However, it can be used to manipulate result set if needed.  However, if result set is being manipulated, be sure to verify that downstream processing can handle any changes to the result set.

Regular Expressions

The search string uses Regular Expressions (Regex) for searching through the SQL string.  Please note that Regular Expressions are used in many variations and have unique structures and implementations based on the usage.  For our purpose, only the Java Regular Expressions can be used (not Posix, PHP etc.)

The following are the commonly utilized expressions for our purpose:
  • (?i)  - Starting the expression with this, specifies to ignore the case during the search
  • (?s) – This modifier allows to ignore New-Line when searching for any character
  • If both of the above modifiers are needed then only one ? in the beginning is needed (?is)
  •  ^ - Asserts position as the start of the string
  • \s+ - Look for white space.  But at least one.  Commonly this will be used before SQL key words as there needs to be at least one space before them (like from or where)
  • \s* - Look for white space.  None is also OK
  •  \( or \) – Parenthesis are part of Regex.  So if you need to specify a parenthesis in your expression, always precede that with a \
  • (.*?) – Look for a group of characters.  Any character can match in this and whatever matches becomes a group (see below on using groups).  Groups are numbered from left to right.  So the first instance of (.*?) is group one ($1), next is Group two ($2) and so one
  • .* - Look for any character to match
  • Anything else mentioned in the expression is taken as is.  So, keywords like select, from, or operators like = etc. are to be specified as is
Please note that this is just a brief list of commonly needed search operators.  Based on your specific need, you may need to use more.

If you need to validate your expression, you can use the site below:




In this site you can provide your regular expression string in the first field and the select statement you are searching for in the next field and it will verify for you if the match is going to occur or not.  Furthermore, it provides all the groups for you also.  See illustration below.


This screen also shows the explanation of your expressions:

Notice Group 1 is what the expressions was looking for first as (.*) and it is also color coded for your convenience.

Generally speaking the groups are used for all variable data that may possibly come in the SQL string.  As this data cannot be forecasted ahead of time, so the search string must be able to ignore it during the search, but still use it later when rebuilding the new string.

So, in this illustration the group data involves part of the SELECT statement and the variable data in the WHERE clause.

SQL Statement from the Trace File

It is very important to find the proper statement in the Trace file for utilization in the QueryHook.  JDA trace files show an SQL statement in three different forms:

  • The original form based on the MOCA command sending it
  •  The XLATE modification where Bind Variables are introduced
  • The modified statement set to the SQL engine

For QueryHook purposes, we must utilize the XLATE version of the SQL.  Any other version may not properly yield a good search pattern and match.  The following is an illustration of how SQL takes different forms in the trace file:

Original SQL:


Executing SQL: select distinct          lodnum,          decode('lodnum', 'subnum', subnum, 'dtlnum',                 subnum, 'x') subnum,          decode('lodnum', 'dtlnum',                 dtlnum, 'x') dtlnum     from inventory_view     where (('lodnum' = 'lodnum' and lodnum = :var_lodnum)              or ('subnum' = 'lodnum' and subnum = :var_lodnum)              or dtlnum = :var_lodnum )

XLATE Version:

XLATE: select distinct          lodnum, (case when :q0 = :q1 then  subnum when :q0 = :q2 then                  subnum else  :q3 end) subnum, (case when :q4 = :q5 then                  dtlnum else  :q6 end) dtlnum     from inventory_view     where ((:q7 = :q8 and lodnum = :var_lodnum)              or (:q9 = :q10 and subnum = :var_lodnum)              or dtlnum = :var_lodnum )

UNBIND Version Sent to SQL Engine:

UNBIND: select distinct          lodnum, (case when 'lodnum' = 'subnum' then  subnum when 'lodnum' = 'dtlnum' then                  subnum else  'x' end) subnum, (case when 'lodnum' = 'dtlnum' then                  dtlnum else  'x' end) dtlnum     from inventory_view     where (('lodnum' = 'lodnum' and lodnum = '00100538930017771875')              or ('subnum' = 'lodnum' and subnum = '00100538930017771875')              or dtlnum = '00100538930017771875' )

Notice that the original version of the statement has a DECODE in it.  Then XLATE converted that to CASE as this was being passed to SQL Server, and then the UNBIND version added all the values for bind variables.

Illustration Example

For sake of illustration I will share two examples.  In Microsoft SQL, there is a common issue where any statement with OR does not yield a good query plan.   This statement if converted to a UNION provides a far better execution plan and performance.  Hence any such instances of SQL statements will be good candidates for the QueryHook.

Converting an "OR" to a "UNION"

The SQL mentioned above is the illustration for converting the OR statement to a UNION.  However, to keep the SQL conversion database independent, the search string did not want to assume that a DECODE will be converted to a CASE statement.  So, the search pattern is as follows:
select distinct          lodnum
$$$$$
(?is)select\s+distinct\s+lodnum\s*,(.*)from\s+inventory_view\s+where\s+\(\(\s*(.*?)\s*=\s*(.*?)\s+and\s+lodnum\s*=\s*(.*?)\s*\)\s+OR\s+\(\s*(.*?)\s*=\s*(.*?)\s+and\s+subnum\s+=\s+(.*?)\s*\)\s+OR\s+dtlnum\s+=\s+(.*?)\s*\)\s*$
$$$$$
select distinct lodnum,
$1
   from inventory_view
  where ($2 = $3
    and lodnum = $4)
  union
 select distinct lodnum,
 $1
   from inventory_view
  where ($5 = $6
    and subnum = $4)
  union
 select distinct lodnum,
 $1
   from inventory_view
  where dtlnum = $4

Explanation

  • The first line is the pattern that needs to match exactly

o   Notice the number of spaces between distinct and lodnum
o   This needed to be specified as this because the XLATE version illustrated above shows in the same manner
o   Hint: Copy the text directly from the Trace file and place it here

  • After the $$$$$ comes the second portion of the QueryHook where the SQL is parsed based on the Regular Expression provided

o   It starts with (?is) to specify to ignore case during the pattern matching and ignore new line
o   After looking for “select”, it says \s+ meaning there should be at least one white space but more is OK
o   After “distinct” it does the same to say \s+ until “lodnum”.  Now that we are using regular expressions, we don’t need to worry about all the spaces between “distinct” and “lodnum
o   After that we have \s* till comma.  So, it can be any number of white space, including none
o   Then comes the first group (.*).  Note that any search criteria when enclosed in () becomes a group.  As mentioned before, this QueryHook did not need to worry about DECODE vs. CASE, so it is saying that after the “lodnum,” till “from”, any text is fine.  However, I want that text to be in my group ($1 in this case), as I want to use it in my Query Rewrite
o   After this, it looks for “From”, then “inventory_view”, then “where
o   After that it starts to look for more Groups of data, as the XLATE was showing that these are possible bind variables.  We need to make sure that the query will match, regardless of how the parameters are passed to it.
o   The Search Pattern terminates with $

  • After the next $$$$$ comes the new SQL

o   In this section, you are allowed to write any valid SQL statement
o   It may or may not have relevance to the original SQL (as illustrated in the next example)
o   However, in most cases you will be rewriting the query to yield similar result set, except in a faster manner
o   So, in this illustration the OR was turned into a three-way UNION
o   The first part of the UNION utilizes the first part of the OR, and so on for the next two portions
o   Important part is the utilization of the groups:
§  Note that $1 was all the text matched after lodnum, till the “from”.  So, in our rewrite we use it as is
§  The rest of the groups need to be utilized properly when the UNION is being expanded so the proper group is utilized where needed
§  Since the original statement only provides the value for the Load Number first in $4, it gets used throughout.  However, the same statement could have been written as:
·       Subnum = $7
·       Dtlnum = $8

After this QueryHook is introduced, the Trace Shows the Following:

Converting a statement to a Completely Different One:

Another possibility is that a problem statement due to performance, needs to be rewritten, but the functionality is not being used in your respective implementation.  So even though you could go through the exercise of a rewrite, it will still take precious seconds to execute, yet to produce no benefit.

So, in this case, simply returning a result set from DUAL yields a faster execution time.
Caution: This use should be utilized with extreme care, as turning the functionality on in the future will not work as we have changed the statement completely using the QueryHook
select distinct trnsp_mode.palletctl_flg
$$$$$
^select\s+distinct\s+trnsp_mode.palletctl_flg\s+from\s+pckwrk\s*,\s*shipment\s*,\s*stop\s*,\s*car_move\s*,\s*cardtl\s*,\s*trnsp_mode\s*,\s*wrkque\s+where\s+shipment.ship_id\s*=\s*pckwrk.ship_id\s+and\s+shipment.stop_id\s*=\s*stop.stop_id\s+and\s+stop.car_move_id\s*=\s*car_move.car_move_id\s+and\s+car_move.carcod\s*=\s*cardtl.carcod\s+and\s+\(wrkque.wrkref\s*=\s*pckwrk.wrkref\s+or\s+wrkque.list_id\s* =\s*pckwrk.list_id\)\s+and\s+wrkque.wh_id\s*=\s*pckwrk.wh_id\s+and\s+wrkque.wh_id\s*=\s*(.*?)\s+and\s+wrkque.reqnum\s*=\s*(.*?)\s*and\s*trnsp_mode.trnsp_mode\s*=\s*nvl\s*\(car_move.trans_mode\s*,\s*cardtl.cartyp\)$
$$$$$
select  0 as palletctl_flg from dual

Explanation

  • The first line shows the predicate to match
  • The line after the $$$$$ has the regular expression to match.  This is complex SQL so the regular expression is quite long.  However, with the knowledge of each search patter it can be understood
  • After the next $$$$$ comes the replacement SQL.  Notice that this is simply being replaced with a select from DUAL.  We did make sure that the correct column is returned so the result set is not compromised for the downstream logic.  But how the 0 was returned did not matter and since Pallet Control logic is not being used, this rewrite yielded the best performance in this case.

Conclusion

The purpose of this blog entry was to illustrate to the reader the important and useful feature of MOCA and the available QueryHook.  Even though that caution must be exercised, this provides a valuable way to fine tune standard SQLs which JDA may not even be able to provide a fix for since the given SQL may only be working poorly in your specific case only.  This was the purpose of the two illustrations also, to emphasize that these SQLs would not be changed by JDA as they are optimized from their perspective. 

2 comments:

  1. It is a brilliant concept and always keeps the developer interested.Learn a lot from the article and implemented some hooks for client!

    ReplyDelete
  2. disppoFniaru Joel Watkins link
    tiechamcandpa

    ReplyDelete