Commit 51cad3f4 authored by Vincent Pelletier's avatar Vincent Pelletier

Major SQLCatalog rework on query-generation (not on indexation):

- further extend SearchKey concept
- define, implement and validate Interfaces
- add a column mapper (mapping is decided on a completely-formed query tree instead of being done individualy on each used column)
- state what is present for backward compatibility, warn about deprecated/dangerous uses


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@25706 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 68a0d3c2
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Interface import Interface
class INode(Interface):
"""
Any kind of node in an Abstract Syntax Tree.
"""
def isLeaf():
"""
Returns True if current node is a leaf in node tree.
Returns False otherwise.
"""
def isColumn():
"""
Returns True if current node is a column in node tree.
Returns False otherwise.
"""
class IValueNode(INode):
"""
Value- and comparison-operator-containig node.
They are leaf nodes in the syntax tree.
"""
def getValue():
"""
Returns node's value.
"""
def getComparisonOperator():
"""
Returns node's comparison operator.
"""
class ILogicalNode(INode):
"""
Logical-operator-containing node.
They are internal tree nodes.
"""
def getLogicalOperator():
"""
Returns node's logical operator.
"""
def getNodeList():
"""
Returns the list of subnodes.
"""
class IColumnNode(INode):
"""
Column-name-containing node.
They are internal tree nodes.
Their value applies to any contained ValueNode, except if there is another
ColumnNode between current one and a ValueNode, for which the other
ColumnNode will take precedence.
"""
def getColumnName():
"""
Returns node's column name.
"""
def getSubNode():
"""
Returns node's (only) subnode.
"""
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Interface import Interface
class IEntireQuery(Interface):
"""
A EntireQuery represents an entire SQL expression, where a Query
represents only the "WHERE" part of that expression.
A EntireQuery contains:
- a Query instance
- a limit expression
- a group-by expression
- an order-by expression
- a select expression
It internaly uses a ColumnMap instance to resolve tables to use to
generate a "from" expression.
"""
def __init__(query, order_by_list=None, group_by_list=None,
select_dict=None, limit=None, catalog_table_name=None,
extra_column_list=None, from_expression=None,
order_by_override_list=None):
"""
query (Query instance)
The root of the Query tree this query will contain.
order_by_list (list of 1-tuple, 2-tuple or 3-tuple)
The list of columns which will be sorted by SQL.
Tuple values are:
- mandatory: column name
- optionnal: sort order (can be "ASC" or "DESC", "ASC" by default)
- optionnal: type cast (no cast by default, see "CAST" SQL method)
group_by_list (list of string)
The list of columns which will be groupped by value by SQL.
select_dict (dict, key: string, value: string, None)
Given values describe columns to make available in SQL result.
If column is aliased in result set, key is the alias and value is the
column.
Otherwise, key is the column, and value must be None.
limit
See SQLExpression.
catalog_table_name (string)
Name of the table to use as a catalog.
Deprecated parameters.
extra_column_list (list of string)
The list of columns to register to column map. They will not be used
in final rendering, but are hint on which table are supposed to be
used when mapping columns.
from_expression
See SQLExpression.
order_by_override_list (list of string)
If a column is in order_by_list, cannot be mapped to a table column
but is present in this list, it will be passed through to
SQLExpression.
"""
def asSQLExpression(sql_catalog, only_group_columns):
"""
Instantiate a column map, process parameters given at instantiation and
register them to column map.
Register query to column map.
Build column map.
Generate extra SQLExpressions from column map.
Generate SQLExpression instance and return it.
"""
def asSearchTextExpression(sql_catalog):
"""
This is just a passthrough to embeded Query's asSearchTextExpression
method.
This means that only the where expression can be represented as a
SearchText, but not sort, limits, ...
"""
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Interface import Interface
class IOperator(Interface):
"""
An operator is responsible for rendering a value and a column name as SQL
or Search Text.
This class is designed to be used as a singleton-per-operator.
"""
def __init__(operator):
"""
operator (string)
Operator's text representation. It is used both for SQL and SearchText
rendering.
"""
def asSearchText(value):
"""
Render given value as Search Text
value (see _renderValue)
Value to render as a string for use in a Search Text expression.
"""
def asSQLExpression(column, value_list, only_group_columns):
"""
Construct a SQLExpression instance from given column and value, with
contained glue text.
value_list can be a non-list instance, which must be handled that same
way as a list of one item.
only_group_columns (bool)
If false, the operator can add group columns in the "select_dict" of
returned SLQExpression.
Otherwise, it must not (SQL would be invalid).
"""
def getOperator():
"""
Accessor for operator's text representation.
"""
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Interface import Interface
class IQuery(Interface):
"""
A Query contains:
- a value
- an operator
- a column
It is the python representation of a predicate, independently of its
rendering (SQL or SearchText).
For SQL rendering to be possible, it is necesary for some data to be
centralized in a data structure known at EntireQuery level (to be able to
generate unique table aliases, for exemple). This is the role of
ColumnMap, and registerColumnMap method on this interface.
This interface also offers various rendering methods, one per rendering
format.
"""
def asSearchTextExpression(sql_catalog, column=None):
"""
Render a query in a user-oriented SearchText.
Returns None if there is this query has no SearchText representation,
but is SearchText-aware.
If column is provided, it must be used instead of local knowledge of
column name. It is used to make queries inside a related key render
correctly.
"""
def asSQLExpression(sql_catalog, column_map, only_group_columns):
"""
Render a query as an SQLExpression instance.
"""
def registerColumnMap(sql_catalog, column_map):
"""
Register a query to given column_map.
"""
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Interface import Interface
class ISearchKeyCatalog(Interface):
def buildQuery(kw, ignore_empty_string=True, operator='and'):
"""
Build a ComplexQuery from kw values.
kw (dict: string keys, any value)
A query will be emited based on its value. Depending on the type of
the value it is handled differently. Query values will be passed
through to result (key is ignored). For all other types, their key
must be either a known column of a sql_search_tables table, or a
related key name.
- String values will be parsed according to the default SearchKey of
their real column (even for related keys). If parsing was
successful, Queries will be generated from its output.
Otherwise, that value will be taken as such.
- Dictionary values can be composed of the following keys:
'query': Their payload value, considered as empty if not given.
'key': The SearchKey to use for this value, overriding default
column configuration.
(for other possible keys, see SearchKeys)
They will be taken as such.
- All other types will be taken as such, and no "empty" check will be
performed on them.
ignore_empty_string (boolean)
If True, values from kw which are empty will be skipped.
operator (string)
Used to explicit the logical relation between kw entries.
It must be a valid ComplexQuery logical operator ('and', 'or').
"""
def buildSQLQuery(query_table='catalog', REQUEST=None,
ignore_empty_string=1, only_group_columns=False,
limit=None, extra_column_list=None,
**kw):
"""
Construct and return an instance of EntireQuery class from given
parameters by calling buildQuery.
ignore_empty_string (boolean)
See buildQuery.
limit (1-tuple, 2-tuple)
If given, will emit SQL to limit the number of result lines.
group_by_list (list of strings)
If given, will emit SQL to group found lines on given parameter names
(their column if they are column names, corresponding virtual columns
otherwise - as for related keys).
select_dict (dict, key: string, value: string, None)
Given values describe columns to make available in SQL result.
If column is aliased in result set, key is the alias and value is the
column.
Otherwise, key is the column, and value can be None or the same as
key.
select_list (list of strings)
Same as providing select_dict with select_list items as keys, and None
values.
order_by_list (list of 1-, 2-, or 3-tuples of strings)
If given, will emit SQL to sort result lines.
Sort will happen with decreasing precedence in list order.
Given n-tuples can contain those values, always in this order:
- parameter name
- sort order (see SQL documentation of 'ORDER BY')
- type cast (see SQL documentation of 'CAST')
Sort will happen on given parameter name (its column if it's a column
name, corresponding virtual column otherwise - as for related keys).
Extra parameters are passed through to buildQuery.
Backward compatibility parameters:
Those parameters are deprecated and should not be used. They are present
to provide backward compatibility with former ZSQLCatalog version.
REQUEST
Ignored.
extra_column_list (list)
query_table (string, None)
The table to use as catalog table.
If given and None, not catalog table will be used. Use this when you
are using SQLCatalog to generate manualy a part of another query.
That table has a special position in returned query:
- all other tables are joined on this one (when it is required to use
other tables)
- it is expected to have some columns (uid, path)
It is strongly discouraged to use this parameter for any value other
than None.
group_by
Use group_by_list instead.
group_by_expression
Use group_by_list instead.
select_expression
Use select_list or select_dict instead.
sort_on
Use order_by_list instead.
sort_order
Use order_by_list instead.
from_expression
This value will be emited in SQL directly in addition to computed
value.
There is no replacement.
where_expression
This value will be emited in SQL directly in addition to computed
value.
Use Query instances instead.
select_expression_key
This prevents given column from being ignored even if they could not
be mapped.
There is no replacement.
only_group_columns
Replaces former stat__ parameter.
Used to globally disalow use of non-group columns in SQL.
"""
def getSearchKey(column, search_key=None):
"""
Returns the default SearchKey instance for the
requested column. There is one instance per
search_key (incl. virtual keys surch as
source_title) and we try to compute it once
only and cache it.
If search_key is provided, it is used as the
name of the search key class to return.
"""
def getComparisonOperator(operator):
"""
Return a comparison operator matching given string.
String must be a valid SQL comparison operator (=, LIKE, IN, ...).
String case does not matter.
There is one comparison operator instance per possible string value.
"""
# TODO: add support for other operators (logical, ensemblist (?))
def searchResults(REQUEST=None, **kw):
"""
Invokes queryResults with the appropriate
ZSQL Method to return a list of results
"""
def countResults(REQUEST=None, **kw):
"""
Invokes queryResults with the appropriate
ZSQL Method to return a statistics for
a list of results
"""
def queryResults(sql_method, REQUEST=None, src__=0, build_sql_query_method=None, **kw):
"""
Return the result of the given 'sql_method' ZSQL Method after
processing all parameters to build a Query object passed to
that method.
The implementation should do the following.
1- Use **kw parameters to build a Query object
by invoking buildQuery
2- Build a ColumnMap instance by invoking
the buildColumnMap on the Query. Some
optmisation may happen here to try
to build the best possible ColumnMap and
use the best possible indices for joining.
During the ColumnMap build process, the
Search Key associated to each Query node
in the Query tree registers the columns
which are used (ex. to search) or provided
(ex. MATCH value for full text search,
interleave expression or parameter in a
UNION Query)
3- Render the query object as an SQLExpression
instance. This instance contains all necessary
parts to generate:
- where_expression
- sort_expression
- group_by_expression
- select_expression
4- Invoke sql_method
"""
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Interface import Interface
class ISQLExpression(Interface):
"""
This is a container for chunks of SQL generated by a Query.
Each Query instance generates its own SQLExpression instance.
SQLExpressions can be nested (an SQLExpression can contain 0, 1 or more
SQLExpressions).
Chunks are:
- table_alias_dict
- order_by_list
- group_by_list
- select_dict
- limit
Mutualy exclusive chunks are:
- where expression
- sql_expression_list
Deprecated chunks are:
- from_expression
Providing sql_expression_list with more than one entry makes
where_expression_operator mandatory.
"""
def __init__(query,
table_alias_dict=None,
order_by_list=None,
order_by_dict=None,
group_by_list=None,
where_expression=None,
where_expression_operator=None,
sql_expression_list=None,
select_dict=None,
limit=None,
from_expression=None):
"""
Instantiate an SQLExpression object.
This method does consistency checks on received parameters, so that
failures are detected as early as possible.
Also, it casts most optional parameters into empty lists and empty dicts
to make code using those values simpler.
query (Query)
The Query instance which called this constructor.
table_alias_dict (dict, key: string, value: string)
Table alias dict as returned by ColumnMap.getTableAliasDict() .
order_by_list (list of strings)
List of result ordering, pre-rendered.
order_by_dict (dict, key: string, value: string)
Column rendering replacement specific to order_by.
group_by_list (list of strings)
List of column names on which result line list will be grouped.
where_expression (string)
Text representing a "where" expression of an SQL query.
where_expression_operator ("and", "or", "not", None)
Operator to apply on immediately contained SQLExpressions.
It must be "and" or "or" when there are multiple contained
SQLExpressions, it can be "not" if there is exactly one contained
SQLExpression, and must not be provided if there is no contained
SQLExpression.
sql_expression_list (list of SQLExpression)
List of immediately contained SQLExpressions.
select_dict (dict, key:string, value:string or Null)
Lists all columns to be part of select expression.
Key is column alias.
Value is column name, or Null. If it is Null, the alias will also be
used as column name.
limit (1-tuple, 2-tuple, other)
First item is the number of lines expected, second one if given is the
offset of limited result list within the unlimited result list.
If it is not a tuple it is treated as the value of a 1-tuple
parameter.
from_expression (string)
This parameter is deprecated.
"""
def asSQLExpressionDict():
"""
Returns a dictionnary usable as a **kw for a catalog sql method.
It renders aliases (see getTableAliasDict) as a list of strings and
"from" expression (see getFromExpression) as a list of strings.
See getWhereExpression, getOrderByExpression, getLimitExpression,
getSelectExpression, getGroupByExpression.
"""
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Interface import Interface
class ISearchKey(Interface):
"""
A SearchKey generates Query instances.
It is responsible for parsing (or not) input value and generating the
appropriate Query structure to match the intended search.
This class is designed as a "singleton per column".
"""
def __init__(column):
"""
column (string)
The column this SearchKey is instanciated for.
"""
def getColumn():
"""
Returns: string
The column this SearchKey is instanciated for.
"""
def buildSQLExpression(operator, value, column_map, only_group_columns, group):
"""
This method passes on SQLExpression creation to operator, giving it the
resolved column of this RelatedKey.
operator (Operator)
A comparison operator. It is used to render value, column and itself
as valid SQL.
value (anything)
column_map (ColumnMap)
The (built) column map to render our column.
group (string)
The group our column was part of.
Returns: SQLExpression
SQLExpression build by operator for current Query.
"""
def buildSearchTextExpression(operator, value, column=None):
"""
Render comparison using operator between value and our column as
SearchText.
operator (Operator)
A comparison operator. It is used to render value as value SearchText.
value (anything)
column (string)
If given, it overrides our column in SearchText rendering. This is
useful when SearchText rendering is done for a virtual column,
because the name of the virtual column must appear when rendered as
SearchText.
"""
def registerColumnMap(column_map, group, simple_query):
"""
Register the column of this SearchKey to given column map.
column_map (ColumnMap)
The ColumnMap instance to register to.
group (string)
The group registration will be part of.
simple_query (SimpleQuery)
The SimpleQuery being registered.
Returns: string
The group assigned to caller. See ColumnMap for methods redefining
caller's group.
"""
def buildQuery(search_value, group=None, logical_operator=None, comparison_operator=None):
"""
Generate queries from given search_value.
logical_operator ('and', 'or', 'not', None)
If no logical operator can be found in search_value, it will use given
default operator.
comparison_operator (string, None)
If given, expresses the comparison between column and value.
"""
def parseSearchText(value):
"""
Parse given value to generate an Abstract Syntax Tree representing its
logical structure, or None if there is no obvious structure in given
value.
See SearchText for parsing code.
value (string)
The string to parse.
Returns: (None, AbstratSyntaxNode)
AbstratSyntaxNode complies with the IAbstractSyntaxNode interface.
"""
class IRelatedKey(ISearchKey):
"""
A RelatedKey is a special variation of a SearchKey.
Only a small set of methods differ. They are defined in this class.
"""
def registerColumnMap(column_map, table_alias_list=None):
"""
This is slightly different for regular registerColumnMap in that it must
register multiple tables (and not columns, since RelatedKeys do not
provide this information).
Also, it must register namely "catalog" table and resolve its alias,
angain since it's hardcoded in RelatedKey ZSQLMethods and not provided
by their definitions.
column_map (ColumnMap)
Deprecated:
table_alias_list (None, list of 2-tuples of strings)
This list must have the exact same length as the list of tables
"""
def buildSQLExpression(sql_catalog, column_map, only_group_columns, group):
"""
operator and value parameters are useless, since this type of SearhKey
does not compare a value to any column, but uses a ZSQLMethod.
To reach that ZSQLMethod, it also required a new sql_catalog parameter.
sql_catalog (SQLCatalog)
Used to retrieve related key's ZSQLMethod.
"""
def buildQuery(sql_catalog, related_key_definition, search_value=None, search_key_name=None, logical_operator=None, comparison_operator=None):
"""
group is useless here, since group is determined by ColumnMap at
registration time. search_value becomes optional.
sql_catalog (SQLCatalog)
Used to retrieve real column's SearchKey. For example, a RelatedKey
used to compare with a "title" column will retrieve title's default
SearchKey (which should be a KeywordKey).
related_key_definition (string)
Describes parameters of a RelatedKey. It is composed of 3 mains parts,
separated by '/':
- a list of table names
Table names are separated by ','
- a column name
- the name of the related key ZSQLMethod
search_value (anything)
If given, a condition on real column will be generated.
Otherwise, only the SQL required to reach that column will be
generated. This is usefull when sorting on a virtual column, for
example.
search_key_name (string, None)
If given, it overrides real column's default SearchKey.
logical_operator (string, None)
If given, expresses the default logical link between operands.
It must be one of None, 'or' and 'and'.
It is overriden by operator present in search_value if it is a dict
and contains an 'operator' key.
'or' is assumed if not given or given with a None value.
comparison_operator (string, None)
If given, expresses the comparison between column and value.
"""
# This imports from Zope's products, which would otherwise be unreachable from parent folder.
from Interface.Verify import verifyClass
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from OperatorBase import OperatorBase
from Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.Interface.IOperator import IOperator
from Interface.Verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator
class ComparisonOperatorBase(OperatorBase):
@profiler_decorator
def asSQLExpression(self, column, value_list, only_group_columns):
"""
In a Comparison Operator, rendering order is:
<column> <operator> <value_list>
"""
column, value_list = self.render(column, value_list)
return SQLExpression(self, where_expression='%s %s %s' % (column, self.getOperator().upper(), value_list))
def render(self, column, value_list):
raise NotImplementedError, 'This method must be overloaded by a subclass.'
def renderValue(self, value_list):
raise NotImplementedError, 'This method must be overloaded by a subclass.'
verifyClass(IOperator, ComparisonOperatorBase)
class MonovaluedComparisonOperator(ComparisonOperatorBase):
@profiler_decorator
def renderValue(self, value_list):
"""
value_list must either be a non-list or a single-value list.
"""
if isinstance(value_list, (tuple, list)):
if len(value_list) > 1:
raise ValueError, '%r: value_list must not contain more than one item. Got %r' % (self, value_list)
value_list = value_list[0]
return self._renderValue(value_list)
@profiler_decorator
def render(self, column, value_list):
"""
value_list must either be a non-list or a single-value list.
"""
if isinstance(value_list, (tuple, list)):
if len(value_list) > 1:
raise ValueError, '%r: value_list must not contain more than one item. Got %r' % (self, value_list)
value_list = value_list[0]
return self._render(column, value_list)
verifyClass(IOperator, MonovaluedComparisonOperator)
class MultivaluedComparisonOperator(ComparisonOperatorBase):
@profiler_decorator
def renderValue(self, value_list):
"""
value_list must be a multi-value list (more than one item).
"""
if not isinstance(value_list, (tuple, list)) or len(value_list) < 2:
raise ValueError, '%r: value_list must be a list of more than one item. Got %r' % (self, value_list)
return '(%s)' % (', '.join([self._renderValue(x) for x in value_list]), )
@profiler_decorator
def render(self, column, value_list):
"""
value_list must be a multi-value list (more than one item).
"""
if not isinstance(value_list, (tuple, list)) or len(value_list) < 2:
raise ValueError, '%r: value_list must be a list of more than one item. Got %r' % (self, value_list)
return column, '(%s)' % (', '.join([self._renderValue(x) for x in value_list]), )
verifyClass(IOperator, MultivaluedComparisonOperator)
class MatchComparisonOperator(MonovaluedComparisonOperator):
def __init__(self, operator, mode=''):
MonovaluedComparisonOperator.__init__(self, operator)
self.where_expression_format_string = 'MATCH (%%(column)s) AGAINST (%%(value_list)s%s)' % (mode, )
@profiler_decorator
def asSQLExpression(self, column, value_list, only_group_columns):
"""
This operator can emit a select expression, so it overrides
asSQLExpression inseatd of just defining a render method.
"""
match_string = self.where_expression_format_string % {
'column': column,
'value_list': self.renderValue(value_list),
}
select_dict = {}
if not only_group_columns:
select_dict[column.replace('`', '').split('.')[-1]] = match_string
# Sort on this column uses relevance.
# TODO: Add a way to allow sorting by raw column value.
order_by_dict = {
column: self.where_expression_format_string,
}
return SQLExpression(
self,
select_dict=select_dict,
where_expression=match_string,
order_by_dict=order_by_dict,
)
verifyClass(IOperator, MatchComparisonOperator)
operator_dict = {
'=': MonovaluedComparisonOperator('='),
'!=': MonovaluedComparisonOperator('!='),
'>': MonovaluedComparisonOperator('>'),
'<': MonovaluedComparisonOperator('<'),
'<=': MonovaluedComparisonOperator('<='),
'>=': MonovaluedComparisonOperator('>='),
'like': MonovaluedComparisonOperator('like'),
'match': MatchComparisonOperator('match'),
'match_boolean': MatchComparisonOperator('match_boolean', mode=' IN BOOLEAN MODE'),
'match_expansion': MatchComparisonOperator('match_expansion', mode=' WITH QUERY EXPANSION'),
'in': MultivaluedComparisonOperator('in'),
'is': MonovaluedComparisonOperator('is'),
}
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from zLOG import LOG
from Products.ZSQLCatalog.Interface.IOperator import IOperator
from Interface.Verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator
@profiler_decorator
def escapeString(value):
# Inspired from ERP5Type/Utils:sqlquote, but this product must not depend on it.
return "'" + value.replace('\\', '\\\\').replace("'", "''") + "'"
@profiler_decorator
def valueFloatRenderer(value):
if isinstance(value, basestring):
value = float(value.replace(' ', ''))
return repr(value)
@profiler_decorator
def valueDateTimeRenderer(value):
return '"%s"' % (value.toZone('UTC').ISO(), )
@profiler_decorator
def valueDefaultRenderer(value):
LOG('OperatorBase', 0, 'Unhandled value class: %s (%r). Converted to string and escaped.' % (value.__class__.__name__, value))
return escapeString(str(value))
@profiler_decorator
def valueNoneRenderer(value):
return 'NULL'
value_renderer = {
'int': str,
'long': str,
'float': valueFloatRenderer,
'DateTime': valueDateTimeRenderer,
'NoneType': valueNoneRenderer,
}
value_search_text_renderer = {
'DateTime': str,
}
@profiler_decorator
def valueDefaultSearchTextRenderer(value):
"""
This is just repr, but always surrounding text strings with doublequotes.
"""
if isinstance(value, basestring):
result = '"%s"' % (value.replace('\\', '\\\\').replace('"', '\\"'), )
else:
result = repr(value)
return result
@profiler_decorator
def columnFloatRenderer(column, format=None):
if format is not None:
if '.' in format:
format = format.replace(' ', '')
column = "TRUNCATE(%s, %s)" % (column, len(format.split('.')[-1]))
return column
@profiler_decorator
def columnDefaultRenderer(column, format=None):
return column
column_renderer = {
'float': columnFloatRenderer
}
class OperatorBase(object):
__implements__ = IOperator
def __init__(self, operator):
self.operator = operator
def getOperator(self):
return self.operator
@profiler_decorator
def _render(self, column, value):
"""
Render given column and value for use in SQL.
Value is rendered to convert it to SQL-friendly value.
Column is rendered to include possible cast code.
column (string)
Column on which the value will be matched
value (see _renderValue)
Value to render.
"""
if isinstance(value, dict):
type = value['type']
column = column_renderer.get(type, columnDefaultRenderer)(column, format=value['format'])
value = value_renderer.get(type, valueDefaultRenderer)(value['query'])
else:
value = self._renderValue(value)
return column, value
@profiler_decorator
def _renderValue(self, value):
"""
Render given value as string.
value (int, float, long, DateTime, string, None)
Value to render as a string for use in SQL (quoted, escaped).
"""
if isinstance(value, basestring):
value = escapeString(value)
else:
value = value_renderer.get(value.__class__.__name__, valueDefaultRenderer)(value)
return value
@profiler_decorator
def asSearchText(self, value):
return value_search_text_renderer.get(value.__class__.__name__, valueDefaultSearchTextRenderer)(value)
def asSQLExpression(self, column, value_list, only_group_columns):
raise NotImplementedError, 'This method must be overloaded by a subclass ' \
'to be able to get an SQL representation of this operator.'
def __repr__(self):
return '<%s(%r) at %s>' % (self.__class__.__name__, self.getOperator(), id(self))
verifyClass(IOperator, OperatorBase)
from ComparisonOperator import operator_dict
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Query import Query
from zLOG import LOG
from Products.ZSQLCatalog.Interface.IQuery import IQuery
from Interface.Verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator
class AutoQuery(Query):
"""
An AutoQuery is a compatibility layer for former Query class.
It passes parameters given at instantiation time to SQLCatalog's
buildQuery or buildSingleQuery, and wraps resulting Query instance (proxy
behaviour).
This is only here for backward compatibility, and use is strongly
discouraged. Use SQLCatalog API instead.
"""
wrapped_query = None
@profiler_decorator
def __init__(self, *args, **kw):
"""
Note: "operator" might contain a logical or a comparison operator.
"""
if len(args):
LOG('AutoQuery', 100, 'Got extra positional parameters (will be ignored): %r' % (args, ))
self.table_alias_list = kw.pop('table_alias_list', None)
self.kw = kw
operator = kw.pop('operator', None)
if isinstance(operator, basestring):
operator = operator.lower()
self.operator = operator
self.ignore_empty_string = kw.pop('ignore_empty_string', True)
if 'key' in kw and len(kw) > 2:
raise ValueError, '"key" parameter cannot be used when more than one column is given. key=%r' % (self.search_key, )
self.search_key = kw.pop('key', None)
@profiler_decorator
def _createWrappedQuery(self, sql_catalog):
"""
Create wrapped query. This requires being able to reach catalog, since
we use it as a query producer.
"""
kw = self.kw
operator = self.operator
if 'range' in kw:
# If we received a range parameter we are building a single query.
# Recreate value as a dict and pass it to buildSingleQuery.
range = kw.pop('range')
assert len(kw) == 1, repr(kw)
key, value = kw.items()[0]
query = sql_catalog.buildSingleQuery(key, {'query': value,
'range': range})
elif operator == 'in':
# 'in' is a *comparison* operator, not a logical operator.
# Transform kw into the proper form.
assert len(kw) == 1, repr(kw)
key, value = kw.items()[0]
query = sql_catalog.buildSingleQuery(key, {'query': value,
'operator': operator})
elif len(kw) == 1 and isinstance(kw.values()[0], (tuple, list)) and \
operator in ('and', 'or'):
# If there is only one parameter, and operator was given and is a
# known logical operator, then operator will apply to it.
# For example (from testDomainTool):
# kw = {'portal_type': ['!=a', '!=b'], 'operator': 'AND'}
# In such case, expected result is
# "portal_type!='a' AND portal_type!='b'"
key, value = kw.items()[0]
query = sql_catalog.buildSingleQuery(key, value, logical_operator=operator)
else:
# Otherwise, the operator will apply to the relationship between
# parameters.
if operator is None:
operator = 'and'
if self.search_key is not None:
key, value = kw.items()[0]
kw = {key: {'query': value, 'key': self.search_key}}
query = sql_catalog.buildQuery(kw, operator=operator, ignore_empty_string=self.ignore_empty_string)
if self.table_alias_list is not None:
query.setTableAliasList(self.table_alias_list)
self.wrapped_query = query
@profiler_decorator
def asSearchTextExpression(self, sql_catalog, column=None):
if self.wrapped_query is None:
self._createWrappedQuery(sql_catalog)
return self.wrapped_query.asSearchTextExpression(sql_catalog, column=column)
@profiler_decorator
def asSQLExpression(self, sql_catalog, column_map, only_group_columns):
if self.wrapped_query is None:
self._createWrappedQuery(sql_catalog)
return self.wrapped_query.asSQLExpression(sql_catalog, column_map, only_group_columns=only_group_columns)
@profiler_decorator
def registerColumnMap(self, sql_catalog, column_map):
if self.wrapped_query is None:
self._createWrappedQuery(sql_catalog)
return self.wrapped_query.registerColumnMap(sql_catalog, column_map)
def __repr__(self):
if self.wrapped_query is None:
result = '<%s(**%r) at %s>' % (self.__class__.__name__, self.kw, id(self))
else:
result = '<%s %r>' % (self.__class__.__name__, self.wrapped_query)
return result
verifyClass(IQuery, AutoQuery)
##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Ivan Tyagov <ivan@nexedi.com>
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
......@@ -26,68 +28,127 @@
#
##############################################################################
from Products.PythonScripts.Utility import allow_class
from Query import QueryMixin
from Query import Query
from Products.ZSQLCatalog.SQLExpression import SQLExpression
from SQLQuery import SQLQuery
from Products.ZSQLCatalog.Interface.IQuery import IQuery
from Interface.Verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator
class ComplexQuery(QueryMixin):
class ComplexQuery(Query):
"""
Used in order to concatenate many queries
A ComplexQuery represents logical operations between Query instances.
"""
@profiler_decorator
def __init__(self, *args, **kw):
self.query_list = args
self.operator = kw.pop('operator', 'AND')
# XXX: What is that used for ?! It's utterly dangerous.
#self.__dict__.update(kw)
def getQueryList(self):
return self.query_list
def getRelatedTableMapDict(self):
result = {}
for query in self.getQueryList():
if not(isinstance(query, basestring)):
result.update(query.getRelatedTableMapDict())
return result
def asSQLExpression(self, key_alias_dict=None,
ignore_empty_string=1,
keyword_search_keys=None,
datetime_search_keys=None,
full_text_search_keys=None,
stat__=0):
"""
Build the sql string
*args (tuple of Query or of list of Query)
list-type entry will extend subquery list, other entries will be
appended.
logical_operator ('and', 'or', 'not')
Logical operator.
Default: 'and'
Deprecated
operator ('and', 'or', 'not')
See logical_operator.
logical_operator takes precedence if given.
unknown_column_dict (dict)
Only one key of this dictionnary is used here:
key: 'from_expression'
value: string
This value will be passed through to SQLExpression. If it is
provided, this ComplexQuery must have no subquery (regular
SQLExpression limitation)
implicit_table_list (list of strings)
Each entry in this list will be registered to column map. This is
used to make column mapper choose tables differently.
"""
sql_expression_list = []
select_expression_list = []
for query in self.getQueryList():
if isinstance(query, basestring):
sql_expression_list.append(query)
self.logical_operator = kw.pop('logical_operator', kw.pop('operator', 'and')).lower()
assert self.logical_operator in ('and', 'or', 'not'), self.logical_operator
unknown_column_dict = kw.pop('unknown_column_dict', {})
self.from_expression = unknown_column_dict.pop('from_expression', None)
self.implicit_table_list = kw.pop('implicit_table_list', [])
query_list = []
append = query_list.append
extend = query_list.extend
# Flaten the first level of list-type arguments
for arg in args:
if isinstance(arg, (list, tuple)):
extend(arg)
else:
append(arg)
new_query_list = []
append = new_query_list.append
# Iterate over the flaten argument list to cast each into a query type.
for query in query_list:
if not isinstance(query, Query):
query = SQLQuery(query)
append(query)
self.query_list = new_query_list
@profiler_decorator
def asSearchTextExpression(self, sql_catalog, column=None):
if column in (None, ''):
query_column = column
else:
query_column = ''
search_text_list = [y for y in [x.asSearchTextExpression(sql_catalog, column=query_column) for x in self.query_list] if y is not None]
if len(search_text_list) == 0:
result = ''
else:
query_result = query.asSQLExpression(key_alias_dict=key_alias_dict,
ignore_empty_string=ignore_empty_string,
keyword_search_keys=keyword_search_keys,
datetime_search_keys=datetime_search_keys,
full_text_search_keys=full_text_search_keys,
stat__=stat__)
sql_expression_list.append(query_result['where_expression'])
select_expression_list.extend(query_result['select_expression_list'])
operator = self.getOperator()
result = {'where_expression':('(%s)' % \
(' %s ' % operator).join(['(%s)' % x for x in sql_expression_list])),
'select_expression_list':select_expression_list}
if self.logical_operator in ('and', 'or'):
if len(search_text_list) == 1:
result = search_text_list[0]
else:
logical_operator = ' %s ' % (self.logical_operator.upper(), )
result = '(%s)' % (logical_operator.join(search_text_list), )
elif self.logical_operator == 'not':
assert len(search_text_list) == 1
result = '(NOT %s)' % (search_text_list[0], )
else:
raise ValueError, 'Unknown operator %r' % (self.logical_operator, )
if column not in (None, ''):
result = '%s:%s' % (column, result)
return result
def getSQLKeyList(self):
@profiler_decorator
def asSQLExpression(self, sql_catalog, column_map, only_group_columns):
sql_expression_list = [x.asSQLExpression(sql_catalog, column_map, only_group_columns)
for x in self.query_list]
if len(sql_expression_list) == 0:
sql_expression_list = [SQLExpression(self, where_expression='1')]
return SQLExpression(self,
sql_expression_list=sql_expression_list,
where_expression_operator=self.logical_operator,
from_expression=self.from_expression)
@profiler_decorator
def registerColumnMap(self, sql_catalog, column_map):
for implicit_table_column in self.implicit_table_list:
column_map.registerColumn(implicit_table_column)
for query in self.query_list:
query.registerColumnMap(sql_catalog, column_map)
def __repr__(self):
return '<%s of %r.join(%r)>' % (self.__class__.__name__, self.logical_operator, self.query_list)
@profiler_decorator
def setTableAliasList(self, table_alias_list):
"""
Returns the list of keys used by this
instance
This function is here for backward compatibility.
This can only be used when there is one and only one subquery which
defines a setTableAliasList method.
See RelatedQuery.
"""
key_list=[]
for query in self.getQueryList():
if not(isinstance(query, basestring)):
key_list.extend(query.getSQLKeyList())
return key_list
assert len(self.query_list) == 1
self.query_list[0].setTableAliasList(table_alias_list)
@profiler_decorator
def setGroup(self, group):
for query in self.query_list:
query.setGroup(group)
verifyClass(IQuery, ComplexQuery)
allow_class(ComplexQuery)
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.ColumnMap import ColumnMap
from zLOG import LOG
from Products.ZSQLCatalog.Interface.IEntireQuery import IEntireQuery
from Interface.Verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator
class EntireQuery(object):
"""
This is not a Query subclass, since it does not define a
registerColumnMap method, and instead does the ColumnMap handling
internaly.
"""
__implements__ = IEntireQuery
column_map = None
@profiler_decorator
def __init__(self, query, order_by_list=None, group_by_list=None,
select_dict=None, limit=None, catalog_table_name=None,
extra_column_list=None, from_expression=None,
order_by_override_list=None):
def default(value):
if value is None:
return []
assert isinstance(value, (tuple, list))
return value
def defaultDict(value):
if value is None:
return {}
assert isinstance(value, dict)
return value
self.query = query
self.order_by_list = default(order_by_list)
self.order_by_override_set = frozenset(default(order_by_override_list))
self.group_by_list = default(group_by_list)
self.select_dict = defaultDict(select_dict)
self.limit = limit
self.catalog_table_name = catalog_table_name
self.extra_column_list = default(extra_column_list)
self.from_expression = from_expression
def asSearchTextExpression(self, sql_catalog):
return query.asSearchTextExpression(sql_catalog)
@profiler_decorator
def asSQLExpression(self, sql_catalog, only_group_columns):
column_map = self.column_map
if column_map is None:
# XXX: should we provide a way to register column map as a separate mathod or do it here ?
# Column Map was not built yet, do it.
self.column_map = column_map = ColumnMap(catalog_table_name=self.catalog_table_name)
for extra_column in self.extra_column_list:
table, column = extra_column.replace('`', '').split('.')
if table != self.catalog_table_name:
raise ValueError, 'Extra columns must be catalog columns. %r does not follow this rule (catalog=%r, extra_column_list=%r)' % (extra_column, self.catalog_table_name, self.extra_column_list)
column_map.registerColumn(extra_column)
for column in self.group_by_list:
column_map.registerColumn(column)
for alias, column in self.select_dict.iteritems():
if column is None:
column = alias
else:
column_map.ignoreColumn(alias)
column_map.registerColumn(column)
for override in self.order_by_override_set:
column_map.ignoreColumn(override)
for order_by in self.order_by_list:
assert isinstance(order_by, (tuple, list))
assert len(order_by)
column_map.registerColumn(order_by[0])
self.query.registerColumnMap(sql_catalog, column_map)
column_map.build(sql_catalog)
# Replace given group_by_list entries by their mapped representations.
new_column_list = []
append = new_column_list.append
for column in self.group_by_list:
try:
append(column_map.asSQLColumn(column))
except KeyError:
LOG('EntireQuery', 100, 'Group-by column %r could not be mapped, but is passed through. This use is strongly discouraged.' % (column, ))
append(column)
self.group_by_list = new_column_list
# Build a dictionnary from select_dict aliasing their mapped representations
self.final_select_dict = select_dict = {}
for alias, raw_column in self.select_dict.iteritems():
if raw_column is None:
column = alias
else:
column = raw_column
try:
rendered = column_map.asSQLColumn(column)
except KeyError:
LOG('EntireQuery', 100, 'Select column %r could not be mapped, but is passed through. This use is strongly discouraged.' % (column, ))
rendered = column
select_dict[alias] = rendered
# Replace given order_by_list entries by their mapped representations.
new_order_by_list = []
append = new_order_by_list.append
for order_by in self.order_by_list:
column = order_by[0]
if column in self.order_by_override_set:
LOG('EntireQuery', 100, 'Order-by column %r is forcibly accepted. This use is strongly discouraged.' % (column, ))
rendered = column
else:
try:
rendered = column_map.asSQLColumn(column)
except KeyError:
LOG('SQLCatalog', 100, 'Order by %r ignored: it could not be mapped to a known column.' % (order_by, ))
rendered = None
if rendered is not None:
if len(order_by) > 1:
if len(order_by) > 2 and order_by[2] not in (None, ''):
rendered = 'CAST(%s AS %s)' % (rendered, order_by[2])
rendered = '%s %s' % (rendered, order_by[1])
append(rendered)
self.order_by_list = new_order_by_list
# generate SQLExpression from query
sql_expression_list = [self.query.asSQLExpression(sql_catalog, column_map, only_group_columns)]
# generate join expression based on column_map.getJoinTableAliasList
append = sql_expression_list.append
for join_query in column_map.iterJoinQueryList():
append(join_query.asSQLExpression(sql_catalog, column_map, only_group_columns))
join_table_list = column_map.getJoinTableAliasList()
if len(join_table_list):
# XXX: Is there any special rule to observe when joining tables ?
# Maybe we could check which column is a primary key instead of
# hardcoding "uid".
where_pattern = '`%s`.`uid` = `%%s`.`uid`' % \
(column_map.getCatalogTableAlias(), )
# XXX: It would cleaner from completeness point of view to use column
# mapper to render column, but makes code much more complex to just do
# a simple text rendering. If there is any reason why we should have
# those column in the mapper, then we should use the clean way.
append(SQLExpression(self, where_expression=' AND '.join(
where_pattern % (x, ) for x in join_table_list
)))
self.sql_expression_list = sql_expression_list
return SQLExpression(
self,
table_alias_dict=column_map.getTableAliasDict(),
from_expression=self.from_expression,
order_by_list=self.order_by_list,
group_by_list=self.group_by_list,
select_dict=self.final_select_dict,
limit=self.limit,
where_expression_operator='and',
sql_expression_list=self.sql_expression_list)
verifyClass(IEntireQuery, EntireQuery)
##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Ivan Tyagov <ivan@nexedi.com>
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
......@@ -26,117 +28,35 @@
#
##############################################################################
from DocumentTemplate.DT_Var import sql_quote
from Products.ZSQLCatalog.Interface.IQuery import IQuery
from Interface.Verify import verifyClass
class QueryMixin:
class Query(object):
"""
Mixing class which implements methods which are
common to all kinds of Queries
This is the base class of all kind of queries. Its only purpose is to be
able to distinguish any kind of value from a query.
"""
operator = None
format = None
type = None
def __call__(self, **kw):
return self.asSQLExpression(**kw)
__implements__ = IQuery
__allow_access_to_unprotected_subobjects__ = 1
def getOperator(self):
return self.operator
def getFormat(self):
return self.format
def getType(self):
return self.type
def getRange(self):
return self.range
def getTableAliasList(self):
return self.table_alias_list
def getSearchMode(self):
"""Search mode used for Full Text search
def asSQLExpression(self, sql_catalog, column_map, only_group_columns):
"""
return self.search_mode
def getSearchKey(self):
"""Search mode used for Full Text search
To enable SQL rendering, overload this method in a subclass.
"""
return self.search_key
def getKey(self):
return self.key
def getValue(self):
return self.value
raise TypeError, 'A %s cannot be rendered as an SQL expression.' % (self.__class__.__name__, )
def getOperator(self):
return self.operator.upper().strip()
def asSearchTextExpression(self):
raise NotImplementedError
def asSQLExpression(self, key_alias_dict=None,
keyword_search_keys=None,
datetime_search_keys=None,
full_text_search_keys=None,
ignore_empty_string=1, stat__=0):
def asSearchTextExpression(self, sql_catalog, column=None):
"""
Return a dictionnary containing the keys and value types:
'where_expression': string
'select_expression_list': string
To enable Search Text rendering, overload this method in a subclass.
"""
raise NotImplementedError
raise TypeError, 'A %s cannot be rendered as a SearchText expression.' % (self.__class__.__name__, )
def getSQLKeyList(self):
def registerColumnMap(self, sql_catalog, column_map):
"""
Return a list of keys used by this query and its subqueries.
This method must always be overloaded by subclasses.
"""
raise NotImplementedError
raise NotImplementedError, '%s is incompeltely implemented.' % (self.__class__.__name__, )
def getRelatedTableMapDict(self):
"""
Return for each key used by this query (plus ones used by its
subqueries) the table alias mapping.
"""
raise NotImplementedError
verifyClass(IQuery, Query)
def _quoteSQLString(self, value):
"""Return a quoted string of the value.
XXX: Left for backwards compatability!
"""
format = self.getFormat()
type = self.getType()
if format is not None and type is not None:
if type == 'date':
if hasattr(value, 'strftime'):
value = value.strftime(format)
if isinstance(value, basestring):
value = "STR_TO_DATE('%s','%s')" % (value, format)
if type == 'float':
# Make sure there is no space in float values
value = value.replace(' ','')
value = "'%s'" % value
else:
if getattr(value, 'ISO', None) is not None:
value = "'%s'" % value.toZone('UTC').ISO()
else:
value = "'%s'" % sql_quote(str(value))
return value
def _quoteSQLKey(self, key):
"""Return a quoted string of the value.
XXX: Left for backwards compatability!
"""
format = self.getFormat()
type = self.getType()
if format is not None and type is not None:
if type == 'date':
key = "STR_TO_DATE(DATE_FORMAT(%s,'%s'),'%s')" % (key, format, format)
if type == 'float':
float_format = format.replace(' ','')
if float_format.find('.') >= 0:
precision = len(float_format.split('.')[1])
key = "TRUNCATE(%s,%s)" % (key, precision)
return key
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Query import Query
from Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.Interface.IQuery import IQuery
from Interface.Verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import profiler_decorator
class RelatedQuery(Query):
"""
A RelatedQuery represents the is a container for a join condition.
"""
@profiler_decorator
def __init__(self, search_key, join_condition=None, table_alias_list=None):
"""
search_key (SearchKey)
join_condition (Query)
If given, it will be registered and rendered by this query.
Deprecated
table_alias_list (list of 2-tuple of strings)
See setTableAliasList.
"""
self.search_key = search_key
self.join_condition = join_condition
self.table_alias_list = table_alias_list
@profiler_decorator
def setTableAliasList(self, table_alias_list):
"""
This function is here for backward compatibility.
table_alias_list (list of 2-tuples of strings)
Each 2-tuple contains the name of a related key parameter and the
table alias it must be mapped on (respectively).
"""
self.table_alias_list = table_alias_list
@profiler_decorator
def asSearchTextExpression(self, sql_catalog, column=None):
assert column is None
join_condition = self.join_condition
if join_condition is None:
result = None
else:
result = join_condition.asSearchTextExpression(sql_catalog, column=self.search_key.getColumn())
return result
@profiler_decorator
def asSQLExpression(self, sql_catalog, column_map, only_group_columns):
sql_expression_list = [self.search_key.buildSQLExpression(sql_catalog, column_map, only_group_columns, self.group)]
join_condition = self.join_condition
if join_condition is not None:
sql_expression_list.append(join_condition.asSQLExpression(sql_catalog, column_map, only_group_columns))
return SQLExpression(self, sql_expression_list=sql_expression_list, where_expression_operator='and')
@profiler_decorator
def registerColumnMap(self, sql_catalog, column_map):
self.group = self.search_key.registerColumnMap(column_map, table_alias_list=self.table_alias_list)
join_condition = self.join_condition
if join_condition is not None:
# Update its group
join_condition.setGroup(self.group)
# Propagate registration to embeded query
join_condition.registerColumnMap(sql_catalog, column_map)
def __repr__(self):
return '<%s on %r with %r>' % (self.__class__.__name__, self.search_key.getColumn(), self.join_condition)
verifyClass(IQuery, RelatedQuery)
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from Query import Query
from Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.Interface.IQuery import IQuery
from Interface.Verify import verifyClass
class SQLQuery(Query):
"""
This Query subclass is used to wrap raw SQL text.
Use of this class is strongly discouraged, and it is only here for
backward compatibility.
"""
def __init__(self, payload):
"""
payload (string)
Raw SQL text.
"""
if not isinstance(payload, basestring):
raise TypeError, 'Payload must be a string, got a %r: %r' % (type(payload), payload)
assert len(payload)
self.payload = '(' + payload + ')'
def asSearchText(self, sql_catalog):
return None
def asSQLExpression(self, sql_catalog, column_map, only_group_columns):
return SQLExpression(self, where_expression=self.payload)
def registerColumnMap(self, sql_catalog, column_map):
"""
There is nothing to register for this type of Query subclass.
"""
pass
def __repr__(self):
return '<%s (%r)>' % (self.__class__.__name__, self.payload)
verifyClass(IQuery, SQLQuery)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Ivan Tyagov <ivan@nexedi.com>
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
......@@ -26,124 +28,36 @@
#
##############################################################################
from DocumentTemplate.DT_Var import sql_quote
from SearchKey import SearchKey
from Products.ZSQLCatalog.Interface.ISearchKey import ISearchKey
from Interface.Verify import verifyClass
class DefaultKey(SearchKey):
""" DefaultKey key is an ERP5 portal_catalog search key which is used to render
SQL expression that will try to exactly one value.
It supports following special operator ['=', '%', '>' , '>=', '<', '<='] in
addition to main logical operators like ['OR', 'or', 'AND', 'and'].
Examples for title column:
* 'foo or bar' --> "title = 'foo' OR title = 'bar'"
* 'foo or =bar' --> "title = 'foo' OR title = 'bar'"
* '%foo% or bar' --> "title = '%foo%' OR title = 'bar'"
* 'Organisation Module' --> "title = 'Organisation Module'"
* '"Organisation Module"' --> "title = 'Organisation Module'"
* '="Organisation Module"' --> "title = 'Organisation Module'"
"""
# default type of sub Queries to be generated out fo a search string
default_key_type = 'default'
tokens = ('OR', 'AND', 'NOT', 'WORDSET', 'WORD',
'GREATERTHAN', 'GREATERTHANEQUAL',
'LESSTHAN', 'LESSTHANEQUAL')
sub_operators = ('GREATERTHAN', 'GREATERTHANEQUAL',
'LESSTHAN', 'LESSTHANEQUAL', 'NOT')
# Note: Order of placing rules (t_WORD for example) is very important
def t_OR(self, t):
r'\s+(OR|or)\s+'
# operator must have leading and trailing ONLY one white space character
# otherwise it's treated as a WORD
t.value = 'OR'
return t
def t_AND(self, t):
r'\s+(AND|and)\s+'
# operator must have leading and trailing ONLY one white space character
# otherwise it's treated as a WORD
t.value = 'AND'
return t
def t_NOT(self, t):
r'(\s+(NOT|not)\s+|!=)'
# operator must have leading and trailing ONLY one white space character
# otherwise it's treated as a WORD
t.value = '!='
return t
t_GREATERTHANEQUAL = r'>='
t_LESSTHANEQUAL = r'<='
t_GREATERTHAN = r'>'
t_LESSTHAN = r'<'
def t_WORD(self, t):
r'([^"\s<>!][\S\n]*|!([^=\s][\S\n]*)?)'
# newlines are allowed, because variations are delimited by newlines.
# WORD may contain arbitrary letters and numbers without white space
# WORD may contain '%' but not at the beginning or end (otherwise it's KEYWORD)
value = t.value.strip()
if value[0] == '=':
value = value[1:]
t.value = value
return t
def t_WORDSET(self, t):
r'"[^"]*"'
#r'"[\x7F-\xFF\w\d\s/%][\x7F-\xFF\w\d\s/%]*"'
# WORDSET is a combination of WORDs separated by white space
# and starting/ending with "
value = t.value.replace('"', '').strip()
t.value = value
return t
def quoteSQLString(self, value, format):
""" Return a quoted string of the value. """
if isinstance(value, (int, long,)):
return str(value)
return "'%s'" % sql_quote(value)
## def buildSQLExpressionFromSearchString(self, key, value, format, mode, range_value, stat__):
## """ Tokenize/analyze passed string value and generate SQL query expressions. """
## where_expressions = []
## select_expressions = []
## tokens = self.tokenize(value)
## operators_mapping_list = self.groupByOperator(tokens)
##
## # find if any logical operator exists
## tokens_values = []
## logical_operator_found = 0
## for token in tokens:
## if token.type not in ('WORDSET', 'WORD',):
## logical_operator_found = 1
## break
## tokens_values.append(token.value.replace("'", ""))
##
## # build expressions
## if not logical_operator_found:
## # no logical operator found so we assume that we search for a combination of words
## where_expressions.append("%s = '%s'" %(key, ' '.join(tokens_values)))
## else:
## # in the search string we have explicitly defined an operator
## for item in operators_mapping_list:
## row_tokens_values = []
## tokens = item['tokens']
## operator = item['operator']
## operator_value = None
## if operator is not None:
## # operator is standalone expression
## operator_value = operator.value
## where_expressions.append('%s' %operator_value)
## if len(tokens):
## # no it's not a stand alone expression,
## # determine it from list of tokens
## operator_value, sub_tokens = self.getOperatorForTokenList(tokens)
## row_tokens_values = [x.value for x in sub_tokens]
## where_expressions.append("%s %s '%s'" %(key, operator_value, ' '.join(row_tokens_values)))
## return where_expressions, select_expressions
This SearchKey behaves like an ExactMatch SearchKey, except if value is a
string and contains a '%' sign, in which case it behaves like a
KeywordKey.
"""
default_comparison_operator = '='
get_operator_from_value = True
def _guessComparisonOperator(self, value):
if isinstance(value, basestring) and '%' in value:
operator = 'like'
else:
operator = SearchKey._guessComparisonOperator(self, value)
return operator
def buildSearchTextExpression(self, operator, value, column=None):
operator_text = operator.getOperator()
if column is None:
column = self.getColumn()
if operator_text == 'like':
assert isinstance(value, basestring)
assert '%' in value
result = '%s:%s' % (column, value)
else:
result = SearchKey.buildSearchTextExpression(self, operator, value, column=column)
return result
verifyClass(ISearchKey, DefaultKey)
##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Ivan Tyagov <ivan@nexedi.com>
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
......@@ -27,77 +29,19 @@
##############################################################################
from SearchKey import SearchKey
import re
SEARCH_MODE_MAPPING = {'in_boolean_mode': 'IN BOOLEAN MODE',
'with_query_expansion': 'WITH QUERY EXPANSION'}
from Products.ZSQLCatalog.SearchText import parse
from Products.ZSQLCatalog.Interface.ISearchKey import ISearchKey
from Interface.Verify import verifyClass
class FullTextKey(SearchKey):
""" FullTextKey key is an ERP5 portal_catalog search key which is used to render
SQL expression that will try match all possible values using
MySQL's fulltext search support.
See syntax see MySQL's FullText search reference:
http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html
"""
This SearchKey generates SQL fulltext comparisons.
"""
default_comparison_operator = 'match'
get_operator_from_value = False
tokens = ('PLUS', 'MINUS', 'WORD', 'GREATERTHAN', 'LESSTHAN', 'LEFTPARENTHES',
'RIGHTPARENTHES', 'TILDE', 'ASTERISK', 'DOUBLEQUOTE',)
# SQL expressions patterns
relevance = '%s_relevance'
where_match_against = "MATCH %s AGAINST (%s %s)"
select_match_against_as = "MATCH %s AGAINST (%s %s) AS %s"
t_PLUS = r'(\+)'
t_MINUS = r'(\-)'
t_GREATERTHAN = r'(\>)'
t_LESSTHAN = r'(\<)'
t_LEFTPARENTHES = r'(\()'
t_RIGHTPARENTHES = r'(\))'
t_TILDE = r'(\~)'
t_ASTERISK = r'(\*)'
t_DOUBLEQUOTE = r'(\")'
def parseSearchText(self, value):
return parse(value)
def t_WORD(self, t):
r'[^\+\-<>\(\)\~\*\"\s]$|[^\+\-<>\(\)\~\*\"\s]+[^\*\"\s\)]'
#r'[^\+\-<>\(\)\~\*\"\s]\S*'
#r'[\x7F-\xFF\w\d][\x7F-\xFF\w\d]*'
# WORD may contain arbitrary letters and numbers without white space
return t
verifyClass(ISearchKey, FullTextKey)
def buildSQLExpression(self, key, value,
format=None, mode=None, range_value=None, stat__=None):
""" Analize token list and generate SQL expressions."""
tokens = self.tokenize(value)
# based on type tokens we may switch to different search mode
mode = SEARCH_MODE_MAPPING.get(mode, '')
if mode == '':
# determine it based on list of tokens i.e if we have only words
# leave as its but if we have '-' or '+' use boolean mode
for token in tokens:
if token.type != 'WORD':
mode = SEARCH_MODE_MAPPING['in_boolean_mode']
break
if mode == '' and len(tokens) > 1:
value = ' '.join(['+%s' % x.value for x in tokens])
mode = SEARCH_MODE_MAPPING['in_boolean_mode']
# split (if possible) to column.key
if key.find('.') != -1:
table, column = key.split('.')
relevance_key1 = self.relevance %key.replace('.', '_')
relevance_key2 = self.relevance %column
else:
relevance_key1 = self.relevance %key
relevance_key2 = None
select_expression_list = []
where_expression = self.where_match_against % (key,
self.quoteSQLString(value, ''), mode)
if not stat__:
# stat__ is an internal implementation artifact to prevent adding
# select_expression for countFolder
select_expression_list = [self.select_match_against_as % (key,
self.quoteSQLString(value, ''), mode, relevance_key1),]
if relevance_key2 is not None:
select_expression_list.append(self.select_match_against_as % (
key, self.quoteSQLString(value, ''), mode, relevance_key2))
return where_expression, select_expression_list
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
# Copyright (c) 2007-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from SearchKey import SearchKey
from Products.ZSQLCatalog.SearchText import parse
from Products.ZSQLCatalog.Interface.ISearchKey import ISearchKey
from Interface.Verify import verifyClass
class KeywordKey(SearchKey):
"""
This SearchKey generates matching comparison Queries suited for strings
with wilcards.
"""
default_comparison_operator = 'like'
get_operator_from_value = True
def parseSearchText(self, value):
return parse(value)
verifyClass(ISearchKey, KeywordKey)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# Automaticaly import all SearchKeys
import os
module_path = os.path.dirname(os.path.abspath(__file__))
global_dict = globals()
__relative_file__ = os.path.basename(__file__)
for filename in os.listdir(module_path):
if filename.endswith('.py') and filename != __relative_file__:
modulename = filename[:-3]
try:
module = __import__(modulename, global_dict, None, [])
except ImportError:
continue
This diff is collapsed.
This diff is collapsed.
from SearchTextParser import parse
This diff is collapsed.
This diff is collapsed.
......@@ -44,3 +44,6 @@ def initialize(context):
from AccessControl import ModuleSecurityInfo, ClassSecurityInfo
ModuleSecurityInfo('Products.ZSQLCatalog.SQLCatalog').declarePublic(
'ComplexQuery', 'Query', 'NegatedQuery',)
from Query import Query, SimpleQuery
from SearchKey import SearchKey
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment