public final class FQLPreprocessor extends java.lang.Object implements HQLParserTokenTypes
true in order to work around a
defect in the ORM's FQL parser which disallows unary logical conditions (TODO: is this still the case?).
checkError() function. The first
parameter to this function is initError(false), and the
second parameter is the expression requiring error handling. The
criteria are:
The call to initError(false) initializes the error
handler for the current checkError() scope. The
execution of the expression may or may not report an error to the
error handler. The checkError() function checks the
error handler's current scope for an error and handles it as
necessary.
Furthermore, each instance of this class provides the necessary contextual information required by the enclosing query to properly embed the rewritten where clause into an overall FQL statement.
For example, the FWD environment allows the following syntax:
from SomeDMOImpl as alias
where alias.propertyABC[5] = ?
as shorthand for something like:
from SomeDMOImpl as alias
join alias.composite32 as alias_composite32
where alias_composite32.propertyABC = ? and index(alias_composite32) = 5
The former syntax is not directly supported by Hibernate and so the where
clause alias.propertyABC[5] = ? is expanded to the latter
form internally, within the P2J persistence framework.
This rewriting is only necessary in the particular situation where a DMO class contains a list of composite elements, and a where clause references a property of such a composite element. The above example corresponds to a Hibernate mapping of:
...
[class name="SomeDMOImpl" ...]
...
[list name="composite32" ]
...
[composite-element class="SomeDMOImpl$Composite32"]
[property name="propertyABC" ... /]
...
[/composite-element]
[/list]
...
[/class]
...
Allowing the shorthand syntax hides the complexity of the DMO implementation underlying the Hibernate mapping, and prevents the public API of the persistence runtime from becoming more complicated. The latter point is made because, in addition to the expansion of the where clause, one or more ANSI-style joins will be added to the overall FQL statement, and the order of query substitution parameters may be changed. The following service methods are provided to enable enclosing queries to embed the preprocessed where clause in the FQL statement, and to organize substitution parameters in the proper order:
A where clause is preprocessed during construction of an instance of this class. Parsing errors are logged, but are otherwise ignored. Ultimately, the ORM will report an FQL problem if the input where clause is invalid.
| Modifier and Type | Class and Description |
|---|---|
private static class |
FQLPreprocessor.CacheKey
Instances of this class are used as keys to cache instances of
HQLPreprocessor. |
(package private) static class |
FQLPreprocessor.PropertyMatch
Object which contains information about a single comparison of a database property to a
substitution parameter or literal value.
|
static class |
FQLPreprocessor.PropertyPair
A public container for storing a pair of properties.
|
static class |
FQLPreprocessor.SessionAttr
Mutable SESSION attributes added to the UDF calls
|
private static class |
FQLPreprocessor.UdfNamesHolder
Holds the set of UDFs synthetic names.
|
| Modifier and Type | Field and Description |
|---|---|
private java.util.LinkedHashSet<java.lang.String> |
ansiJoins
FQL subexpressions to perform ANSI joins of associated lists
|
private static LRUCache<java.lang.String,HQLAst> |
astCache
Cache of parsed ASTs representing where clauses
|
private java.util.Map<java.lang.String,RecordBuffer> |
bufferMap
Temporary map of dmo alias names to record buffers
|
private static LRUCache<FQLPreprocessor.CacheKey,FQLPreprocessor> |
cacheNoArgs
Cache of prepared instances, indexed by Persistence and where clause
|
private static LRUCache<FQLPreprocessor.CacheKey,FQLPreprocessor> |
cacheWithArgs
Cache of prepared instances, indexed by Persistence and where clause
|
private Database |
database
Database associated with this object
|
private java.util.Deque<RecordBuffer> |
defaultBuffers
The stack of default buffers when sub-SELECTs are processed.
|
private boolean |
dependsOnSessionAttribute
Flag indicating that the query depends on mutable SESSION attribute(s).
|
private java.lang.String |
dropAlias
DMO alias to drop during FQL where clause generation or
null if none |
private java.util.LinkedList<java.lang.String> |
dropAliases
The stack of original aliases names when sub-SELECTs are processed.
|
private java.util.List<java.lang.String> |
earlyPublishEntities
List of entities which publish uncommitted changes prematurely
|
private boolean |
findByRowid
Flag the query that it should performed on recid/rowid lookup table.
|
private java.lang.Long |
findByRowidValue
The actual
rowid value that is to be loaded if it is a constant detected in
fql preprocessing. |
private FQLExpression |
fql
FQL-compliant where clause (possibly rewritten)
|
private static java.lang.String |
GUARDED
Prefix for guarded versions of UDFs
|
private boolean |
hasContains
Flags any
contains method encountered while processing the predicate. |
private boolean |
inline
Whether substitution parameter inlining is permitted/was performed
|
private boolean |
inlinedTernary
Flag indicating if a ternary then/else was removed based on substitution parameter value,
thus caching will not be possible.
|
private static CentralLogger |
LOG
Logger
|
private boolean |
nonConstantContains
Flag indicating that the FQL has a "contains" keyword with a non-constant expression.
|
private static FQLPreprocessor |
NOP_INSTANCE
A do-nothing instance for empty where clauses
|
private static java.util.Map<FunctionKey,java.lang.String> |
overloadedFunctions
Map of FQL function keys to overloaded SQL (unique) function names
|
private ParameterIndices |
paramIndices
Mapping of index positions for substitution parameters after rewrite
|
private java.util.List<FQLPreprocessor.PropertyMatch> |
propertyMatches
List of property matches detected in a where clause
|
private java.util.BitSet |
queryProperties
The set of properties used in the query predicate.
|
private java.lang.String |
replacementAlias
If not
null the dropAlias is forced instead of dropping it. |
private java.util.LinkedList<java.lang.String> |
replacementAliases
The stack of unique aliases for the outer sub-SELECTs are processed.
|
private java.util.Map<java.lang.String,java.util.Set<java.lang.String>> |
restrictionProperties
Map of properties used by this where clause, keyed by DMO entity
|
private java.util.List<FQLPreprocessor.PropertyPair> |
substPairs
The substitution pairs.
|
private static HQLAst |
TERNARY_COMPARE_EXP_TEMPLATE
The template for the normalized ternary which was used in a comparison expression.
|
private static HQLAst |
TERNARY_LOGICAL_EXP_TEMPLATE
The template for the normalized ternary which was used in a logical expression.
|
private UniqueIndexLookup |
uniqueIndexLookup
Helper that helps indentifying if this where clause is in fact a unique index look-up.
|
ALIAS, AND, AS, BOOL_FALSE, BOOL_TRUE, CASE, CAST, COMMA, CONCAT, DEC_LITERAL, DIGIT, DIVIDE, DMO, DOT, ELSE, END, EOF, EQUALS, ESCAPE, FROM, FUNCTION, GT, GTE, IN, INDEX, IS, IS_NULL, JOIN, LBRACKET, LETTER, LIKE, LONG_LITERAL, LPARENS, LT, LTE, MINUS, MULTIPLY, NOT, NOT_EQ, NOT_NULL, NULL, NULL_TREE_LOOKAHEAD, NUM_LITERAL, OR, PLUS, PROPERTY, RBRACKET, RPARENS, SELECT, SQL_TYPE, STRING, SUBSCRIPT, SUBSELECT, SUBST, SYM_CHAR, SYMBOL, TERNARY, THEN, UN_MINUS, VALID_1ST_IDENT, VALID_SYM_CHAR, WHEN, WHERE, WS| Modifier | Constructor and Description |
|---|---|
private |
FQLPreprocessor(Database database,
java.lang.String where,
java.util.Map<java.lang.String,RecordBuffer> bufferMap,
java.lang.Object[] parameters,
FqlType[] paramTypes,
java.lang.String[] referenceSubs,
Dialect dialect,
boolean overload,
boolean inline,
java.lang.String dropAlias,
java.lang.String replacementAlias,
boolean informational,
java.util.Set<java.lang.String> indexedFields)
Constructor which accepts an unprocessed where clause.
|
private |
FQLPreprocessor(java.util.List<RecordBuffer> bound,
java.util.List<RecordBuffer> definition,
java.lang.String where,
boolean singleBuffer)
Translate this
where clause to use the bound DMO's alias and property names, instead the
definition (conversion-time) alias and property names. |
| Modifier and Type | Method and Description |
|---|---|
private void |
addRestrictionProperty(java.lang.String entity,
java.lang.String property)
Add the given property name to the set of properties used by this where
clause as restriction criteria.
|
private java.util.Map<HQLAst,java.lang.String> |
analyzeComposites(HQLAst root)
Analyze the AST generated for the FQL expression, identifying those
subtrees which are targets for conversion.
|
private void |
annotateParameters(HQLAst root,
FqlType[] paramTypes)
Walk the FQL AST, annotating query substitution parameter nodes with their data types as they are
visited.
|
(package private) java.util.Iterator<java.lang.String> |
ansiJoins()
Get an iterator on the ANSI-style join subexpressions which must be
merged by the enclosing query into the overall FQL query statement, in
order to properly join associated composite element lists (if any).
|
private HQLAst |
augmentForUnknownValue(HQLAst root,
Dialect dialect)
Walk the tree, which has been simplified/rolled-up
simplifyUnknowns(Set) by now,
and augment certain binary sub-expressions to match Progress' unknown value semantics. |
private boolean |
checkFindByRowid(HQLAst root)
Analyze the query preprocessed tree and checks if it represents a simple ROWID/RECID lookup.
|
private UniqueIndexLookup |
checkUniqueFind()
Check if the where clause is a look-up over a unique index.
|
private void |
collectPropertyMatches(HQLAst root)
Walk the where clause AST and collect all simple property matches.
|
private static HQLAst |
createAstNode(int type,
java.lang.String text,
HQLAst parent)
Utility method for creating a designed HQLAst node.
|
private ParameterIndices |
createParameterIndices(HQLAst root,
java.lang.Object[] parameters)
Create the mapping of substitution parameters to their corresponding
substitution placeholders in the rewritten FQL where clause.
|
private void |
duplicateAugmentedOperand(HQLAst source,
HQLAst parent)
Duplicate the given source operand node and graft the duplicate under
parent. |
private FQLExpression |
emit(HQLAst root,
boolean translate)
Emit the
restructured where clause AST into an infix notation expression, which is
compliant with the FQL syntax supported by the ORM. |
private HQLAst |
fixEmptyContains(HQLAst root,
java.lang.Object[] parameters)
Replaces
contains function node with false is the second argument is an empty string. |
private java.lang.String |
generateJoin(java.lang.String compName,
java.lang.String alias,
java.lang.String verboseAlias)
Generate a single FQL subexpression for an ANSI-style join, which joins
a "master" DMO record with an associated list of composite elements.
|
private java.util.LinkedHashSet<java.lang.String> |
generateJoins(java.util.Map<HQLAst,java.lang.String> targets)
Define the set of ANSI-style join statements required to join the
"master" DMO with one or more associated lists of composite elements.
|
static FQLPreprocessor |
get(java.lang.String where,
Database database,
Dialect dialect,
RecordBuffer[] buffers,
java.lang.Object[] parameters,
java.lang.String[] referenceSubs,
boolean inline,
java.lang.String dropAlias,
java.lang.String replacementAlias,
boolean informational,
java.util.Set<java.lang.String> indexedFields)
Factory method which accepts an unprocessed where clause, the database
instance and the database dialect associated with the enclosing query,
the DMO implementation class, and the substitution parameters, if any,
for the query.
|
private java.lang.Iterable<HQLAst> |
getDenormalizedFields(HQLAst root)
Get list of denormalized fields with extent for where clause AST.
|
(package private) java.util.List<java.lang.String> |
getEarlyPublishEntities()
Retrieve the list of DMO entity names which will trigger the premature
publication of uncommitted changes across sessions, once the query
containing this preprocessor's underlying where clause is executed.
|
long |
getFindByRowid(java.lang.Object[] queryParams)
If this query is a ROWID/RECID lookup, get the record ID we are looking for.
|
(package private) FQLExpression |
getFQL()
Get the (possibly rewritten) where clause, which is compliant with FQL syntax.
|
(package private) ParameterIndices |
getParameterIndices()
Get the
ParameterIndices object associated with this query. |
(package private) java.util.List<FQLPreprocessor.PropertyMatch> |
getPropertyMatches()
Get an unmodifiable list of the property matches collected for this where clause.
|
java.util.BitSet |
getQueryProperties()
Obtains the list of properties the preprocessed FQL is based on.
|
(package private) static java.lang.String |
getRegisteredFunction(Database database,
java.lang.String fqlFunction,
java.lang.Class<?>[] paramTypes)
Returns the SQL name of the user-defined function previously registered by
registerFunction(Database, String, String, Method). |
(package private) java.util.Map<java.lang.String,java.util.Set<java.lang.String>> |
getRestrictionProperties()
Get an unmodifiable map of DMO entity names to sets of the names of the
DMO properties used as restriction criteria in the preprocessed where
clause.
|
java.util.List<FQLPreprocessor.PropertyPair> |
getSubstPairs()
Obtain the substitution pair list, if any.
|
UniqueIndexLookup |
getUniqueIndexLookup()
Retrieve the unique index loop-up object, responsible for storing information on the
unique index that is used.
|
(package private) boolean |
hasAnsiJoins()
Report whether any ANSI-style join subexpressions are present.
|
(package private) boolean |
hasContains()
Reports whether the predicate uses
contains() function at least once. |
(package private) boolean |
hasNonConstantContains()
Report whether "contains" keyword with a non-constant expression is present in the FQL.
|
private HQLAst |
honorDisjunctiveForm(HQLAst root)
The method receives a HQLAst and searches for augmented nodes and rewrites them if possible.
|
static void |
initializeCache()
Initializes the cache using CacheManager.
|
private boolean |
injectComputedColumn(HQLAst astNode)
Given an AST which represents a qualified or an unqualified text property, replace the
property's text (the property name) with a name which represents a computed column.
|
private void |
inlineDenormalizedField(HQLAst ast,
java.lang.Object[] parameters)
Replace legacy indexed property with extent with custom property.
|
private java.util.List<FQLPreprocessor.PropertyPair> |
inlineReferenceSubs(HQLAst root,
java.lang.String[] referenceSubs)
Given an array containing one or more strings and zero or more nulls, replace those query substitution
parameters in the where clause AST which positionally coincide with the non-null strings in the array.
|
private int |
inlineSubstitutionParameter(HQLAst ast,
java.lang.Object[] parameters,
Dialect dialect)
Attempt to inline a single query substitution parameter, if it is a
numeric, text, or date type.
|
private boolean |
isBinaryOperator(Aast ast)
Convenience method to detect whether an AST represents a binary
operator, based on its token type.
|
(package private) static boolean |
isConstant(HQLAst node)
Starting from
node, check if the current expreesion is constant or not. |
boolean |
isDependsOnSessionAttribute()
Checks if the query depends on mutable SESSION attribute(s).
|
boolean |
isFindByRowid()
Check if the preprocessed query is a simple RECID/ROWID lookup query.
|
private boolean |
isMandatoryProperty(HQLAst alias,
HQLAst property)
Determine whether the given property in the context of the DMO class represented by the
given buffer alias represents a mandatory (i.e., non-nullable) database column.
|
private boolean |
isOppositeOperandNullable(HQLAst ast,
int index)
Given an AST and its index, and assuming it is one of two operands in a
binary comparison, indicate whether the opposite operand is nullable.
|
private static boolean |
isUDF(Dialect dialect,
HQLAst n)
Check if node represents UDF.
|
private static boolean |
isUDFArgument(Dialect dialect,
HQLAst n)
Check if node represents UDF argument.
|
boolean |
isUniqueFind(boolean includeRowid)
Check if the where clause is a conjunction of equality clauses that strictly cover an
unique index.
|
private RecordBuffer |
lookupBuffer(Aast alias)
Look up the record buffer associated with the given alias.
|
private HQLAst |
mainWalk(HQLAst root,
java.lang.Object[] parameters,
FqlType[] paramTypes,
Dialect dialect,
boolean overload)
Walk the AST depth-first from root to leaves and rewrite certain nodes.
|
private void |
makeExplicitCast(HQLAst parent,
int index,
Dialect dialect)
Replaces the specified child node of a given parent with
cast(? as <datatype>) if it represents
a substitution parameter. |
private void |
manuallyOverload(HQLAst function)
Manually disambiguate a user defined function by taking its overloaded
function name and data type signature and replacing the name in its AST
with a unique name generated when the function was registered.
|
private HQLAst |
normalizeTernary(HQLAst root,
java.lang.Object[] parameters)
Normalize a ternary clause into an equivalent logical expression.
|
private HQLAst |
parse()
Scan and parse the input where clause and return it in the form of an
abstract syntax tree.
|
private void |
preprocess(java.lang.String where,
java.lang.Object[] parameters,
FqlType[] paramTypes,
java.lang.String[] referenceSubs,
Dialect dialect,
boolean overload,
boolean informational,
java.util.Set<java.lang.String> indexedFields)
The main worker method which drives preprocessing.
|
static void |
registerFunction(Database database,
java.lang.String fqlFunction,
java.lang.String sqlFunction,
java.lang.reflect.Method method)
Register a user-defined function to the specified backing method for the given database.
|
private HQLAst |
restructure(HQLAst root,
java.util.Map<HQLAst,java.lang.String> targets)
Restructure the AST subtrees (if any) defined as rewrite targets by the
composite analysis step. |
private void |
rewriteAlias(HQLAst alias,
HQLAst relRoot,
java.lang.String compName)
Given a particular, target subtree and a composite list entity name, rewrite the AST as
described in the
restructure(com.goldencode.p2j.persist.hql.HQLAst, java.util.Map<com.goldencode.p2j.persist.hql.HQLAst, java.lang.String>) method. |
private void |
simplifyProperties(HQLAst root,
boolean eliminateUpper,
boolean eliminateRtrim)
Eliminates unneeded [upper] functions for case-insensitive properties
and [rtrim] functions for automatically rtrimmed properties.
|
private java.util.Set<HQLAst> |
simplifyUnknowns(java.util.Set<HQLAst> unknowns)
Get rid of unknowns in an FQL AST by evaluating relations in which these unknowns
participate.
|
(package private) static java.lang.String |
translate(java.util.List<RecordBuffer> bound,
java.util.List<RecordBuffer> definition,
java.lang.String where,
boolean singleBuffer)
Translate this
where clause to use the bound DMO's alias and property names, instead the
definition (conversion-time) alias and property names. |
private HQLAst |
trySimplifyBooleans(HQLAst booleanOperand,
HQLAst root)
Tries to roll up subexpressions according to the following rules:
{condition} or false --> {condition}
{condition} or true --> true
{condition} and true --> {condition}
{condition} and false --> false
|
(package private) boolean |
wasInlined()
Indicate whether query substitution parameters participating in range
checks were inlined directly into the query string.
|
private static final java.lang.String GUARDED
private static final CentralLogger LOG
private static final FQLPreprocessor NOP_INSTANCE
private static final java.util.Map<FunctionKey,java.lang.String> overloadedFunctions
private static final HQLAst TERNARY_LOGICAL_EXP_TEMPLATE
private static final HQLAst TERNARY_COMPARE_EXP_TEMPLATE
private static LRUCache<FQLPreprocessor.CacheKey,FQLPreprocessor> cacheWithArgs
private static LRUCache<FQLPreprocessor.CacheKey,FQLPreprocessor> cacheNoArgs
private static LRUCache<java.lang.String,HQLAst> astCache
private final Database database
private java.lang.String dropAlias
null if noneprivate java.lang.String replacementAlias
null the dropAlias is forced instead of dropping it. This is needed
in order to allow the ORM to properly resolve the fields in a query in a very specific
case:
- nested queries;
- the dropAlias table is part of both outer and inner selects;
- the dropAlias table has a field with same name.
For unknown cause, the ORM will generate a SQL query that instead of this particular
field, the id field of the table from outer select will be used.
Valid only if dropAlias is not null.
private java.util.Deque<RecordBuffer> defaultBuffers
private java.util.LinkedList<java.lang.String> dropAliases
private java.util.LinkedList<java.lang.String> replacementAliases
private java.util.LinkedHashSet<java.lang.String> ansiJoins
private boolean hasContains
contains method encountered while processing the predicate.private FQLExpression fql
private boolean inline
private boolean inlinedTernary
private ParameterIndices paramIndices
private java.util.Map<java.lang.String,java.util.Set<java.lang.String>> restrictionProperties
private java.util.List<java.lang.String> earlyPublishEntities
private java.util.List<FQLPreprocessor.PropertyMatch> propertyMatches
private boolean findByRowid
private UniqueIndexLookup uniqueIndexLookup
private java.lang.Long findByRowidValue
rowid value that is to be loaded if it is a constant detected in
fql preprocessing. If it is null but findByRowid = true then
the row index was not yet evaluated because it is a SUBST node. The actual value cannot be
cached in this object because it may be different at each call. To detect whether the query
looks for the template record of a table, the parameter[0] of the query iteration
must be analyzed.private java.util.Map<java.lang.String,RecordBuffer> bufferMap
private java.util.List<FQLPreprocessor.PropertyPair> substPairs
preprocess if replacementAliases
are present.private java.util.BitSet queryProperties
private boolean dependsOnSessionAttribute
private boolean nonConstantContains
private FQLPreprocessor(java.util.List<RecordBuffer> bound, java.util.List<RecordBuffer> definition, java.lang.String where, boolean singleBuffer)
where clause to use the bound DMO's alias and property names, instead the
definition (conversion-time) alias and property names.bound - The runtime-bound buffers.definition - The buffers as it was used to generate the where clause at conversion time.where - The FQL to be translated.singleBuffer - Single buffer mode. If activated, a standalone property is assumed to belong the only bound
buffer. Requires bound and definition to have a single element.private FQLPreprocessor(Database database, java.lang.String where, java.util.Map<java.lang.String,RecordBuffer> bufferMap, java.lang.Object[] parameters, FqlType[] paramTypes, java.lang.String[] referenceSubs, Dialect dialect, boolean overload, boolean inline, java.lang.String dropAlias, java.lang.String replacementAlias, boolean informational, java.util.Set<java.lang.String> indexedFields) throws PersistenceException
database - Database within which the associated query will be run.where - An unprocessed where clause which will be scanned, parsed, and possibly rewritten.
Should not include the leading where keyword.bufferMap - A map of DMO aliases to their corresponding record buffers. If dropAlias
is not null, a null key will be mapped to the buffer corresponding
with this alias.parameters - Query substitution parameters for the where clause.paramTypes - The query substitution parameter types.dialect - Database dialect in use.overload - true to manually overload user-defined functions.inline - true to allow substitution parameters involved in range checks to be
inlined; false to disallow such inlining. If inlining occurs, the
preprocessor will not be cached.dropAlias - If non-null, suppress this alias qualifier when emitting the final
FQL where clause string; if null, emit aliases normally.informational - Flag indicating preprocessing is analytical only and query will not be executed.indexedFields - The list of indexed fields. In all cases, except for indexed FIND queries:
t.f <= ? and ? >= t.f expressions are always evaluated to
true;t.f < ? and ? > t.f expressions are equivalent to
t.f <> ??) is sorted high in P4GL index.
In the case of FIND queries where the compared field is not part of the current index, P4GL has a flaw that evaluates the mentioned expressions:
t.f <= ? and ? >= t.f to true only if t.f
is unknownt.f < ? and ? > t.f are always false.PersistenceException - if there is any error rewriting the where clause.public static FQLPreprocessor get(java.lang.String where, Database database, Dialect dialect, RecordBuffer[] buffers, java.lang.Object[] parameters, java.lang.String[] referenceSubs, boolean inline, java.lang.String dropAlias, java.lang.String replacementAlias, boolean informational, java.util.Set<java.lang.String> indexedFields) throws PersistenceException
where - An unprocessed where clause which will be scanned, parsed, and possibly rewritten.
Should not include the leading where keyword.database - Database object associated with the client query.dialect - Database dialect in use.buffers - All buffers referenced by the where clause.parameters - Query substitution parameters for the where clause.referenceSubs - TODO: add descriptioninline - true to permit inlining of substitution parameters involved in range
matches (>, >=, <, <=, like); false to disallow such inlining.
Inlining will embed such parameters into the FQL string directly.dropAlias - If non-null, suppress this alias qualifier when emitting the final
FQL where clause string; if null, emit aliases normally.replacementAlias - In the case of a nested SELECT, use this unique alias as a replacement for default
alias that may cause the ORM to generate bad SQL.informational - Flag indicating preprocessing is analytical only and query will not be executed.indexedFields - The list of indexed fields. In all cases, except for indexed FIND queries:
t.f <= ? and ? >= t.f expressions are always evaluated to
true;t.f < ? and ? > t.f expressions are equvalent to
t.f <> ??) is sorted high in P4GL index.
In the case of FIND queries where the compared field is not part of the current index, P4GL has a flaw that evaluates the mentioned expressions:
t.f <= ? and ? >= t.f to true only if t.f
is unknownt.f < ? and ? > t.f are always false.PersistenceException - if there is any error rewriting the where clause.public static void registerFunction(Database database, java.lang.String fqlFunction, java.lang.String sqlFunction, java.lang.reflect.Method method)
database - Database for which user-defined function is registered.fqlFunction - FQL function name (as generated in java code).sqlFunction - SQL function name that will be used in queries.method - Method which will back the given function and provide function signature.static java.lang.String getRegisteredFunction(Database database, java.lang.String fqlFunction, java.lang.Class<?>[] paramTypes)
registerFunction(Database, String, String, Method).database - Database for which user-defined function is registered.fqlFunction - FQL function name (as generated in java code).paramTypes - The function signature.static java.lang.String translate(java.util.List<RecordBuffer> bound, java.util.List<RecordBuffer> definition, java.lang.String where, boolean singleBuffer)
where clause to use the bound DMO's alias and property names, instead the
definition (conversion-time) alias and property names.bound - The runtime-bound buffers.definition - The buffers as it was used to generate the where clause at conversion time.where - The FQL to be translated.singleBuffer - Single buffer mode. If activated, a standalone property is assumed to belong the only bound
buffer. Requires bound and definition to have a single element.static boolean isConstant(HQLAst node)
node, check if the current expreesion is constant or not.node - The HQLAst node from where we start the check.FQLExpression getFQL()
where keyword.public java.util.List<FQLPreprocessor.PropertyPair> getSubstPairs()
null if none was detected.public java.util.BitSet getQueryProperties()
public boolean isDependsOnSessionAttribute()
true if the query depends on mutable SESSION attribute(s).public boolean isFindByRowid()
Generally, we can easily find such records using persistent database or dirty database for uncommitted transient records. Using normal queries, the transient record that has not validated its index, won't break the transaction isolation idiom and normally should not be shared to any context. However, using RECID/ROWID lookup query the record should be find across transactions and contexts, by direct accessing the record lookup table.
The predicate of such query will only test for equality of the RECID/ROWID of the record against a SUBST value. Using LTE or GTE will fail to find the requested record.
true if the preprocessed query is a simple RECID/ROWID lookup query.public long getFindByRowid(java.lang.Object[] queryParams)
By convention, if the result is negative, the template record should be loaded. Values strictly positive are looked up into the database.
queryParams - The current parameter list for the query.public boolean isUniqueFind(boolean includeRowid)
includeRowid - true if the function should consider rowid as a field of an unique index.true if the target where clause if for zero or one record due to
an unique index look-up.public UniqueIndexLookup getUniqueIndexLookup()
public static void initializeCache()
ParameterIndices getParameterIndices()
ParameterIndices object associated with this query.
This object maintains an array of zero-based indices into the array of
query substitution arguments for the where clause, as it exists
after preprocessing is complete. This additional level of
indirection is necessary because the query substitution placeholders may
have been reordered within the FQL where clause expression during the
FQL preprocessing rewrite step, and some parameters may have been
inlined into the where clause.java.util.Iterator<java.lang.String> ansiJoins()
null.boolean hasAnsiJoins()
true if there are ANSI joins; else false.boolean hasContains()
contains() function at least once.true if the contains() function was encountered while parsing the predicate.boolean hasNonConstantContains()
true if "contains" keyword is present; else falsejava.util.List<java.lang.String> getEarlyPublishEntities()
This method is intended to support the emulation of a quirk/bug in Progress whereby the execution of a query (in the generic sense of the term) in a session after uncommitted changes, will trigger those changes to be prematurely published to other sessions.
TODO: this behavior may represent a version-specific bug in Progress which should be disabled in P2J for other versions than 9.1C.
null.java.util.List<FQLPreprocessor.PropertyMatch> getPropertyMatches()
java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRestrictionProperties()
null if the where clause contained no restriction
properties.boolean wasInlined()
true if inlining was performed (with the caveats
noted above); else false.private RecordBuffer lookupBuffer(Aast alias)
alias - AST representing the alias (variable name) with which the buffer was defined in
business logic.java.lang.IllegalArgumentException - if alias does not match any buffer whose scope is currently open.private void preprocess(java.lang.String where,
java.lang.Object[] parameters,
FqlType[] paramTypes,
java.lang.String[] referenceSubs,
Dialect dialect,
boolean overload,
boolean informational,
java.util.Set<java.lang.String> indexedFields)
throws PersistenceException
true.
If the analysis performed in step 3 determines that rewriting is not necessary, the following three steps are skipped, and the original where clause simply is stored.
where - An unprocessed where clause which will be scanned, parsed, and possibly rewritten.
Should not include the leading where keyword.parameters - Query substitution parameters for the where clause.paramTypes - The query substitution parameter types.dialect - Database dialect in use.overload - true to manually overload user-defined functions.informational - Flag indicating preprocessing is analytical only and query will not be executed.indexedFields - The list of indexed fields. In all cases, except for indexed FIND queries:
t.f <= ? and ? >= t.f expressions are always evaluated to
true;t.f < ? and ? > t.f expressions are equvalent to
t.f <> ??) is sorted high in P4GL index.
In the case of FIND queries where the compared field is not part of the current index, P4GL has a flaw that evaluates the mentioned expressions:
t.f <= ? and ? >= t.f to true only if t.f
is unknownt.f < ? and ? > t.f are always false.PersistenceException - if there is any error rewriting the where clause.private HQLAst fixEmptyContains(HQLAst root, java.lang.Object[] parameters)
contains function node with false is the second argument is an empty string.root - The parsed FQL root.parameters - The query parameters.private void simplifyProperties(HQLAst root, boolean eliminateUpper, boolean eliminateRtrim)
root - The parsed FQL root.eliminateUpper - true if upper functions should be eliminatedeliminateRtrim - true if rtrim functions should be eliminatedprivate HQLAst normalizeTernary(HQLAst root, java.lang.Object[] parameters)
root - The parsed FQL root.parameters - The query parameters.private boolean checkFindByRowid(HQLAst root)
A simple ROWID/RECID lookup query only test for equality of the id field.
root - The root of the tree obtained from preprocessing the FQL query.true if the root represent the tree of a simple ROWID/RECID lookup
query and false for any other kind of query.private UniqueIndexLookup checkUniqueFind()
The goal here is to identify a common business logic pattern: find by an unique index, usually primary. The implementation doesn't detect if the where clauses combination suggest that zero or one record is expected. It just checks if the property matches match an unique index. Callers rely on this. Extending the method with heuristics for uniqueness beyond unique indexes may cause problems.
true if the where clause is a conjunction of equalities covering all
the fields of an unique index.private HQLAst parse() throws PersistenceException
PersistenceException - if there is any error parsing the FQL.private java.util.List<FQLPreprocessor.PropertyPair> inlineReferenceSubs(HQLAst root, java.lang.String[] referenceSubs)
root - FQL AST root node.referenceSubs - Array of field references and nulls.private void annotateParameters(HQLAst root, FqlType[] paramTypes)
datatype.root - Root AST node at which we begin the walk.paramTypes - The type of each substitution parameter.private void collectPropertyMatches(HQLAst root)
The purpose of this collection is to conduct a rudimentary analysis of the complexity of the where clause and its suitability to participate as a sub-expression in a server-side join.
root - FQL AST root node.private HQLAst mainWalk(HQLAst root, java.lang.Object[] parameters, FqlType[] paramTypes, Dialect dialect, boolean overload) throws PersistenceException
root - Root node of the AST for the FQL expression.parameters - Query substitution parameters for the where clause.paramTypes - The query substitution parameter types.dialect - Database dialect in use.overload - true to manually overload user-defined functions.PersistenceException - if any error occurred during rewriting the AST.java.lang.IllegalArgumentException - if an alias encountered in the where clause is not mapped in
the buffer manager.private HQLAst augmentForUnknownValue(HQLAst root, Dialect dialect) throws PersistenceException
simplifyUnknowns(Set) by now,
and augment certain binary sub-expressions to match Progress' unknown value semantics.root - Root node of the AST for the FQL expression.dialect - Database dialect in use.PersistenceException - if any error occurred during rewriting the AST.private HQLAst honorDisjunctiveForm(HQLAst root)
#augmentForUnknownValue(HQLAst)).
The reconstruct occurs when only two augmented nodes are present, even if there are other conditions
in the clause. At the same time, the augmented nodes must have the same AND parent.
This method converts the following construct
[
(t1.f1 = t2.f2 or (t1.f1 is null and t2.f2 is null)) and
(t1.f3 = t2.f4 or (t1.f3 is null and t2.f4 is null))
]
into it's DNF (Disjunctive Normal Form)
[
(t1.f1 = t2.f2 and t1.f3 = t2.f4) or
(t1.f1 = t2.f2 and (t1.f3 is null and t2.f4 is null)) or
((t1.f1 is null and t2.f2 is null) and t1.f3 = t2.f4) or
((t1.f1 is null and t2.f2 is null) and (t1.f3 is null and t2.f4 is null))
]
which is a very specific case where two character fields are compared and augmented.
There are only a few cases that are handled:
a. There are only two augmented nodes. Reconstruction is as mentioned above.
[
(t1.f1 = t2.f2 or (t1.f1 is null and t2.f2 is null)) and
(t1.f3 = t2.f4 or (t1.f3 is null and t2.f4 is null))
]
b. There are additional conditions in the clause. Reconstruction is as mentioned above,
the parent of the construct becomes the next AND and the clause will look like
(root - Root node of the AST for the FQL expression.private void duplicateAugmentedOperand(HQLAst source, HQLAst parent)
parent.
If source represents an extent field, annotate the duplicate node to indicate
that an index check node should not be created (it would be redundant) when rewriting the
alias node.source - Original alias node (or enclosing function).parent - Parent node onto which to graft the duplicate of source as a child.private boolean isMandatoryProperty(HQLAst alias, HQLAst property)
alias - Buffer alias ASTproperty - Property AST.true if the property is mandatory, else false.private java.util.Set<HQLAst> simplifyUnknowns(java.util.Set<HQLAst> unknowns)
unknowns - Set of FQL nodes which represent all unknowns in the FQL AST. Nodes can be
substitution parameters or whole statements which evaluate to unknown values.BOOL_TRUE and BOOL_FALSE nodes which were
created during the simplifications of unknowns. May be null.private void makeExplicitCast(HQLAst parent, int index, Dialect dialect)
cast(? as <datatype>) if it represents
a substitution parameter. E.g. the element will be replaced with cast(? as big_decimal) is we
have a decimal variable.parent - Parent of the node for which the cast must be done.index - Index of the child element.dialect - The dialect the query will be using.private HQLAst trySimplifyBooleans(HQLAst booleanOperand, HQLAst root) throws AstException
If an expression has been rolled up to a boolean value, it tries to roll this value up with other conditions and so on.
booleanOperand - A node which represents a boolean operand in the expressions above.root - The root node of the whole FQL tree.AstException - If there is any error grafting subtrees in the process of
restructuring the subtree.private void inlineDenormalizedField(HQLAst ast, java.lang.Object[] parameters)
ast - A node which represents a denormalized field with extent.parameters - Query substitution parameters for the where clause.private int inlineSubstitutionParameter(HQLAst ast, java.lang.Object[] parameters, Dialect dialect)
?) is replaced with the text of
the parameter's value. The node is annotated as "inlined" to enable
downstream processing to accurately track the remaining placeholders.ast - AST node which represents the query substitution parameter to
be inlined.parameters - The array of substitution parameters from which the value of
the inlined parameter will be determined.dialect - Database dialect in use.SUBST if
the parameter wasn't inlined.private void manuallyOverload(HQLAst function) throws PersistenceException
function - AST node for the function.PersistenceException - if there is an error determining the data type of any parameter
of the function.private boolean injectComputedColumn(HQLAst astNode) throws AstException
If the property reference is already embedded within an upper() function call, that
call is discarded if it is redundant with the computed column definition.
astNode - Qualified or unqualified property AST node.true if the computed column reference was injected;
false if the current database dialect does not use computed columns, or if
the given property is not a text property.AstException - if there is an error restructuring the alias node or its parent node.private java.util.Map<HQLAst,java.lang.String> analyzeComposites(HQLAst root)
The AST subtree structure which represents our target is as follows:
ALIAS
|
+--PROPERTY
|
+--LBRACKET
|
+--{...} (SUBST or NUM_LITERAL)
In its original form, this would look like:
alias.property[subscript]
If any such constructs are identified, they are added to a map of targets and returned.
root - Root node of the AST for the FQL expression.null if no targets were found. The
composite names are retrieved by the DatabaseManager from the appropriate ORM
configurations.private java.lang.Iterable<HQLAst> getDenormalizedFields(HQLAst root)
root - Root node of the AST for the FQL expression.private void addRestrictionProperty(java.lang.String entity,
java.lang.String property)
In the current implementation, the nature of the restriction is not preserved (e.g., equality match, function parameter, etc.).
entity - DMO entity containing the given property.property - Name of a property used by this where clause as a restrictive criterion.private HQLAst restructure(HQLAst root, java.util.Map<HQLAst,java.lang.String> targets) throws PersistenceException
composite analysis step. This involves the expansion of an
expression, for instance:...
OR [ or ]
|
+--EQUALS [ = ]
| |
| +--ALIAS [ alias ]
| | |
| | +--PROPERTY [ propertyABC ]
| | |
| | +--LBRACKET [ [ ]
| | |
| | +--NUM_LITERAL [ 0 ]
| |
| +--SUBST [ ? ]
|
+--EQUALS [ = ]
|
+--ALIAS [ alias ]
| |
| +--PROPERTY [ propertyABC ]
| |
| +--LBRACKET [ [ ]
| |
| +--NUM_LITERAL [ 1 ]
|
+--SUBST [ ? ]
...to an expression which consists of the modified original expression and index
specifications added to the lop level of the tree using logical AND operators:
AND [ and ]
|
+--AND [ and ]
| |
| +--LPARENS [ ( ]
| | |
| | +--OR [ or ]
| | |
| | +--EQUALS [ = ]
| | | |
| | | +--ALIAS [ alias_composite2_0 ]
| | | | |
| | | | +--PROPERTY [ propertyABC ]
| | | |
| | | +--SUBST [ ? ]
| | |
| | +--EQUALS [ = ]
| | |
| | +--ALIAS [ alias_composite2_1 ]
| | | |
| | | +--PROPERTY [ propertyABC ]
| | |
| | +--SUBST [ ? ]
| |
| +--EQUALS [ = ]
| |
| +--FUNCTION [ index ]
| | |
| | +--ALIAS [ alias_composite2_0 ]
| |
| +--NUM_LITERAL [ 0 ]
|
+--EQUALS [ = ]
|
+--FUNCTION [ index ]
| |
| +--ALIAS [ alias_composite2_1 ]
|
+--NUM_LITERAL [ 1 ]
This corresponds to the following expression expansion (for this expression, two composite targets will be passed to the function):
alias.propertyABC[0] = ? or alias.propertyABC[1] = ?
...to this one:
(alias_composite2_0.propertyABC = ? or
alias_composite2_1.propertyABC = ?) and
index(alias_composite2_0) = 0 and
index(alias_composite2_1) = 1
However, the infix example above is informational only; the generation of the final, infix
form of the expression is performed in a later step (see emit(com.goldencode.p2j.persist.hql.HQLAst, boolean)).targets - Map of AST subtrees targeted for rewriting by the analysis step. The map's keys
are the subtrees themselves; the map's values are the names of the composite list
entities retrieved by the DatabaseManager.root - The root node of the AST of the whole FQL expression.PersistenceException - if there is any error restructuring the ASTs.private void rewriteAlias(HQLAst alias, HQLAst relRoot, java.lang.String compName) throws AstException
restructure(com.goldencode.p2j.persist.hql.HQLAst, java.util.Map<com.goldencode.p2j.persist.hql.HQLAst, java.lang.String>) method.alias - Alias subtree to be expanded.relRoot - The injection point for the index condition for the alias.compName - Name of the composite list associated with the combination of the alias name and
property name encountered in the alias subtree.AstException - if there is any error grafting subtrees in the process of
restructuring the subtree.private FQLExpression emit(HQLAst root, boolean translate)
restructured where clause AST into an infix notation expression, which is
compliant with the FQL syntax supported by the ORM. This is the post-rewrite, "finished product"
expression which will be returned by the getFQL() method.root - Root of the restructured where clause AST.translate - Flag indicating if this processing is for a query translate. In this case, the
dropAlias and replacementAlias is not used.private java.util.LinkedHashSet<java.lang.String> generateJoins(java.util.Map<HQLAst,java.lang.String> targets)
Duplicates are eliminated. The order in which joins are detected is preserved in the resulting data structure, such that later iterations over the set will return these in the same order in which they were added.
targets - Map of AST subtrees targeted for rewriting by the analysis
step. The map's keys are the subtrees themselves; the map's
values are the names of the composite list entities retrieved
by the DatabaseManager.null.private java.lang.String generateJoin(java.lang.String compName,
java.lang.String alias,
java.lang.String verboseAlias)
compName - Name of the associated list of composite elements.alias - Alias for the "master" DMO record to which the composite list
is to be joined.verboseAlias - Verbose alias name, including the composite portion and
subscript qualifier.private ParameterIndices createParameterIndices(HQLAst root, java.lang.Object[] parameters)
?) may have been reordered or removed (due to
inlining) during the rewrite process.
During parsing of the FQL clause, an index annotation was added to each
AST of type SUBST (token type for the query substitution
parameter placeholder), denoting that placeholder's position in the
original where clause. We now walk the rewritten AST, extracting this
annotation and storing it in a new list. The resulting array defines
the new order of the substitution parameters. For parameters which
were dropped, due to inlining, a null placeholder will appear in the new
list.
root - Root node of the rewritten where clause AST.parameters - Query substitution parameters for the where clause.private boolean isBinaryOperator(Aast ast)
ast - AST node to test.true if the AST's token type indicates a binary
operator, else false.private boolean isOppositeOperandNullable(HQLAst ast, int index)
TODO: currently, we only test if the property is the reserved, primary key property. We should augment this test to check for other non-nullable properties.
ast - The operand opposite the operand we wish to test.index - The index of ast among its siblings. This must be
0 or 1, since the expression is binary.true if the operand opposite ast is
nullable, else false.private static HQLAst createAstNode(int type, java.lang.String text, HQLAst parent)
type - The type of the new HQLAst node.text - The text of the new HQLAst node.parent - The parent of the new HQLAst node (if not null).private static boolean isUDF(Dialect dialect, HQLAst n)
dialect - database dialectn - node to be checkedtrue if node represents UDF.