Commit d47df833 authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki

use fulltext search in title and description.

* to quickly setup catalog_full_text table, you can use the following SQL.

  REPLACE INTO catalog_full_text SELECT uid, title, description FROM catalog;

* non fulltext queries like '=abc', '>abc', '%abc%' are supported.

* now erp5_full_text_mroonga_catalog is used for unit tests thus I recommend using it instead of erp5_full_text_myisam_catalog.

* to migrate existing MyISAM full_text table into Mroonga, you can use the following SQL.

  ALTER TABLE full_text DROP KEY SearchableText,
    ENGINE = mroonga,
    ADD FULLTEXT KEY SearchableText (`SearchableText`) COMMENT 'parser "TokenBigramSplitSymbolAlpha"';

* fulltext search score is no longer provided as (column_name) but now provided as (column_name)__score__.

* (category)_title, like source_title, related keys are automatically generated. (category)_description keys as well.
parent 315d57fb
<key_list> <key_list>
<key>accounting_transaction_line_node_uid | stock/node_uid/z_related_accounting_transaction_stock_line</key> <key>accounting_transaction_line_node_uid | stock/node_uid/z_related_accounting_transaction_stock_line</key>
<key>accounting_transaction_line_total_price | stock/total_price/z_related_accounting_transaction_stock_line</key> <key>accounting_transaction_line_total_price | stock/total_price/z_related_accounting_transaction_stock_line</key>
<key>accounting_transaction_mirror_section_title | catalog/title/z_related_accounting_transaction_mirror_section</key> <key>accounting_transaction_mirror_section_title | catalog_full_text/title/z_related_accounting_transaction_mirror_section</key>
<key>accounting_transaction_payment_title | catalog/title/z_related_accounting_transaction_payment</key> <key>accounting_transaction_payment_title | catalog_full_text/title/z_related_accounting_transaction_payment</key>
<key>accounting_transaction_project_title | catalog/title/z_related_accounting_transaction_project</key> <key>accounting_transaction_project_title | catalog_full_text/title/z_related_accounting_transaction_project</key>
<key>accounting_transaction_section_title | catalog/title/z_related_accounting_transaction_section</key> <key>accounting_transaction_section_title | catalog_full_text/title/z_related_accounting_transaction_section</key>
<key>preferred_gap_id | category,catalog/id/z_related_preferred_gap</key> <key>preferred_gap_id | category,catalog/id/z_related_preferred_gap</key>
<key>preferred_gap_strict_membership_id | category,catalog/id/z_related_strict_membership_preferred_gap</key> <key>preferred_gap_strict_membership_id | category,catalog/id/z_related_strict_membership_preferred_gap</key>
</key_list> </key_list>
\ No newline at end of file
preferred_gap_id | category,catalog/id/z_related_preferred_gap preferred_gap_id | category,catalog/id/z_related_preferred_gap
preferred_gap_strict_membership_id | category,catalog/id/z_related_strict_membership_preferred_gap preferred_gap_strict_membership_id | category,catalog/id/z_related_strict_membership_preferred_gap
accounting_transaction_mirror_section_title | catalog/title/z_related_accounting_transaction_mirror_section accounting_transaction_mirror_section_title | catalog_full_text/title/z_related_accounting_transaction_mirror_section
accounting_transaction_section_title | catalog/title/z_related_accounting_transaction_section accounting_transaction_section_title | catalog_full_text/title/z_related_accounting_transaction_section
accounting_transaction_project_title | catalog/title/z_related_accounting_transaction_project accounting_transaction_project_title | catalog_full_text/title/z_related_accounting_transaction_project
accounting_transaction_payment_title | catalog/title/z_related_accounting_transaction_payment accounting_transaction_payment_title | catalog_full_text/title/z_related_accounting_transaction_payment
accounting_transaction_line_node_uid | stock/node_uid/z_related_accounting_transaction_stock_line accounting_transaction_line_node_uid | stock/node_uid/z_related_accounting_transaction_stock_line
accounting_transaction_line_total_price | stock/total_price/z_related_accounting_transaction_stock_line accounting_transaction_line_total_price | stock/total_price/z_related_accounting_transaction_stock_line
\ No newline at end of file
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
from DateTime import DateTime\n from DateTime import DateTime\n
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery\n
\n \n
# params\n # params\n
section_title = \'My Organisation\'\n section_title = \'My Organisation\'\n
...@@ -90,7 +91,7 @@ if 1:\n ...@@ -90,7 +91,7 @@ if 1:\n
def getAccountByTitle(title):\n def getAccountByTitle(title):\n
account_list = [x.getObject().getRelativeUrl() for x in\n account_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Account\',\n portal.portal_catalog(portal_type=\'Account\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(account_list) == 1, \\\n assert len(account_list) == 1, \\\n
\'%d account with title "%s"\' % (len(account_list), title)\n \'%d account with title "%s"\' % (len(account_list), title)\n
return account_list[0]\n return account_list[0]\n
...@@ -98,7 +99,7 @@ def getAccountByTitle(title):\n ...@@ -98,7 +99,7 @@ def getAccountByTitle(title):\n
def getOrganisationByTitle(title):\n def getOrganisationByTitle(title):\n
document_list = [x.getObject().getRelativeUrl() for x in\n document_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Organisation\',\n portal.portal_catalog(portal_type=\'Organisation\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(document_list) == 1, \\\n assert len(document_list) == 1, \\\n
\'%d organisation with title "%s"\' % (len(document_list), title)\n \'%d organisation with title "%s"\' % (len(document_list), title)\n
return document_list[0]\n return document_list[0]\n
...@@ -109,14 +110,14 @@ euro_resource = \'currency_module/euro\'\n ...@@ -109,14 +110,14 @@ euro_resource = \'currency_module/euro\'\n
def getBankAccountByTitle(title):\n def getBankAccountByTitle(title):\n
document_list = [x.getObject().getRelativeUrl() for x in\n document_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Bank Account\',\n portal.portal_catalog(portal_type=\'Bank Account\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(document_list) == 1, \\\n assert len(document_list) == 1, \\\n
\'%d Bank Account with title "%s"\' % (len(document_list), title)\n \'%d Bank Account with title "%s"\' % (len(document_list), title)\n
return document_list[0]\n return document_list[0]\n
\n \n
product_list = [o.getObject() for o in portal.portal_catalog(\n product_list = [o.getObject() for o in portal.portal_catalog(\n
portal_type=\'Product\',\n portal_type=\'Product\',\n
title=\'Dummy Product for testing\')]\n title=SimpleQuery(title=\'Dummy Product for testing\', comparison_operator=\'=\'))]\n
if product_list:\n if product_list:\n
product = product_list[0]\n product = product_list[0]\n
else:\n else:\n
......
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
from DateTime import DateTime\n from DateTime import DateTime\n
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery\n
\n \n
# params\n # params\n
section_title = \'My Organisation\'\n section_title = \'My Organisation\'\n
...@@ -80,7 +81,7 @@ if 1:\n ...@@ -80,7 +81,7 @@ if 1:\n
def getAccountByTitle(title):\n def getAccountByTitle(title):\n
account_list = [x.getObject().getRelativeUrl() for x in\n account_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Account\',\n portal.portal_catalog(portal_type=\'Account\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(account_list) == 1, \\\n assert len(account_list) == 1, \\\n
\'%d account with title "%s"\' % (len(account_list), title)\n \'%d account with title "%s"\' % (len(account_list), title)\n
return account_list[0]\n return account_list[0]\n
...@@ -88,7 +89,7 @@ def getAccountByTitle(title):\n ...@@ -88,7 +89,7 @@ def getAccountByTitle(title):\n
def getOrganisationByTitle(title):\n def getOrganisationByTitle(title):\n
document_list = [x.getObject().getRelativeUrl() for x in\n document_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Organisation\',\n portal.portal_catalog(portal_type=\'Organisation\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(document_list) == 1, \\\n assert len(document_list) == 1, \\\n
\'%d organisation with title "%s"\' % (len(document_list), title)\n \'%d organisation with title "%s"\' % (len(document_list), title)\n
return document_list[0]\n return document_list[0]\n
...@@ -106,7 +107,7 @@ euro_resource = getCurrencyByReference(\'EUR\')\n ...@@ -106,7 +107,7 @@ euro_resource = getCurrencyByReference(\'EUR\')\n
def getBankAccountByTitle(title):\n def getBankAccountByTitle(title):\n
document_list = [x.getObject().getRelativeUrl() for x in\n document_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Bank Account\',\n portal.portal_catalog(portal_type=\'Bank Account\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(document_list) == 1, \\\n assert len(document_list) == 1, \\\n
\'%d Bank Account with title "%s"\' % (len(document_list), title)\n \'%d Bank Account with title "%s"\' % (len(document_list), title)\n
return document_list[0]\n return document_list[0]\n
......
...@@ -58,6 +58,7 @@ business_process = \'business_process_module/erp5_default_business_process\'\n ...@@ -58,6 +58,7 @@ business_process = \'business_process_module/erp5_default_business_process\'\n
portal = context.getPortalObject()\n portal = context.getPortalObject()\n
accounting_module = portal.accounting_module\n accounting_module = portal.accounting_module\n
from DateTime import DateTime\n from DateTime import DateTime\n
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery\n
year = 2005\n year = 2005\n
\n \n
# if the previous test didn\'t change input data, no need to recreate content\n # if the previous test didn\'t change input data, no need to recreate content\n
...@@ -82,7 +83,7 @@ if 1:\n ...@@ -82,7 +83,7 @@ if 1:\n
def getAccountByTitle(title):\n def getAccountByTitle(title):\n
account_list = [x.getObject().getRelativeUrl() for x in\n account_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Account\',\n portal.portal_catalog(portal_type=\'Account\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(account_list) == 1, \\\n assert len(account_list) == 1, \\\n
\'%d account with title "%s"\' % (len(account_list), title)\n \'%d account with title "%s"\' % (len(account_list), title)\n
return account_list[0]\n return account_list[0]\n
...@@ -90,7 +91,7 @@ def getAccountByTitle(title):\n ...@@ -90,7 +91,7 @@ def getAccountByTitle(title):\n
def getOrganisationByTitle(title):\n def getOrganisationByTitle(title):\n
document_list = [x.getObject().getRelativeUrl() for x in\n document_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Organisation\',\n portal.portal_catalog(portal_type=\'Organisation\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(document_list) == 1, \\\n assert len(document_list) == 1, \\\n
\'%d organisation with title "%s"\' % (len(document_list), title)\n \'%d organisation with title "%s"\' % (len(document_list), title)\n
return document_list[0]\n return document_list[0]\n
...@@ -99,7 +100,7 @@ section = getOrganisationByTitle(section_title)\n ...@@ -99,7 +100,7 @@ section = getOrganisationByTitle(section_title)\n
def getPersonByTitle(title):\n def getPersonByTitle(title):\n
document_list = [x.getObject().getRelativeUrl() for x in\n document_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Person\',\n portal.portal_catalog(portal_type=\'Person\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(document_list) == 1, \\\n assert len(document_list) == 1, \\\n
\'%d person with title "%s"\' % (len(document_list), title)\n \'%d person with title "%s"\' % (len(document_list), title)\n
return document_list[0]\n return document_list[0]\n
...@@ -116,7 +117,7 @@ euro_resource = getCurrencyByReference(\'EUR\')\n ...@@ -116,7 +117,7 @@ euro_resource = getCurrencyByReference(\'EUR\')\n
def getBankAccountByTitle(title):\n def getBankAccountByTitle(title):\n
document_list = [x.getObject().getRelativeUrl() for x in\n document_list = [x.getObject().getRelativeUrl() for x in\n
portal.portal_catalog(portal_type=\'Bank Account\',\n portal.portal_catalog(portal_type=\'Bank Account\',\n
title=title)]\n title=SimpleQuery(title=title, comparison_operator=\'=\'))]\n
assert len(document_list) == 1, \\\n assert len(document_list) == 1, \\\n
\'%d Bank Account with title "%s"\' % (len(document_list), title)\n \'%d Bank Account with title "%s"\' % (len(document_list), title)\n
return document_list[0]\n return document_list[0]\n
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n <value> <string>portal = context.getPortalObject()\n
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery\n
\n \n
# validate rules\n # validate rules\n
for rule in portal.portal_rules.objectValues():\n for rule in portal.portal_rules.objectValues():\n
...@@ -129,7 +130,7 @@ stool.setSelectionColumns(\'account_module_selection\',\n ...@@ -129,7 +130,7 @@ stool.setSelectionColumns(\'account_module_selection\',\n
\n \n
# delete the "dummy account" we create in test_account_gap_parallel_list_field\n # delete the "dummy account" we create in test_account_gap_parallel_list_field\n
dummy_account_list = portal.account_module.searchFolder(\n dummy_account_list = portal.account_module.searchFolder(\n
title=\'Dummy Account for UI Test\')\n title=SimpleQuery(title=\'Dummy Account for UI Test\', comparison_operator=\'=\'))\n
if dummy_account_list:\n if dummy_account_list:\n
portal.account_module.manage_delObjects([dummy_account_list[0].getId()])\n portal.account_module.manage_delObjects([dummy_account_list[0].getId()])\n
\n \n
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<key>child_address_SearchableText | catalog,full_text/SearchableText/z_related_child_address</key> <key>child_address_SearchableText | catalog,full_text/SearchableText/z_related_child_address</key>
<key>child_telephone_SearchableText | catalog,full_text/SearchableText/z_related_child_telephone</key> <key>child_telephone_SearchableText | catalog,full_text/SearchableText/z_related_child_telephone</key>
<key>default_email_text | catalog,email/url_string/z_related_default_email</key> <key>default_email_text | catalog,email/url_string/z_related_default_email</key>
<key>destination_person_title | category,catalog/title/z_related_destination_person</key> <key>destination_person_title | category,catalog_full_text/title/z_related_destination_person</key>
<key>related_resource_use_uid | category,category,catalog,catalog/uid/z_related_resource_use</key> <key>related_resource_use_uid | category,category,catalog,catalog/uid/z_related_resource_use</key>
<key>source_organisation_title | category,catalog/title/z_related_source_organisation</key> <key>source_organisation_title | category,catalog_full_text/title/z_related_source_organisation</key>
<key>source_person_title | category,catalog/title/z_related_source_person</key> <key>source_person_title | category,catalog_full_text/title/z_related_source_person</key>
</key_list> </key_list>
\ No newline at end of file
source_organisation_title | category,catalog/title/z_related_source_organisation source_organisation_title | category,catalog_full_text/title/z_related_source_organisation
source_person_title | category,catalog/title/z_related_source_person source_person_title | category,catalog_full_text/title/z_related_source_person
destination_person_title | category,catalog/title/z_related_destination_person destination_person_title | category,catalog_full_text/title/z_related_destination_person
default_email_text | catalog,email/url_string/z_related_default_email default_email_text | catalog,email/url_string/z_related_default_email
related_resource_use_uid | category,category,catalog,catalog/uid/z_related_resource_use related_resource_use_uid | category,category,catalog,catalog/uid/z_related_resource_use
child_address_SearchableText | catalog,full_text/SearchableText/z_related_child_address child_address_SearchableText | catalog,full_text/SearchableText/z_related_child_address
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
erp5_core_proxy_field_legacy erp5_core_proxy_field_legacy
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_workflow erp5_workflow
\ No newline at end of file
erp5_core_proxy_field_legacy erp5_core_proxy_field_legacy
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_workflow erp5_workflow
\ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
erp5_core_proxy_field_legacy erp5_core_proxy_field_legacy
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_workflow erp5_workflow
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>from Products.ZSQLCatalog.SQLCatalog import Query, SimpleQuery, AndQuery\n
\n
########### CONFIGURATION ######################################\n
# This is a query for search translated properties of Person #\n
# documents. If you want to get other results. Customise this. # \n
################################################################\n
\n
query = AndQuery(SimpleQuery(**{\'content_translation.translated_text\': value, \'comparison_operator\': \'match\'}),\n
Query(**{\'content_translation.property_name\': \'title\'})\n
)\n
\n
################################################################\n
# Above query must make SQL condition like this. #\n
################################################################\n
# MATCH(content_translation.translated_text) AGAINST({value})\n
# AND content_translation.property_name = \'title\'\n
\n
return query\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>value</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SQLCatalog_makeContentTranslationSearchQuery</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<key_list> <key_list>
<key>content_translation_title | SQLCatalog_makeContentTranslationSearchQuery</key> <key>content_translation_title | SQLCatalog_makeTranslatedTitleQuery</key>
</key_list> </key_list>
\ No newline at end of file
content_translation_title | SQLCatalog_makeContentTranslationSearchQuery content_translation_title | SQLCatalog_makeTranslatedTitleQuery
\ No newline at end of file \ No newline at end of file
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
<key>related_source_or_source_decision_or_destination_or_destination_decision | category,catalog,catalog/uid/z_related_source_or_source_decision_or_destination_or_destination_decision</key> <key>related_source_or_source_decision_or_destination_or_destination_decision | category,catalog,catalog/uid/z_related_source_or_source_decision_or_destination_or_destination_decision</key>
<key>related_source_or_source_section_or_destination_or_destination_section | category,catalog,catalog/uid/z_related_source_or_source_section_or_destination_or_destination_section</key> <key>related_source_or_source_section_or_destination_or_destination_section | category,catalog,catalog/uid/z_related_source_or_source_section_or_destination_or_destination_section</key>
<key>related_source_section_or_destination_section | category,catalog,catalog/uid/z_related_source_section_or_destination_section</key> <key>related_source_section_or_destination_section | category,catalog,catalog/uid/z_related_source_section_or_destination_section</key>
<key>source_organisation_title | category,catalog/title/z_related_source_organisation</key> <key>source_organisation_title | category,catalog_full_text/title/z_related_source_organisation</key>
</key_list> </key_list>
\ No newline at end of file
source_organisation_title | category,catalog/title/z_related_source_organisation source_organisation_title | category,catalog_full_text/title/z_related_source_organisation
event_causality_ticket_uid | category,catalog,category,catalog/uid/z_related_event_causality_ticket event_causality_ticket_uid | category,catalog,category,catalog/uid/z_related_event_causality_ticket
related_source_or_destination | category,catalog,catalog/uid/z_related_source_or_destination related_source_or_destination | category,catalog,catalog/uid/z_related_source_or_destination
related_source_section_or_destination_section | category,catalog,catalog/uid/z_related_source_section_or_destination_section related_source_section_or_destination_section | category,catalog,catalog/uid/z_related_source_section_or_destination_section
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>return (\'erp5_core_proxy_field_legacy\',\n <value> <string>return (\'erp5_core_proxy_field_legacy\',\n
\'erp5_full_text_myisam_catalog\',\n \'erp5_full_text_mroonga_catalog\',\n
\'erp5_base\',\n \'erp5_base\',\n
\'erp5_workflow\',\n \'erp5_workflow\',\n
\'erp5_configurator\',\n \'erp5_configurator\',\n
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>return (\'erp5_core_proxy_field_legacy\',\n <value> <string>return (\'erp5_core_proxy_field_legacy\',\n
\'erp5_full_text_myisam_catalog\',\n \'erp5_full_text_mroonga_catalog\',\n
\'erp5_base\',\n \'erp5_base\',\n
\'erp5_simulation\',\n \'erp5_simulation\',\n
\'erp5_dhtml_style\',\n \'erp5_dhtml_style\',\n
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_web erp5_web
erp5_ingestion_mysql_innodb_catalog erp5_ingestion_mysql_innodb_catalog
......
<key_list>
<key>SearchableText</key>
<key>full_text.SearchableText</key>
</key_list>
\ No newline at end of file
...@@ -68,8 +68,9 @@ for path in path_list:\n ...@@ -68,8 +68,9 @@ for path in path_list:\n
try:\n try:\n
tmp_dict = {}\n tmp_dict = {}\n
for property in property_list:\n for property in property_list:\n
if property == \'SearchableText\':\n getter = getattr(obj, property, None)\n
value = obj.SearchableText()\n if getter is not None and callable(getter):\n
value = getter()\n
else:\n else:\n
value = getattr(obj, \'get%s\' % UpperCase(property))()\n value = getattr(obj, \'get%s\' % UpperCase(property))()\n
tmp_dict[property] = value\n tmp_dict[property] = value\n
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_hook</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z0_drop_catalog_fulltext</string> </value>
</item>
<item>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>max_rows_</string> </key>
<value> <int>1000</int> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string>DROP TABLE IF EXISTS catalog_full_text</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<catalog_method>
<item key="sql_uncatalog_object" type="int">
<value>1</value>
</item>
</catalog_method>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>uid</string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_hook</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_deferred_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z0_uncatalog_catalog_fulltext</string> </value>
</item>
<item>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>max_rows_</string> </key>
<value> <int>1000</int> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
DELETE FROM catalog_full_text WHERE <dtml-sqltest uid op=eq type=int>
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<catalog_method>
<item key="sql_catalog_object_list" type="int">
<value>1</value>
</item>
</catalog_method>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>uid\r\n
getTitle\r\n
getDescription</string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_hook</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_deferred_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_catalog_catalog_fulltext_list</string> </value>
</item>
<item>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>max_rows_</string> </key>
<value> <int>1000</int> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
REPLACE INTO\n
catalog_full_text (`uid`, `title`, `description`)\n
VALUES\n
<dtml-in prefix="loop" expr="_.range(_.len(uid))">\n
(\n
<dtml-sqlvar expr="uid[loop_item]" type="int">, \n
<dtml-sqlvar expr="getTitle[loop_item]" type="string" optional>,\n
<dtml-sqlvar expr="getDescription[loop_item]" type="string" optional>\n
)<dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -55,21 +55,16 @@ SearchableText</string> </value> ...@@ -55,21 +55,16 @@ SearchableText</string> </value>
<key> <string>src</string> </key> <key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
DELETE FROM\n <dtml-let document_list="[]" delete_list="[]">\n
full_text\n
WHERE\n
<dtml-in uid>\n
uid=<dtml-sqlvar sequence-item type="int"><dtml-if sequence-end><dtml-else> OR </dtml-if>\n
</dtml-in>\n
;\n
<dtml-var "\'\\0\'"><dtml-let document_list="[]">\n
<dtml-in prefix="loop" expr="_.range(_.len(uid))">\n <dtml-in prefix="loop" expr="_.range(_.len(uid))">\n
<dtml-if "SearchableText[loop_item]">\n <dtml-if "SearchableText[loop_item]">\n
<dtml-call expr="document_list.append(loop_item)">\n <dtml-call expr="document_list.append(loop_item)">\n
<dtml-else>\n
<dtml-call expr="delete_list.append(loop_item)">\n
</dtml-if>\n </dtml-if>\n
</dtml-in>\n </dtml-in>\n
<dtml-if expr="_.len(document_list) > 0">\n <dtml-if expr="_.len(document_list) > 0">\n
INSERT INTO\n REPLACE INTO\n
full_text\n full_text\n
VALUES\n VALUES\n
<dtml-in prefix="loop" expr="document_list">\n <dtml-in prefix="loop" expr="document_list">\n
...@@ -79,7 +74,19 @@ VALUES\n ...@@ -79,7 +74,19 @@ VALUES\n
)<dtml-unless sequence-end>,</dtml-unless>\n )<dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n </dtml-in>\n
</dtml-if>\n </dtml-if>\n
</dtml-let> <dtml-if expr="_.len(delete_list) > 0">\n
<dtml-var sql_delimiter>\n
DELETE FROM\n
full_text\n
WHERE uid IN\n
( \n
<dtml-in prefix="loop" expr="delete_list">\n
<dtml-sqlvar expr="uid[loop_item]" type="int"><dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n
)\n
</dtml-if>\n
</dtml-let>\n
]]></string> </value> ]]></string> </value>
</item> </item>
......
<catalog_method>
<item key="sql_clear_catalog" type="int">
<value>1</value>
</item>
</catalog_method>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_hook</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_create_catalog_fulltext</string> </value>
</item>
<item>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>max_rows_</string> </key>
<value> <int>1000</int> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string># Host:\n
# Database: test\n
# Table: \'catalog_full_text\'\n
#\n
CREATE TABLE `catalog_full_text` (\n
`uid` BIGINT UNSIGNED NOT NULL,\n
`title` varchar(255) default \'\',\n
`description` text,\n
PRIMARY KEY (`uid`),\n
FULLTEXT `title` (`title`) COMMENT \'parser "TokenBigramSplitSymbolAlpha"\',\n
FULLTEXT `description` (`description`) COMMENT \'parser "TokenBigramSplitSymbolAlpha"\'\n
) ENGINE=mroonga;\n
</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
`content_language` VARBINARY(100),\n `content_language` VARBINARY(100),\n
`translated_text` TEXT,\n `translated_text` TEXT,\n
PRIMARY KEY (`uid`, `property_name`, `content_language`),\n PRIMARY KEY (`uid`, `property_name`, `content_language`),\n
FULLTEXT KEY (`translated_text`)\n FULLTEXT KEY (`translated_text`) COMMENT \'parser "TokenBigramSplitSymbolAlpha"\'\n
) ENGINE=mroonga;\n ) ENGINE=mroonga;\n
</string> </value> </string> </value>
</item> </item>
......
...@@ -66,7 +66,7 @@ CREATE TABLE `full_text` (\n ...@@ -66,7 +66,7 @@ CREATE TABLE `full_text` (\n
`uid` BIGINT UNSIGNED NOT NULL,\n `uid` BIGINT UNSIGNED NOT NULL,\n
`SearchableText` MEDIUMTEXT,\n `SearchableText` MEDIUMTEXT,\n
PRIMARY KEY (`uid`),\n PRIMARY KEY (`uid`),\n
FULLTEXT `SearchableText` (`SearchableText`)\n FULLTEXT `SearchableText` (`SearchableText`) COMMENT \'parser "TokenBigramSplitSymbolAlpha"\'\n
) ENGINE=mroonga;\n ) ENGINE=mroonga;\n
</string> </value> </string> </value>
</item> </item>
......
<key_list>
<key>career_skill_title | category,catalog,catalog_full_text/title/z_related_career_skill</key>
<key>description | catalog_full_text/description/z_related_uid</key>
<key>parent_description | catalog_full_text/description/z_related_parent</key>
<key>parent_title | catalog_full_text/title/z_related_parent</key>
<key>stock_explanation_title | catalog_full_text/title/z_related_explanation_from_stock</key>
<key>stock_mirror_section_title | catalog_full_text/title/z_related_mirror_section_uid_from_stock</key>
<key>stock_node_title | catalog_full_text/title/z_related_node_uid_from_stock</key>
<key>title | catalog_full_text/title/z_related_uid</key>
</key_list>
\ No newline at end of file
<key_list> <key_list>
<key>catalog_full_text</key>
<key>full_text</key> <key>full_text</key>
</key_list> </key_list>
\ No newline at end of file
<key_list>
<key>SearchableText | MroongaBooleanFullTextKey</key>
<key>catalog_full_text.description | MroongaBooleanFullTextKey</key>
<key>catalog_full_text.title | MroongaBooleanFullTextKey</key>
<key>description | MroongaBooleanFullTextKey</key>
<key>full_text.SearchableText | MroongaBooleanFullTextKey</key>
<key>title | MroongaBooleanFullTextKey</key>
</key_list>
\ No newline at end of file
SearchableText
full_text.SearchableText
\ No newline at end of file
erp5_mysql_innodb/SQLCatalog_deferFullTextIndex erp5_mysql_innodb/SQLCatalog_deferFullTextIndex
erp5_mysql_innodb/SQLCatalog_deferFullTextIndexActivity erp5_mysql_innodb/SQLCatalog_deferFullTextIndexActivity
erp5_mysql_innodb/SQLCatalog_makeFullTextQuery erp5_mysql_innodb/SQLCatalog_makeFullTextQuery
erp5_mysql_innodb/z0_drop_catalog_fulltext
erp5_mysql_innodb/z0_drop_content_translation erp5_mysql_innodb/z0_drop_content_translation
erp5_mysql_innodb/z0_drop_fulltext erp5_mysql_innodb/z0_drop_fulltext
erp5_mysql_innodb/z0_uncatalog_catalog_fulltext
erp5_mysql_innodb/z0_uncatalog_fulltext erp5_mysql_innodb/z0_uncatalog_fulltext
erp5_mysql_innodb/z_catalog_catalog_fulltext_list
erp5_mysql_innodb/z_catalog_fulltext_list erp5_mysql_innodb/z_catalog_fulltext_list
erp5_mysql_innodb/z_create_catalog_fulltext
erp5_mysql_innodb/z_create_content_translation erp5_mysql_innodb/z_create_content_translation
erp5_mysql_innodb/z_create_fulltext erp5_mysql_innodb/z_create_fulltext
\ No newline at end of file
career_skill_title | category,catalog,catalog_full_text/title/z_related_career_skill
description | catalog_full_text/description/z_related_uid
parent_description | catalog_full_text/description/z_related_parent
parent_title | catalog_full_text/title/z_related_parent
stock_explanation_title | catalog_full_text/title/z_related_explanation_from_stock
stock_mirror_section_title | catalog_full_text/title/z_related_mirror_section_uid_from_stock
stock_node_title | catalog_full_text/title/z_related_node_uid_from_stock
title | catalog_full_text/title/z_related_uid
\ No newline at end of file
catalog_full_text
full_text full_text
\ No newline at end of file
SearchableText | MroongaBooleanFullTextKey
catalog_full_text.description | MroongaBooleanFullTextKey
catalog_full_text.title | MroongaBooleanFullTextKey
description | MroongaBooleanFullTextKey
full_text.SearchableText | MroongaBooleanFullTextKey
title | MroongaBooleanFullTextKey
\ No newline at end of file
<key_list> <key_list>
<key>SearchableText</key> <key>SearchableText</key>
<key>full_text.SearchableText</key> <key>catalog_full_text.description</key>
<key>catalog_full_text.title</key> |
<key>description</key> |
<key>full_text.SearchableText</key> |
<key>title</key> |
</key_list> </key_list>
\ No newline at end of file
...@@ -68,8 +68,9 @@ for path in path_list:\n ...@@ -68,8 +68,9 @@ for path in path_list:\n
try:\n try:\n
tmp_dict = {}\n tmp_dict = {}\n
for property in property_list:\n for property in property_list:\n
if property == \'SearchableText\':\n getter = getattr(obj, property, None)\n
value = obj.SearchableText()\n if getter is not None and callable(getter):\n
value = getter()\n
else:\n else:\n
value = getattr(obj, \'get%s\' % UpperCase(property))()\n value = getattr(obj, \'get%s\' % UpperCase(property))()\n
tmp_dict[property] = value\n tmp_dict[property] = value\n
......
<catalog_method>
<item key="sql_clear_catalog" type="int">
<value>1</value>
</item>
</catalog_method>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_hook</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z0_drop_catalog_fulltext</string> </value>
</item>
<item>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>max_rows_</string> </key>
<value> <int>1000</int> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string>DROP TABLE IF EXISTS catalog_full_text</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<catalog_method>
<item key="sql_uncatalog_object" type="int">
<value>1</value>
</item>
</catalog_method>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>uid</string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_hook</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_deferred_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z0_uncatalog_catalog_fulltext</string> </value>
</item>
<item>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>max_rows_</string> </key>
<value> <int>1000</int> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
DELETE FROM catalog_full_text WHERE <dtml-sqltest uid op=eq type=int>
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<catalog_method>
<item key="sql_catalog_object_list" type="int">
<value>1</value>
</item>
</catalog_method>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>uid\r\n
getTitle\r\n
getDescription</string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_hook</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_deferred_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_catalog_catalog_fulltext_list</string> </value>
</item>
<item>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>max_rows_</string> </key>
<value> <int>1000</int> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
REPLACE INTO\n
catalog_full_text (`uid`, `title`, `description`)\n
VALUES\n
<dtml-in prefix="loop" expr="_.range(_.len(uid))">\n
(\n
<dtml-sqlvar expr="uid[loop_item]" type="int">, \n
<dtml-sqlvar expr="getTitle[loop_item]" type="string" optional>,\n
<dtml-sqlvar expr="getDescription[loop_item]" type="string" optional>\n
)<dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -55,21 +55,16 @@ SearchableText</string> </value> ...@@ -55,21 +55,16 @@ SearchableText</string> </value>
<key> <string>src</string> </key> <key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
DELETE FROM\n <dtml-let document_list="[]" delete_list="[]">\n
full_text\n
WHERE\n
<dtml-in uid>\n
uid=<dtml-sqlvar sequence-item type="int"><dtml-if sequence-end><dtml-else> OR </dtml-if>\n
</dtml-in>\n
;\n
<dtml-var "\'\\0\'"><dtml-let document_list="[]">\n
<dtml-in prefix="loop" expr="_.range(_.len(uid))">\n <dtml-in prefix="loop" expr="_.range(_.len(uid))">\n
<dtml-if "SearchableText[loop_item]">\n <dtml-if "SearchableText[loop_item]">\n
<dtml-call expr="document_list.append(loop_item)">\n <dtml-call expr="document_list.append(loop_item)">\n
<dtml-else>\n
<dtml-call expr="delete_list.append(loop_item)">\n
</dtml-if>\n </dtml-if>\n
</dtml-in>\n </dtml-in>\n
<dtml-if expr="_.len(document_list) > 0">\n <dtml-if expr="_.len(document_list) > 0">\n
INSERT INTO\n REPLACE INTO\n
full_text\n full_text\n
VALUES\n VALUES\n
<dtml-in prefix="loop" expr="document_list">\n <dtml-in prefix="loop" expr="document_list">\n
...@@ -79,7 +74,19 @@ VALUES\n ...@@ -79,7 +74,19 @@ VALUES\n
)<dtml-unless sequence-end>,</dtml-unless>\n )<dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n </dtml-in>\n
</dtml-if>\n </dtml-if>\n
</dtml-let> <dtml-if expr="_.len(delete_list) > 0">\n
<dtml-var sql_delimiter>\n
DELETE FROM\n
full_text\n
WHERE uid IN\n
( \n
<dtml-in prefix="loop" expr="delete_list">\n
<dtml-sqlvar expr="uid[loop_item]" type="int"><dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n
)\n
</dtml-if>\n
</dtml-let>\n
]]></string> </value> ]]></string> </value>
</item> </item>
......
<catalog_method>
<item key="sql_clear_catalog" type="int">
<value>1</value>
</item>
</catalog_method>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_hook</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_create_catalog_fulltext</string> </value>
</item>
<item>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>max_rows_</string> </key>
<value> <int>1000</int> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string># Host:\n
# Database: test\n
# Table: \'catalog_full_text\'\n
#\n
CREATE TABLE `catalog_full_text` (\n
`uid` BIGINT UNSIGNED NOT NULL,\n
`title` varchar(255) default \'\',\n
`description` text,\n
PRIMARY KEY (`uid`),\n
FULLTEXT `title` (`title`),\n
FULLTEXT `description` (`description`)\n
) ENGINE=MyISAM;\n
</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<key_list>
<key>career_skill_title | category,catalog,catalog_full_text/title/z_related_career_skill</key>
<key>description | catalog_full_text/description/z_related_uid</key>
<key>parent_description | catalog_full_text/description/z_related_parent</key>
<key>parent_title | catalog_full_text/title/z_related_parent</key>
<key>stock_explanation_title | catalog_full_text/title/z_related_explanation_from_stock</key>
<key>stock_mirror_section_title | catalog_full_text/title/z_related_mirror_section_uid_from_stock</key>
<key>stock_node_title | catalog_full_text/title/z_related_node_uid_from_stock</key>
<key>title | catalog_full_text/title/z_related_uid</key>
</key_list>
\ No newline at end of file
<key_list> <key_list>
<key>catalog_full_text</key>
<key>full_text</key> <key>full_text</key>
</key_list> </key_list>
\ No newline at end of file
SearchableText SearchableText
catalog_full_text.description
catalog_full_text.title
description
full_text.SearchableText full_text.SearchableText
title
\ No newline at end of file
erp5_mysql_innodb/SQLCatalog_deferFullTextIndex erp5_mysql_innodb/SQLCatalog_deferFullTextIndex
erp5_mysql_innodb/SQLCatalog_deferFullTextIndexActivity erp5_mysql_innodb/SQLCatalog_deferFullTextIndexActivity
erp5_mysql_innodb/SQLCatalog_makeFullTextQuery erp5_mysql_innodb/SQLCatalog_makeFullTextQuery
erp5_mysql_innodb/z0_drop_catalog_fulltext
erp5_mysql_innodb/z0_drop_content_translation erp5_mysql_innodb/z0_drop_content_translation
erp5_mysql_innodb/z0_drop_fulltext erp5_mysql_innodb/z0_drop_fulltext
erp5_mysql_innodb/z0_uncatalog_catalog_fulltext
erp5_mysql_innodb/z0_uncatalog_fulltext erp5_mysql_innodb/z0_uncatalog_fulltext
erp5_mysql_innodb/z_catalog_catalog_fulltext_list
erp5_mysql_innodb/z_catalog_fulltext_list erp5_mysql_innodb/z_catalog_fulltext_list
erp5_mysql_innodb/z_create_catalog_fulltext
erp5_mysql_innodb/z_create_content_translation erp5_mysql_innodb/z_create_content_translation
erp5_mysql_innodb/z_create_fulltext erp5_mysql_innodb/z_create_fulltext
\ No newline at end of file
career_skill_title | category,catalog,catalog_full_text/title/z_related_career_skill
description | catalog_full_text/description/z_related_uid
parent_description | catalog_full_text/description/z_related_parent
parent_title | catalog_full_text/title/z_related_parent
stock_explanation_title | catalog_full_text/title/z_related_explanation_from_stock
stock_mirror_section_title | catalog_full_text/title/z_related_mirror_section_uid_from_stock
stock_node_title | catalog_full_text/title/z_related_node_uid_from_stock
title | catalog_full_text/title/z_related_uid
\ No newline at end of file
catalog_full_text
full_text full_text
\ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_ingestion_mysql_innodb_catalog erp5_ingestion_mysql_innodb_catalog
erp5_web erp5_web
erp5_crm erp5_crm
......
<key_list> <key_list>
<key>item_catalog_portal_type | catalog/portal_type/z_related_item_catalog</key> <key>item_catalog_portal_type | catalog/portal_type/z_related_item_catalog</key>
<key>item_catalog_reference | catalog/reference/z_related_item_catalog</key> <key>item_catalog_reference | catalog/reference/z_related_item_catalog</key>
<key>item_catalog_title | catalog/title/z_related_item_catalog</key> <key>item_catalog_title | catalog_full_text/title/z_related_item_catalog</key>
<key>item_catalog_validation_state | catalog/validation_state/z_related_item_catalog</key> <key>item_catalog_validation_state | catalog/validation_state/z_related_item_catalog</key>
</key_list> </key_list>
\ No newline at end of file
item_catalog_title | catalog/title/z_related_item_catalog item_catalog_title | catalog_full_text/title/z_related_item_catalog
item_catalog_portal_type | catalog/portal_type/z_related_item_catalog item_catalog_portal_type | catalog/portal_type/z_related_item_catalog
item_catalog_reference | catalog/reference/z_related_item_catalog item_catalog_reference | catalog/reference/z_related_item_catalog
item_catalog_validation_state | catalog/validation_state/z_related_item_catalog item_catalog_validation_state | catalog/validation_state/z_related_item_catalog
\ No newline at end of file
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
<tr>\n <tr>\n
<td>type</td>\n <td>type</td>\n
<td>search_text</td>\n <td>search_text</td>\n
<td>Pouet_NO_EXISTS</td>\n <td>Poueet</td>\n
</tr>\n </tr>\n
<tr>\n <tr>\n
<td>clickAndWait</td>\n <td>clickAndWait</td>\n
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
<tr>\n <tr>\n
<td>verifyValue</td>\n <td>verifyValue</td>\n
<td>search_text</td>\n <td>search_text</td>\n
<td>Pouet_NO_EXISTS</td>\n <td>Poueet</td>\n
</tr>\n </tr>\n
<tr>\n <tr>\n
<td>type</td>\n <td>type</td>\n
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_web erp5_web
erp5_ingestion erp5_ingestion
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_simulation erp5_simulation
erp5_configurator_standard_trade_template erp5_configurator_standard_trade_template
erp5_simulation_test erp5_simulation_test
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_ingestion_mysql_innodb_catalog erp5_ingestion_mysql_innodb_catalog
erp5_ingestion erp5_ingestion
\ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
############################################################################## ##############################################################################
from Products.ERP5.Document.ERP5ProjectUnitTestDistributor import ERP5ProjectUnitTestDistributor from Products.ERP5.Document.ERP5ProjectUnitTestDistributor import ERP5ProjectUnitTestDistributor
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
from zLOG import LOG,ERROR from zLOG import LOG,ERROR
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
...@@ -110,7 +111,10 @@ class ERP5ScalabilityDistributor(ERP5ProjectUnitTestDistributor): ...@@ -110,7 +111,10 @@ class ERP5ScalabilityDistributor(ERP5ProjectUnitTestDistributor):
tag = "%s_%s" % (self.getRelativeUrl(), title) tag = "%s_%s" % (self.getRelativeUrl(), title)
if portal.portal_activities.countMessageWithTag(tag) == 0: if portal.portal_activities.countMessageWithTag(tag) == 0:
test_node_list = test_node_module.searchFolder(portal_type="Test Node",title=title) test_node_list = test_node_module.searchFolder(
portal_type="Test Node",
title=SimpleQuery(comparison_operator='=', title=title),
)
assert len(test_node_list) in (0, 1), "Unable to find testnode : %s" % title assert len(test_node_list) in (0, 1), "Unable to find testnode : %s" % title
test_node = None test_node = None
if len(test_node_list) == 1: if len(test_node_list) == 1:
...@@ -129,7 +133,8 @@ class ERP5ScalabilityDistributor(ERP5ProjectUnitTestDistributor): ...@@ -129,7 +133,8 @@ class ERP5ScalabilityDistributor(ERP5ProjectUnitTestDistributor):
isMasterTestnode : return True if the node given in parameter exists and is a validated master isMasterTestnode : return True if the node given in parameter exists and is a validated master
""" """
test_node_module = self._getTestNodeModule() test_node_module = self._getTestNodeModule()
test_node_master = [ node for node in test_node_module.searchFolder(portal_type="Test Node", title=title, test_node_master = [ node for node in test_node_module.searchFolder(portal_type="Test Node",
title=SimpleQuery(comparison_operator='=', title=title),
specialise_uid=self.getUid(), specialise_uid=self.getUid(),
validation_state="validated") if node.getMaster() == 1 ] validation_state="validated") if node.getMaster() == 1 ]
if len(test_node_master) == 1: if len(test_node_master) == 1:
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
\ No newline at end of file
...@@ -41,7 +41,7 @@ class TestOxatisSynchronization(ERP5TypeTestCase): ...@@ -41,7 +41,7 @@ class TestOxatisSynchronization(ERP5TypeTestCase):
""" Return the list of BT required by unit tests. """ """ Return the list of BT required by unit tests. """
return ( return (
'erp5_core_proxy_field_legacy', 'erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_pdm', 'erp5_pdm',
'erp5_simulation', 'erp5_simulation',
......
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
<tr>\n <tr>\n
<td>assertValue</td>\n <td>assertValue</td>\n
<td>listbox_title</td>\n <td>listbox_title</td>\n
<td>"%1%" OR "%2%"</td>\n <td>("%1%" OR "%2%")</td>\n
</tr>\n </tr>\n
<tr>\n <tr>\n
<td>clickAndWait</td>\n <td>clickAndWait</td>\n
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
<tr>\n <tr>\n
<td>assertValue</td>\n <td>assertValue</td>\n
<td>listbox_title</td>\n <td>listbox_title</td>\n
<td>"%1%" OR "%2%"</td>\n <td>("%1%" OR "%2%")</td>\n
</tr>\n </tr>\n
\n \n
<tr>\n <tr>\n
...@@ -137,7 +137,7 @@ ...@@ -137,7 +137,7 @@
<tr>\n <tr>\n
<td>assertValue</td>\n <td>assertValue</td>\n
<td>listbox_title</td>\n <td>listbox_title</td>\n
<td>"%1%" OR "%2%"</td>\n <td>("%1%" OR "%2%")</td>\n
</tr>\n </tr>\n
\n \n
<tr>\n <tr>\n
...@@ -181,7 +181,7 @@ ...@@ -181,7 +181,7 @@
<tr>\n <tr>\n
<td>assertValue</td>\n <td>assertValue</td>\n
<td>listbox_title</td>\n <td>listbox_title</td>\n
<td>"%1%" OR "%2%"</td>\n <td>("%1%" OR "%2%")</td>\n
</tr>\n </tr>\n
\n \n
<tal:block tal:condition="python: context.TestTool_getSkinName()!=\'Mobile\'">\n <tal:block tal:condition="python: context.TestTool_getSkinName()!=\'Mobile\'">\n
...@@ -213,7 +213,7 @@ ...@@ -213,7 +213,7 @@
<tr>\n <tr>\n
<td>assertValue</td>\n <td>assertValue</td>\n
<td>listbox_title</td>\n <td>listbox_title</td>\n
<td>"%1%" OR "%2%"</td>\n <td>("%1%" OR "%2%")</td>\n
</tr>\n </tr>\n
\n \n
<tr>\n <tr>\n
......
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
<tr>\n <tr>\n
<td>type</td>\n <td>type</td>\n
<td>field_my_foo_category_title</td>\n <td>field_my_foo_category_title</td>\n
<td>b</td>\n <td>=b</td>\n
</tr>\n </tr>\n
<tr>\n <tr>\n
<td>type</td>\n <td>type</td>\n
......
...@@ -126,7 +126,7 @@ metal:use-macro="here/RelationFieldZuite_CommonTemplate/macros/init"\n ...@@ -126,7 +126,7 @@ metal:use-macro="here/RelationFieldZuite_CommonTemplate/macros/init"\n
<tr>\n <tr>\n
<td>type</td>\n <td>type</td>\n
<td>listbox_title</td>\n <td>listbox_title</td>\n
<td>a</td>\n <td>=a</td>\n
</tr>\n </tr>\n
<tr>\n <tr>\n
<td>clickAndWait</td>\n <td>clickAndWait</td>\n
...@@ -135,7 +135,7 @@ metal:use-macro="here/RelationFieldZuite_CommonTemplate/macros/init"\n ...@@ -135,7 +135,7 @@ metal:use-macro="here/RelationFieldZuite_CommonTemplate/macros/init"\n
</tr>\n </tr>\n
<tr>\n <tr>\n
<td>click</td>\n <td>click</td>\n
<td>//*[@class=\'listbox-data-line-0 DataA\']//input[@type="checkbox"]</td>\n <td>//tr//a[.="a"]/parent::td/preceding-sibling::td/input[@type="checkbox"]</td>\n
<td></td>\n <td></td>\n
</tr>\n </tr>\n
<tr>\n <tr>\n
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_upgrader_test erp5_upgrader_test
\ No newline at end of file
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>template_tool = context\n <value> <string>template_tool = context\n
\n \n
return template_tool.upgradeSite((\'erp5_full_text_myisam_catalog\',),\n return template_tool.upgradeSite((\'erp5_full_text_mroonga_catalog\',),\n
dry_run=(not fixit))\n dry_run=(not fixit))\n
</string> </value> </string> </value>
</item> </item>
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_jquery erp5_jquery
erp5_ingestion_mysql_innodb_catalog erp5_ingestion_mysql_innodb_catalog
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_web erp5_web
erp5_ingestion_mysql_innodb_catalog erp5_ingestion_mysql_innodb_catalog
......
erp5_ingestion_mysql_innodb_catalog erp5_ingestion_mysql_innodb_catalog
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
erp5_jquery erp5_jquery
erp5_web erp5_web
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_pdm erp5_pdm
erp5_data_set erp5_data_set
\ No newline at end of file
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
\ No newline at end of file \ No newline at end of file
...@@ -37,7 +37,7 @@ import string ...@@ -37,7 +37,7 @@ import string
from zLOG import LOG,INFO,ERROR from zLOG import LOG,INFO,ERROR
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ZSQLCatalog.SQLCatalog import Query from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
TEST_SUITE_MAX = 4 TEST_SUITE_MAX = 4
# Depending on the test suite priority, we will affect # Depending on the test suite priority, we will affect
# more or less cores # more or less cores
...@@ -210,7 +210,10 @@ class ERP5ProjectUnitTestDistributor(XMLObject): ...@@ -210,7 +210,10 @@ class ERP5ProjectUnitTestDistributor(XMLObject):
tag = "%s_%s" % (self.getRelativeUrl(), title) tag = "%s_%s" % (self.getRelativeUrl(), title)
if portal.portal_activities.countMessageWithTag(tag) == 0: if portal.portal_activities.countMessageWithTag(tag) == 0:
test_node_list = test_node_module.searchFolder(portal_type="Test Node",title=title) test_node_list = test_node_module.searchFolder(
portal_type="Test Node",
title=SimpleQuery(comparison_operator='=', title=title),
)
assert len(test_node_list) in (0, 1), "Unable to find testnode : %s" % title assert len(test_node_list) in (0, 1), "Unable to find testnode : %s" % title
test_node = None test_node = None
if len(test_node_list) == 1: if len(test_node_list) == 1:
...@@ -244,9 +247,11 @@ class ERP5ProjectUnitTestDistributor(XMLObject): ...@@ -244,9 +247,11 @@ class ERP5ProjectUnitTestDistributor(XMLObject):
from_date = now - 30 from_date = now - 30
def getTestSuiteSortKey(test_suite): def getTestSuiteSortKey(test_suite):
test_result = portal.portal_catalog(portal_type="Test Result", test_result = portal.portal_catalog(portal_type="Test Result",
title='="%s"' % test_suite.getTitle(), title=SimpleQuery(title=test_suite.getTitle()),
modification_date=Query(**{"creation_date": from_date, creation_date=SimpleQuery(
"range": "min"}), creation_date=from_date,
comparison_operator='>=',
),
sort_on=[("modification_date", "descending")], sort_on=[("modification_date", "descending")],
limit=1) limit=1)
if len(test_result): if len(test_result):
...@@ -304,7 +309,10 @@ class ERP5ProjectUnitTestDistributor(XMLObject): ...@@ -304,7 +309,10 @@ class ERP5ProjectUnitTestDistributor(XMLObject):
config_list = [] config_list = []
tag = "%s_%s" % (self.getRelativeUrl(), title) tag = "%s_%s" % (self.getRelativeUrl(), title)
if portal.portal_activities.countMessageWithTag(tag) == 0: if portal.portal_activities.countMessageWithTag(tag) == 0:
test_node_list = test_node_module.searchFolder(portal_type="Test Node",title=title) test_node_list = test_node_module.searchFolder(
portal_type="Test Node",
title=SimpleQuery(comparison_operator='=', title=title),
)
assert len(test_node_list) in (0, 1), "Unable to find testnode : %s" % title assert len(test_node_list) in (0, 1), "Unable to find testnode : %s" % title
test_node = None test_node = None
if len(test_node_list) == 1: if len(test_node_list) == 1:
...@@ -365,7 +373,9 @@ class ERP5ProjectUnitTestDistributor(XMLObject): ...@@ -365,7 +373,9 @@ class ERP5ProjectUnitTestDistributor(XMLObject):
def _getTestNodeFromTitle(self, node_title): def _getTestNodeFromTitle(self, node_title):
test_node_list = self._getTestNodeModule().searchFolder( test_node_list = self._getTestNodeModule().searchFolder(
portal_type='Test Node', title="='%s'" % node_title) portal_type="Test Node",
title=SimpleQuery(comparison_operator='=', title=node_title),
)
assert len(test_node_list) == 1, "We found %i test nodes for %s" % ( assert len(test_node_list) == 1, "We found %i test nodes for %s" % (
len(test_node_list), node_title) len(test_node_list), node_title)
test_node = test_node_list[0].getObject() test_node = test_node_list[0].getObject()
...@@ -373,7 +383,9 @@ class ERP5ProjectUnitTestDistributor(XMLObject): ...@@ -373,7 +383,9 @@ class ERP5ProjectUnitTestDistributor(XMLObject):
def _getTestSuiteFromTitle(self, suite_title): def _getTestSuiteFromTitle(self, suite_title):
test_suite_list = self._getTestSuiteModule().searchFolder( test_suite_list = self._getTestSuiteModule().searchFolder(
portal_type='Test Suite', title="='%s'" % suit_tile, validation_state="validated") portal_type='Test Suite',
title=SimpleQuery(comparison_operator='=', title=suite_title),
validation_state='validated')
assert len(test_suite_list) == 1, "We found %i test suite for %s" % ( assert len(test_suite_list) == 1, "We found %i test suite for %s" % (
len(test_suite_list), name) len(test_suite_list), name)
test_suite = test_suite_list[0].getObject() test_suite = test_suite_list[0].getObject()
......
...@@ -30,6 +30,7 @@ import random ...@@ -30,6 +30,7 @@ import random
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
from zLOG import LOG from zLOG import LOG
from xmlrpclib import Binary from xmlrpclib import Binary
...@@ -91,7 +92,7 @@ class TaskDistributionTool(BaseTool): ...@@ -91,7 +92,7 @@ class TaskDistributionTool(BaseTool):
def createTestResultLineList(test_result, test_name_list): def createTestResultLineList(test_result, test_name_list):
duration_list = [] duration_list = []
previous_test_result_list = portal.test_result_module.searchFolder( previous_test_result_list = portal.test_result_module.searchFolder(
title='="%s"' % test_result.getTitle(), title=SimpleQuery(comparison_operator='=', title=test_result.getTitle()),
sort_on=[('creation_date','descending')], sort_on=[('creation_date','descending')],
simulation_state=('stopped', 'public_stopped'), simulation_state=('stopped', 'public_stopped'),
limit=1) limit=1)
...@@ -126,7 +127,7 @@ class TaskDistributionTool(BaseTool): ...@@ -126,7 +127,7 @@ class TaskDistributionTool(BaseTool):
int_index, reference = revision int_index, reference = revision
result_list = portal.test_result_module.searchFolder( result_list = portal.test_result_module.searchFolder(
portal_type="Test Result", portal_type="Test Result",
title='="%s"' % test_title, title=SimpleQuery(comparison_operator='=', title=test_title),
sort_on=(("creation_date","descending"),), sort_on=(("creation_date","descending"),),
limit=1) limit=1)
if result_list: if result_list:
...@@ -162,7 +163,7 @@ class TaskDistributionTool(BaseTool): ...@@ -162,7 +163,7 @@ class TaskDistributionTool(BaseTool):
test_result._setIntIndex(int_index) test_result._setIntIndex(int_index)
if project_title is not None: if project_title is not None:
project_list = portal.portal_catalog(portal_type='Project', project_list = portal.portal_catalog(portal_type='Project',
title='="%s"' % project_title) title=SimpleQuery(comparison_operator='=', title=project_title))
if len(project_list) != 1: if len(project_list) != 1:
raise ValueError('found this list of project : %r for title %r' % \ raise ValueError('found this list of project : %r for title %r' % \
([x.path for x in project_list], project_title)) ([x.path for x in project_list], project_title))
......
erp5_full_text_myisam_catalog erp5_full_text_mroonga_catalog
erp5_base erp5_base
\ No newline at end of file
...@@ -56,7 +56,11 @@ portal = context.getPortalObject()\n ...@@ -56,7 +56,11 @@ portal = context.getPortalObject()\n
# This scriptable key supports content_translation if the table is present\n # This scriptable key supports content_translation if the table is present\n
catalog = portal.portal_catalog.getSQLCatalog()\n catalog = portal.portal_catalog.getSQLCatalog()\n
if \'content_translation\' in catalog.getProperty(\'sql_search_tables\'):\n if \'content_translation\' in catalog.getProperty(\'sql_search_tables\'):\n
return AndQuery(SimpleQuery(**{\'content_translation.translated_text\': value, \'comparison_operator\': \'match\'}),\n if [x for x in catalog.getProperty(\'sql_catalog_search_keys\', []) if \'Mroonga\' in x]:\n
return AndQuery(SimpleQuery(**{\'content_translation.translated_text\': value, \'comparison_operator\': \'mroonga_boolean\'}),\n
Query(**{\'content_translation.property_name\': \'title\'}))\n
else:\n
return AndQuery(SimpleQuery(**{\'content_translation.translated_text\': value, \'comparison_operator\': \'match_boolean\'}),\n
Query(**{\'content_translation.property_name\': \'title\'}))\n Query(**{\'content_translation.property_name\': \'title\'}))\n
\n \n
# Otherwise it simply use title\n # Otherwise it simply use title\n
......
...@@ -82,7 +82,7 @@ class TestCRM(BaseTestCRM): ...@@ -82,7 +82,7 @@ class TestCRM(BaseTestCRM):
return "CRM" return "CRM"
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ('erp5_full_text_myisam_catalog', return ('erp5_full_text_mroonga_catalog',
'erp5_core_proxy_field_legacy', 'erp5_core_proxy_field_legacy',
'erp5_base', 'erp5_base',
'erp5_ingestion', 'erp5_ingestion',
...@@ -571,7 +571,7 @@ class TestCRMMailIngestion(BaseTestCRM): ...@@ -571,7 +571,7 @@ class TestCRMMailIngestion(BaseTestCRM):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
# Mail Ingestion must work with CRM alone. # Mail Ingestion must work with CRM alone.
return ('erp5_core_proxy_field_legacy', return ('erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_ingestion', 'erp5_ingestion',
'erp5_ingestion_mysql_innodb_catalog', 'erp5_ingestion_mysql_innodb_catalog',
......
...@@ -48,7 +48,7 @@ class TestERP5Coordinate(ERP5TypeTestCase): ...@@ -48,7 +48,7 @@ class TestERP5Coordinate(ERP5TypeTestCase):
Return the list of required business templates. Return the list of required business templates.
""" """
return ('erp5_core_proxy_field_legacy', return ('erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base',) 'erp5_base',)
def beforeTearDown(self): def beforeTearDown(self):
......
...@@ -55,7 +55,7 @@ class TestERP5Credential(ERP5TypeTestCase): ...@@ -55,7 +55,7 @@ class TestERP5Credential(ERP5TypeTestCase):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ( return (
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_core_proxy_field_legacy', 'erp5_core_proxy_field_legacy',
'erp5_base', 'erp5_base',
'erp5_jquery', 'erp5_jquery',
......
...@@ -63,7 +63,7 @@ class TestEgov(ERP5TypeTestCase): ...@@ -63,7 +63,7 @@ class TestEgov(ERP5TypeTestCase):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
"""return list of business templates to be installed. """ """return list of business templates to be installed. """
bt_list = ['erp5_core_proxy_field_legacy', bt_list = ['erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_web', 'erp5_web',
'erp5_ingestion_mysql_innodb_catalog', 'erp5_ingestion_mysql_innodb_catalog',
......
...@@ -56,7 +56,7 @@ class TestZeleniumRunMyDocSample(ERP5TypeFunctionalTestCase): ...@@ -56,7 +56,7 @@ class TestZeleniumRunMyDocSample(ERP5TypeFunctionalTestCase):
""" """
Return the list of business templates. Return the list of business templates.
""" """
return ('erp5_core_proxy_field_legacy', 'erp5_full_text_myisam_catalog', return ('erp5_core_proxy_field_legacy', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_ui_test_core','erp5_web', 'erp5_ingestion', 'erp5_base', 'erp5_ui_test_core','erp5_web', 'erp5_ingestion',
'erp5_accounting', 'erp5_accounting',
'erp5_jquery', 'erp5_dms', 'erp5_jquery_ui', 'erp5_web', 'erp5_jquery', 'erp5_dms', 'erp5_jquery_ui', 'erp5_web',
......
...@@ -43,21 +43,42 @@ class TestI18NSearch(ERP5TypeTestCase): ...@@ -43,21 +43,42 @@ class TestI18NSearch(ERP5TypeTestCase):
portal_type='Person', portal_type='Person',
first_name='Gabriel', first_name='Gabriel',
last_name='Fauré', last_name='Fauré',
description='Quick brown fox jumps over the lazy dog.',
) )
person2 = person_module.newContent( person2 = person_module.newContent(
portal_type='Person', portal_type='Person',
first_name='武者小路', first_name='武者小路',
last_name='実篤' last_name='実篤',
description='Slow white fox jumps over the diligent dog.',
) )
self.tic() self.tic()
# check if 'é' == 'e' collation works # check if 'é' == 'e' collation works
result = person_module.searchFolder(SearchableText='Faure') result = person_module.searchFolder(SearchableText='Faure')
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(result[0].getPath(), person1.getPath()) self.assertEqual(result[0].getPath(), person1.getPath())
result = person_module.searchFolder(title='Faure')
self.assertEqual(len(result), 1)
self.assertEqual(result[0].getPath(), person1.getPath())
# check if a partial string of CJK string matches # check if a partial string of CJK string matches
result = person_module.searchFolder(SearchableText='武者') result = person_module.searchFolder(SearchableText='武者')
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(result[0].getPath(), person2.getPath()) self.assertEqual(result[0].getPath(), person2.getPath())
result = person_module.searchFolder(title='武者')
self.assertEqual(len(result), 1)
self.assertEqual(result[0].getPath(), person2.getPath())
# check boolean language mode search
result = person_module.searchFolder(SearchableText='+quick +fox +dog')
self.assertEqual(len(result), 1)
self.assertEqual(result[0].getPath(), person1.getPath())
result = person_module.searchFolder(description='+quick +fox +dog')
self.assertEqual(len(result), 1)
self.assertEqual(result[0].getPath(), person1.getPath())
# check fulltext search for automatically generated related keys.
self.assertTrue('MATCH' in self.portal.portal_catalog(destination_title='Faure', src__=1))
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
......
...@@ -50,7 +50,7 @@ class TestKMMixIn(TestDocumentMixin): ...@@ -50,7 +50,7 @@ class TestKMMixIn(TestDocumentMixin):
manager_password = '' manager_password = ''
website_id = 'km_test' website_id = 'km_test'
business_template_list = ['erp5_core_proxy_field_legacy', business_template_list = ['erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog','erp5_base', 'erp5_full_text_mroonga_catalog','erp5_base',
'erp5_jquery', 'erp5_jquery_ui', 'erp5_knowledge_pad', 'erp5_jquery', 'erp5_jquery_ui', 'erp5_knowledge_pad',
'erp5_ingestion_mysql_innodb_catalog', 'erp5_ingestion', 'erp5_ingestion_mysql_innodb_catalog', 'erp5_ingestion',
'erp5_web', 'erp5_dms', 'erp5_web', 'erp5_dms',
......
...@@ -68,7 +68,7 @@ class TestSpellChecking(ERP5TypeTestCase): ...@@ -68,7 +68,7 @@ class TestSpellChecking(ERP5TypeTestCase):
return "Spell Checking Test" return "Spell Checking Test"
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ('erp5_full_text_myisam_catalog', return ('erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_simulation', 'erp5_simulation',
'erp5_accounting', 'erp5_accounting',
......
...@@ -52,7 +52,7 @@ class TestTemplateTool(ERP5TypeTestCase): ...@@ -52,7 +52,7 @@ class TestTemplateTool(ERP5TypeTestCase):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ('erp5_core_proxy_field_legacy', return ('erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_stock_cache', 'erp5_stock_cache',
'erp5_csv_style') 'erp5_csv_style')
...@@ -650,7 +650,7 @@ class TestTemplateTool(ERP5TypeTestCase): ...@@ -650,7 +650,7 @@ class TestTemplateTool(ERP5TypeTestCase):
'erp5_core_proxy_field_legacy': first_group, 'erp5_core_proxy_field_legacy': first_group,
'erp5_mysql_innodb_catalog': first_group, 'erp5_mysql_innodb_catalog': first_group,
'erp5_core': first_group, 'erp5_core': first_group,
'erp5_full_text_myisam_catalog': first_group, 'erp5_full_text_mroonga_catalog': first_group,
'erp5_xhtml_style': first_group, 'erp5_xhtml_style': first_group,
'erp5_ingestion_mysql_innodb_catalog': second_group, 'erp5_ingestion_mysql_innodb_catalog': second_group,
'erp5_base': second_group, 'erp5_base': second_group,
......
...@@ -881,9 +881,10 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): ...@@ -881,9 +881,10 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject):
by looking at the category tree. by looking at the category tree.
For exemple it will generate: For exemple it will generate:
destination_title | category,catalog/title/z_related_destination destination_reference | category,catalog/reference/z_related_destination
default_destination_title | category,catalog/title/z_related_destination default_destination_reference | category,catalog/reference/z_related_destination
strict_destination_title | category,catalog/title/z_related_strict_destination strict_destination_reference | category,catalog/reference/z_related_strict_destination
destination_title | category,catalog_full_text/title/z_related_destination
strict_ related keys only returns documents which are strictly member of strict_ related keys only returns documents which are strictly member of
the category. the category.
...@@ -917,9 +918,18 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): ...@@ -917,9 +918,18 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject):
if related: if related:
end_key = end_key[len(related_string):] end_key = end_key[len(related_string):]
# XXX: joining with non-catalog tables is not trivial and requires # XXX: joining with non-catalog tables is not trivial and requires
# ZSQLCatalog's ColumnMapper cooperation, so only allow catalog # ZSQLCatalog's ColumnMapper cooperation, so only allow columns in
# columns. # catalog or catalog_full_text tables.
if 'catalog' in column_map.get(end_key, ()): if end_key != 'uid' and 'catalog_full_text' in column_map.get(end_key, ()):
related_key_list.append(
prefix + key + ' | category,catalog_full_text/' +
end_key +
'/z_related_' +
('strict_' if strict else '') +
expected_base_cat_id +
('_related' if related else '')
)
elif 'catalog' in column_map.get(end_key, ()):
is_uid = end_key == 'uid' is_uid = end_key == 'uid'
if is_uid: if is_uid:
end_key = 'uid' if related else 'category_uid' end_key = 'uid' if related else 'category_uid'
......
...@@ -49,7 +49,7 @@ class TestArchive(InventoryAPITestCase): ...@@ -49,7 +49,7 @@ class TestArchive(InventoryAPITestCase):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return InventoryAPITestCase.getBusinessTemplateList(self) + ( return InventoryAPITestCase.getBusinessTemplateList(self) + (
'erp5_archive', 'erp5_archive',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
) )
# Different variables used for this test # Different variables used for this test
......
...@@ -44,7 +44,7 @@ from Products.ERP5Type.tests.utils import createZODBPythonScript, todo_erp5, \ ...@@ -44,7 +44,7 @@ from Products.ERP5Type.tests.utils import createZODBPythonScript, todo_erp5, \
from Products.ZSQLCatalog.ZSQLCatalog import HOT_REINDEXING_FINISHED_STATE,\ from Products.ZSQLCatalog.ZSQLCatalog import HOT_REINDEXING_FINISHED_STATE,\
HOT_REINDEXING_RECORDING_STATE, HOT_REINDEXING_DOUBLE_INDEXING_STATE HOT_REINDEXING_RECORDING_STATE, HOT_REINDEXING_DOUBLE_INDEXING_STATE
from Products.CMFActivity.Errors import ActivityFlushError from Products.CMFActivity.Errors import ActivityFlushError
from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery, SimpleQuery
from OFS.ObjectManager import ObjectManager from OFS.ObjectManager import ObjectManager
...@@ -92,7 +92,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -92,7 +92,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
return "ERP5Catalog" return "ERP5Catalog"
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ('erp5_full_text_myisam_catalog', 'erp5_base',) return ('erp5_full_text_mroonga_catalog', 'erp5_base',)
# Different variables used for this test # Different variables used for this test
username = 'seb' username = 'seb'
...@@ -124,6 +124,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -124,6 +124,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
self.getCategoryTool().region, self.getCategoryTool().region,
self.getCategoryTool().group ]: self.getCategoryTool().group ]:
module.manage_delObjects(list(module.objectIds())) module.manage_delObjects(list(module.objectIds()))
module.reindexObject()
# Remove copied sql_connector and catalog # Remove copied sql_connector and catalog
if self.new_erp5_sql_connection in self.portal.objectIds(): if self.new_erp5_sql_connection in self.portal.objectIds():
self.portal.manage_delObjects([self.new_erp5_sql_connection]) self.portal.manage_delObjects([self.new_erp5_sql_connection])
...@@ -189,7 +190,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -189,7 +190,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
person = person_module.newContent(id='1',portal_type='Person') person = person_module.newContent(id='1',portal_type='Person')
path_list = [person.getRelativeUrl()] path_list = [person.getRelativeUrl()]
self.checkRelativeUrlNotInSQLPathList(path_list) self.checkRelativeUrlNotInSQLPathList(path_list)
person.immediateReindexObject() self.tic()
self.checkRelativeUrlInSQLPathList(path_list) self.checkRelativeUrlInSQLPathList(path_list)
person_module.manage_delObjects('1') person_module.manage_delObjects('1')
self.tic() self.tic()
...@@ -197,10 +198,10 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -197,10 +198,10 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
# Now we will ask to immediatly reindex # Now we will ask to immediatly reindex
person = person_module.newContent(id='2', person = person_module.newContent(id='2',
portal_type='Person',) portal_type='Person',)
person.immediateReindexObject() self.tic()
path_list = [person.getRelativeUrl()] path_list = [person.getRelativeUrl()]
self.checkRelativeUrlInSQLPathList(path_list) self.checkRelativeUrlInSQLPathList(path_list)
person.immediateReindexObject() self.tic()
self.checkRelativeUrlInSQLPathList(path_list) self.checkRelativeUrlInSQLPathList(path_list)
person_module.manage_delObjects('2') person_module.manage_delObjects('2')
self.tic() self.tic()
...@@ -209,7 +210,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -209,7 +210,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
person = person_module.newContent(id='3',portal_type='Person') person = person_module.newContent(id='3',portal_type='Person')
path_list = [person.getRelativeUrl()] path_list = [person.getRelativeUrl()]
self.checkRelativeUrlNotInSQLPathList(path_list) self.checkRelativeUrlNotInSQLPathList(path_list)
person.immediateReindexObject() self.tic()
self.checkRelativeUrlInSQLPathList(path_list) self.checkRelativeUrlInSQLPathList(path_list)
person_module.deleteContent('3') person_module.deleteContent('3')
# Now delete things is made with activities # Now delete things is made with activities
...@@ -223,10 +224,10 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -223,10 +224,10 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertEqual([],folder_object_list) self.assertEqual([],folder_object_list)
person = person_module.newContent(id='4',portal_type='Person',) person = person_module.newContent(id='4',portal_type='Person',)
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertEqual(['4'],folder_object_list) self.assertEqual(['4'],folder_object_list)
person.immediateReindexObject() self.tic()
person_module.manage_delObjects('4') person_module.manage_delObjects('4')
self.tic() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
...@@ -240,7 +241,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -240,7 +241,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
self.assertEqual([],folder_object_list) self.assertEqual([],folder_object_list)
person = person_module.newContent(id='4',portal_type='Person') person = person_module.newContent(id='4',portal_type='Person')
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertEqual(['4'],folder_object_list) self.assertEqual(['4'],folder_object_list)
...@@ -274,7 +275,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -274,7 +275,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
portal_catalog.manage_catalogClear() portal_catalog.manage_catalogClear()
person = person_module.newContent(id='4',portal_type='Person') person = person_module.newContent(id='4',portal_type='Person')
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertEqual(['4'],folder_object_list) self.assertEqual(['4'],folder_object_list)
...@@ -298,7 +299,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -298,7 +299,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
portal_catalog.manage_catalogClear() portal_catalog.manage_catalogClear()
person = person_module.newContent(id='4',portal_type='Person') person = person_module.newContent(id='4',portal_type='Person')
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertEqual(['4'],folder_object_list) self.assertEqual(['4'],folder_object_list)
...@@ -310,11 +311,11 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -310,11 +311,11 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
portal_catalog.manage_catalogClear() portal_catalog.manage_catalogClear()
person = person_module.newContent(id='a',portal_type='Person',title='a',description='z') person = person_module.newContent(id='a',portal_type='Person',title='a',description='z')
person.immediateReindexObject() self.tic()
person = person_module.newContent(id='b',portal_type='Person',title='a',description='y') person = person_module.newContent(id='b',portal_type='Person',title='a',description='y')
person.immediateReindexObject() self.tic()
person = person_module.newContent(id='c',portal_type='Person',title='a',description='x') person = person_module.newContent(id='c',portal_type='Person',title='a',description='x')
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() folder_object_list = [x.getObject().getId()
for x in person_module.searchFolder(sort_on=[('id','ascending')])] for x in person_module.searchFolder(sort_on=[('id','ascending')])]
self.assertEqual(['a','b','c'],folder_object_list) self.assertEqual(['a','b','c'],folder_object_list)
...@@ -335,11 +336,11 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -335,11 +336,11 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
portal_catalog.manage_catalogClear() portal_catalog.manage_catalogClear()
person = person_module.newContent(id='a',portal_type='Person',title='1') person = person_module.newContent(id='a',portal_type='Person',title='1')
person.immediateReindexObject() self.tic()
person = person_module.newContent(id='b',portal_type='Person',title='2') person = person_module.newContent(id='b',portal_type='Person',title='2')
person.immediateReindexObject() self.tic()
person = person_module.newContent(id='c',portal_type='Person',title='12') person = person_module.newContent(id='c',portal_type='Person',title='12')
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getTitle() for x in person_module.searchFolder(sort_on=[('title','ascending')])] folder_object_list = [x.getObject().getTitle() for x in person_module.searchFolder(sort_on=[('title','ascending')])]
self.assertEqual(['1','12','2'],folder_object_list) self.assertEqual(['1','12','2'],folder_object_list)
folder_object_list = [x.getObject().getTitle() for x in person_module.searchFolder(sort_on=[('title','ascending','int')])] folder_object_list = [x.getObject().getTitle() for x in person_module.searchFolder(sort_on=[('title','ascending','int')])]
...@@ -654,7 +655,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -654,7 +655,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
title = 'Sébastien' title = 'Sébastien'
person = person_module.newContent(id='5',portal_type='Person',title=title) person = person_module.newContent(id='5',portal_type='Person',title=title)
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertEqual(['5'],folder_object_list) self.assertEqual(['5'],folder_object_list)
folder_object_list = [x.getObject().getId() for x in folder_object_list = [x.getObject().getId() for x in
...@@ -666,7 +667,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -666,7 +667,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
title = 'Sébastien' title = 'Sébastien'
person = person_module.newContent(id='5',portal_type='Person', title=title) person = person_module.newContent(id='5',portal_type='Person', title=title)
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in folder_object_list = [x.getObject().getId() for x in
person_module.searchFolder(title=title)] person_module.searchFolder(title=title)]
self.assertEqual(['5'],folder_object_list) self.assertEqual(['5'],folder_object_list)
...@@ -690,10 +691,10 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -690,10 +691,10 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
portal_category.group.objectIds()]) portal_category.group.objectIds()])
group_nexedi_category = portal_category.group\ group_nexedi_category = portal_category.group\
.newContent( id = 'nexedi', title='Nexedi', .newContent( id = 'nexedi', title='Nexedi',
description='a') reference='a')
group_nexedi_category2 = portal_category.group\ group_nexedi_category2 = portal_category.group\
.newContent( id = 'storever', title='Storever', .newContent( id = 'storever', title='Storever',
description='b') reference='b')
module = portal.getDefaultModule('Organisation') module = portal.getDefaultModule('Organisation')
organisation = module.newContent(portal_type='Organisation',) organisation = module.newContent(portal_type='Organisation',)
organisation.setGroup('nexedi') organisation.setGroup('nexedi')
...@@ -716,17 +717,17 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -716,17 +717,17 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
organisation_list = [x.getObject() for x in organisation_list = [x.getObject() for x in
module.searchFolder(group_id='storever')] module.searchFolder(group_id='storever')]
self.assertEqual(organisation_list,[organisation2]) self.assertEqual(organisation_list,[organisation2])
# Try to get the organisation with the group description 'a' # Try to get the organisation with the group reference 'a'
organisation_list = [x.getObject() for x in organisation_list = [x.getObject() for x in
module.searchFolder(group_description='a')] module.searchFolder(group_reference='a')]
self.assertEqual(organisation_list,[organisation]) self.assertEqual(organisation_list,[organisation])
# Try to get the organisation with the group description 'c' # Try to get the organisation with the group reference 'c'
organisation_list = [x.getObject() for x in organisation_list = [x.getObject() for x in
module.searchFolder(group_description='c')] module.searchFolder(group_reference='c')]
self.assertEqual(organisation_list,[]) self.assertEqual(organisation_list,[])
# Try to get the organisation with the default group description 'c' # Try to get the organisation with the default group reference 'c'
organisation_list = [x.getObject() for x in organisation_list = [x.getObject() for x in
module.searchFolder(default_group_description='c')] module.searchFolder(default_group_reference='c')]
self.assertEqual(organisation_list,[]) self.assertEqual(organisation_list,[])
# Try to get the organisation with group relative_url # Try to get the organisation with group relative_url
group_relative_url = group_nexedi_category.getRelativeUrl() group_relative_url = group_nexedi_category.getRelativeUrl()
...@@ -752,10 +753,10 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -752,10 +753,10 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
portal_category.group.objectIds()]) portal_category.group.objectIds()])
group_nexedi_category = portal_category.group\ group_nexedi_category = portal_category.group\
.newContent( id = 'nexedi', title='Nexedi', .newContent( id = 'nexedi', title='Nexedi',
description='a') reference='a')
sub_group_nexedi = group_nexedi_category\ sub_group_nexedi = group_nexedi_category\
.newContent( id = 'erp5', title='ERP5', .newContent( id = 'erp5', title='ERP5',
description='b') reference='b')
module = portal.getDefaultModule('Organisation') module = portal.getDefaultModule('Organisation')
organisation = module.newContent(portal_type='Organisation',) organisation = module.newContent(portal_type='Organisation',)
organisation.setGroup('nexedi/erp5') organisation.setGroup('nexedi/erp5')
...@@ -771,13 +772,13 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -771,13 +772,13 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
organisation_list = [x.getObject() for x in organisation_list = [x.getObject() for x in
module.searchFolder(strict_group_title='ERP5')] module.searchFolder(strict_group_title='ERP5')]
self.assertEqual(organisation_list,[organisation]) self.assertEqual(organisation_list,[organisation])
# Try to get the organisation with the group description a # Try to get the organisation with the group reference a
organisation_list = [x.getObject() for x in organisation_list = [x.getObject() for x in
module.searchFolder(strict_group_description='a')] module.searchFolder(strict_group_reference='a')]
self.assertEqual(organisation_list,[]) self.assertEqual(organisation_list,[])
# Try to get the organisation with the group description b # Try to get the organisation with the group reference b
organisation_list = [x.getObject() for x in organisation_list = [x.getObject() for x in
module.searchFolder(strict_group_description='b')] module.searchFolder(strict_group_reference='b')]
self.assertEqual(organisation_list,[organisation]) self.assertEqual(organisation_list,[organisation])
def test_22_SearchingWithUnicode(self): def test_22_SearchingWithUnicode(self):
...@@ -793,7 +794,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -793,7 +794,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
# Now we will ask to immediatly reindex # Now we will ask to immediatly reindex
person = person_module.newContent(id='2', person = person_module.newContent(id='2',
portal_type='Person',) portal_type='Person',)
person.immediateReindexObject() self.tic()
path_list = [person.getRelativeUrl()] path_list = [person.getRelativeUrl()]
self.checkRelativeUrlInSQLPathList(path_list) self.checkRelativeUrlInSQLPathList(path_list)
# We will delete the connector # We will delete the connector
...@@ -855,19 +856,19 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -855,19 +856,19 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
"""Sort-on parameter and related key. (Assumes that region_title is a \ """Sort-on parameter and related key. (Assumes that region_title is a \
valid related key)""" valid related key)"""
self.assertTrue( self.assertTrue(
self.getCatalogTool().buildSQLQuery(region_title='foo', self.getCatalogTool().buildSQLQuery(region_reference='foo',
sort_on=(('region_title', 'ascending'),))['order_by_expression'].endswith('.`title` ASC')) sort_on=(('region_reference', 'ascending'),))['order_by_expression'].endswith('.`reference` ASC'))
self.assertTrue( self.assertTrue(
self.getCatalogTool().buildSQLQuery(region_title='foo', self.getCatalogTool().buildSQLQuery(region_reference='foo',
sort_on=(('region_title', 'descending'),))['order_by_expression'].endswith('.`title` DESC')) sort_on=(('region_reference', 'descending'),))['order_by_expression'].endswith('.`reference` DESC'))
self.assertTrue( self.assertTrue(
self.getCatalogTool().buildSQLQuery( self.getCatalogTool().buildSQLQuery(
sort_on=(('region_title', 'ascending'),))['order_by_expression'].endswith('.`title` ASC'), sort_on=(('region_reference', 'ascending'),))['order_by_expression'].endswith('.`reference` ASC'),
'sort_on parameter must be taken into account even if related key ' 'sort_on parameter must be taken into account even if related key '
'is not a parameter of the current query') 'is not a parameter of the current query')
self.assertTrue( self.assertTrue(
self.getCatalogTool().buildSQLQuery( self.getCatalogTool().buildSQLQuery(
sort_on=(('region_title', 'descending'),))['order_by_expression'].endswith('.`title` DESC'), sort_on=(('region_reference', 'descending'),))['order_by_expression'].endswith('.`reference` DESC'),
'sort_on parameter must be taken into account even if related key ' 'sort_on parameter must be taken into account even if related key '
'is not a parameter of the current query') 'is not a parameter of the current query')
...@@ -957,8 +958,8 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -957,8 +958,8 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
self.assertEqual([organisation.getPath()], self.assertEqual([organisation.getPath()],
[x.path for x in self.getCatalogTool()( [x.path for x in self.getCatalogTool()(
title={'query': (organisation_title, 'something else'), **{'catalog.title':{'query': (organisation_title, 'something else'),
'operator': 'or'})]) 'operator': 'or'}})])
def test_33_SimpleQueryDictWithAndOperator(self): def test_33_SimpleQueryDictWithAndOperator(self):
"""use a dict as a keyword parameter, with AND operator. """use a dict as a keyword parameter, with AND operator.
...@@ -1149,20 +1150,19 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -1149,20 +1150,19 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
SearchableText='title')[0][0]) SearchableText='title')[0][0])
# 'different' is found in more than 50% of records # 'different' is found in more than 50% of records
# MySQL ignores such a word, but Tritonn does not ignore. # MySQL ignores such a word, but Mroonga does not ignore.
try: if 'ENGINE=Mroonga' in self.portal.erp5_sql_connection.manage_test(
self.portal.erp5_sql_connection.manage_test('SHOW SENNA STATUS') 'SHOW CREATE TABLE full_text')[0][1]:
except ProgrammingError: # Mroonga
self.assertEqual(10, self.getCatalogTool().countResults(
portal_type='Organisation', SearchableText='different')[0][0])
else:
# MySQL # MySQL
self.assertEqual([], self.assertEqual([],
[x.getObject for x in self.getCatalogTool()( [x.getObject for x in self.getCatalogTool()(
portal_type='Organisation', SearchableText='different')]) portal_type='Organisation', SearchableText='different')])
self.assertEqual(0, self.getCatalogTool().countResults( self.assertEqual(0, self.getCatalogTool().countResults(
portal_type='Organisation', SearchableText='different')[0][0]) portal_type='Organisation', SearchableText='different')[0][0])
else:
# Tritonn
self.assertEqual(10, self.getCatalogTool().countResults(
portal_type='Organisation', SearchableText='different')[0][0])
def test_43_ManagePasteObject(self): def test_43_ManagePasteObject(self):
portal_catalog = self.getCatalogTool() portal_catalog = self.getCatalogTool()
...@@ -1221,11 +1221,11 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -1221,11 +1221,11 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
# Recursive Complex Query # Recursive Complex Query
# (title='abc' and description='abc') OR # (title='abc' and description='abc') OR
# title='foo' and description='bar' # title='foo' and description='bar'
catalog_kw = {'query':ComplexQuery(ComplexQuery(Query(title='abc'), catalog_kw = {'query':ComplexQuery(ComplexQuery(SimpleQuery(title='abc'),
Query(description='abc'), SimpleQuery(description='abc'),
operator='AND'), operator='AND'),
ComplexQuery(Query(title='foo'), ComplexQuery(SimpleQuery(title='foo'),
Query(description='bar'), SimpleQuery(description='bar'),
operator='AND'), operator='AND'),
operator='OR')} operator='OR')}
self.failIfDifferentSet([org_a.getPath(), org_f.getPath()], self.failIfDifferentSet([org_a.getPath(), org_f.getPath()],
...@@ -1578,11 +1578,11 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -1578,11 +1578,11 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
catalog = portal_catalog.objectValues()[0] catalog = portal_catalog.objectValues()[0]
person = person_module.newContent(id='a',portal_type='Person',title='a',description='z') person = person_module.newContent(id='a',portal_type='Person',title='a',description='z')
person.immediateReindexObject() self.tic()
person = person_module.newContent(id='b',portal_type='Person',title='a',description='y') person = person_module.newContent(id='b',portal_type='Person',title='a',description='y')
person.immediateReindexObject() self.tic()
person = person_module.newContent(id='c',portal_type='Person',title='a',description='x') person = person_module.newContent(id='c',portal_type='Person',title='a',description='x')
person.immediateReindexObject() self.tic()
index_columns = getattr(catalog, 'sql_catalog_index_on_order_keys', None) index_columns = getattr(catalog, 'sql_catalog_index_on_order_keys', None)
self.assertNotEqual(index_columns, None) self.assertNotEqual(index_columns, None)
self.assertEqual(len(index_columns), 0) self.assertEqual(len(index_columns), 0)
...@@ -2105,7 +2105,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor): ...@@ -2105,7 +2105,7 @@ class TestERP5Catalog(ERP5TypeTestCase, LogInterceptor):
# description is not a keyword by default. (This might change in the # description is not a keyword by default. (This might change in the
# future, in this case, this test have to be updated) # future, in this case, this test have to be updated)
self.assertSameSet([doc], [x.getObject() for x in self.assertSameSet([doc], [x.getObject() for x in
ctool(portal_type='Organisation', description='Foo')]) ctool(portal_type='Organisation', description='=Foo')])
self.assertEqual({doc, other_doc}, {x.getObject() for x in self.assertEqual({doc, other_doc}, {x.getObject() for x in
ctool(portal_type='Organisation', description=dict(query='Foo', ctool(portal_type='Organisation', description=dict(query='Foo',
key='Keyword'))}) key='Keyword'))})
...@@ -3546,19 +3546,19 @@ VALUES ...@@ -3546,19 +3546,19 @@ VALUES
portal_category.group.objectIds()]) portal_category.group.objectIds()])
group_nexedi_category = portal_category.group\ group_nexedi_category = portal_category.group\
.newContent( id = 'nexedi', title='Nexedi', .newContent( id = 'nexedi', title='Nexedi',
description='a') reference='a')
group_nexedi_category2 = portal_category.group\ group_nexedi_category2 = portal_category.group\
.newContent( id = 'storever', title='Storever', .newContent( id = 'storever', title='Storever',
description='b') reference='b')
module = portal.getDefaultModule('Organisation') module = portal.getDefaultModule('Organisation')
organisation = module.newContent(portal_type='Organisation', organisation = module.newContent(portal_type='Organisation',
title='Nexedi Orga', title='Nexedi Orga',
description='c') reference='c')
organisation.setGroup('nexedi') organisation.setGroup('nexedi')
self.assertEqual(organisation.getGroupValue(), group_nexedi_category) self.assertEqual(organisation.getGroupValue(), group_nexedi_category)
organisation2 = module.newContent(portal_type='Organisation', organisation2 = module.newContent(portal_type='Organisation',
title='Storever Orga', title='Storever Orga',
description='d') reference='d')
organisation2.setGroup('storever') organisation2.setGroup('storever')
organisation2.setTitle('Organisation 2') organisation2.setTitle('Organisation 2')
self.assertEqual(organisation2.getGroupValue(), group_nexedi_category2) self.assertEqual(organisation2.getGroupValue(), group_nexedi_category2)
...@@ -3580,19 +3580,19 @@ VALUES ...@@ -3580,19 +3580,19 @@ VALUES
category_list = [x.getObject() for x in category_list = [x.getObject() for x in
base_category.searchFolder(group_related_id='storever')] base_category.searchFolder(group_related_id='storever')]
self.assertEqual(category_list,[group_nexedi_category2]) self.assertEqual(category_list,[group_nexedi_category2])
# Try to get the category with the group related organisation description 'd' # Try to get the category with the group related organisation reference 'd'
category_list = [x.getObject() for x in category_list = [x.getObject() for x in
base_category.searchFolder(group_related_description='d')] base_category.searchFolder(group_related_reference='d')]
self.assertEqual(category_list,[group_nexedi_category2]) self.assertEqual(category_list,[group_nexedi_category2])
# Try to get the category with the group related organisation description # Try to get the category with the group related organisation reference
# 'e' # 'e'
category_list = [x.getObject() for x in category_list = [x.getObject() for x in
base_category.searchFolder(group_related_description='e')] base_category.searchFolder(group_related_reference='e')]
self.assertEqual(category_list,[]) self.assertEqual(category_list,[])
# Try to get the category with the default group related organisation description # Try to get the category with the default group related organisation reference
# 'e' # 'e'
category_list = [x.getObject() for x in category_list = [x.getObject() for x in
base_category.searchFolder(default_group_related_description='e')] base_category.searchFolder(default_group_related_reference='e')]
self.assertEqual(category_list,[]) self.assertEqual(category_list,[])
# Try to get the category with the group related organisation relative_url # Try to get the category with the group related organisation relative_url
organisation_relative_url = organisation.getRelativeUrl() organisation_relative_url = organisation.getRelativeUrl()
...@@ -3618,19 +3618,19 @@ VALUES ...@@ -3618,19 +3618,19 @@ VALUES
portal_category.group.objectIds()]) portal_category.group.objectIds()])
group_nexedi_category = portal_category.group\ group_nexedi_category = portal_category.group\
.newContent( id = 'nexedi', title='Nexedi', .newContent( id = 'nexedi', title='Nexedi',
description='a') reference='a')
sub_group_nexedi = group_nexedi_category\ sub_group_nexedi = group_nexedi_category\
.newContent( id = 'erp5', title='ERP5', .newContent( id = 'erp5', title='ERP5',
description='b') reference='b')
module = portal.getDefaultModule('Organisation') module = portal.getDefaultModule('Organisation')
organisation = module.newContent(portal_type='Organisation', organisation = module.newContent(portal_type='Organisation',
title='ERP5 Orga', title='ERP5 Orga',
description='c') reference='c')
organisation.setGroup('nexedi/erp5') organisation.setGroup('nexedi/erp5')
self.assertEqual(organisation.getGroupValue(), sub_group_nexedi) self.assertEqual(organisation.getGroupValue(), sub_group_nexedi)
organisation2 = module.newContent(portal_type='Organisation', organisation2 = module.newContent(portal_type='Organisation',
title='Nexedi Orga', title='Nexedi Orga',
description='d') reference='d')
organisation2.setGroup('nexedi') organisation2.setGroup('nexedi')
# Flush message queue # Flush message queue
self.tic() self.tic()
...@@ -3649,15 +3649,15 @@ VALUES ...@@ -3649,15 +3649,15 @@ VALUES
base_category.portal_catalog( base_category.portal_catalog(
strict_group_related_title='ERP5 Orga')] strict_group_related_title='ERP5 Orga')]
self.assertEqual(category_list,[sub_group_nexedi]) self.assertEqual(category_list,[sub_group_nexedi])
# Try to get the category with the group related organisation description d # Try to get the category with the group related organisation reference d
category_list = [x.getObject() for x in category_list = [x.getObject() for x in
base_category.portal_catalog( base_category.portal_catalog(
strict_group_related_description='d')] strict_group_related_reference='d')]
self.assertEqual(category_list,[group_nexedi_category]) self.assertEqual(category_list,[group_nexedi_category])
# Try to get the category with the group related organisation description c # Try to get the category with the group related organisation reference c
category_list = [x.getObject() for x in category_list = [x.getObject() for x in
base_category.portal_catalog( base_category.portal_catalog(
strict_group_related_description='c')] strict_group_related_reference='c')]
self.assertEqual(category_list,[sub_group_nexedi]) self.assertEqual(category_list,[sub_group_nexedi])
def test_EscapingLoginInSescurityQuery(self): def test_EscapingLoginInSescurityQuery(self):
...@@ -3768,7 +3768,7 @@ VALUES ...@@ -3768,7 +3768,7 @@ VALUES
title='foo (bar)' title='foo (bar)'
person = person_module.newContent(portal_type='Person',title=title) person = person_module.newContent(portal_type='Person',title=title)
person_id = person.getId() person_id = person.getId()
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertTrue(person_id in folder_object_list) self.assertTrue(person_id in folder_object_list)
folder_object_list = [x.getObject().getId() for x in folder_object_list = [x.getObject().getId() for x in
...@@ -3781,15 +3781,16 @@ VALUES ...@@ -3781,15 +3781,16 @@ VALUES
# Make sure that the catalog will not split it with such research : # Make sure that the catalog will not split it with such research :
# title=foo AND title=bar # title=foo AND title=bar
title='foo bar' title='foo bar'
person_module.newContent(portal_type='Person',title=title).immediateReindexObject() person_module.newContent(portal_type='Person',title=title)
self.tic()
title = title.replace(' ', ' ') title = title.replace(' ', ' ')
person = person_module.newContent(portal_type='Person',title=title) person = person_module.newContent(portal_type='Person',title=title)
person_id = person.getId() person_id = person.getId()
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertTrue(person_id in folder_object_list) self.assertTrue(person_id in folder_object_list)
folder_object_list = [x.getObject().getId() for x in folder_object_list = [x.getObject().getId() for x in
person_module.searchFolder(title=title)] person_module.searchFolder(**{'catalog.title':title})]
self.assertEqual([person_id],folder_object_list) self.assertEqual([person_id],folder_object_list)
def test_SearchFolderWithSingleQuote(self): def test_SearchFolderWithSingleQuote(self):
...@@ -3800,7 +3801,7 @@ VALUES ...@@ -3800,7 +3801,7 @@ VALUES
title="foo 'bar" title="foo 'bar"
person = person_module.newContent(portal_type='Person',title=title) person = person_module.newContent(portal_type='Person',title=title)
person_id = person.getId() person_id = person.getId()
person.immediateReindexObject() self.tic()
folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()] folder_object_list = [x.getObject().getId() for x in person_module.searchFolder()]
self.assertTrue(person_id in folder_object_list) self.assertTrue(person_id in folder_object_list)
folder_object_list = [x.getObject().getId() for x in folder_object_list = [x.getObject().getId() for x in
...@@ -3817,7 +3818,7 @@ VALUES ...@@ -3817,7 +3818,7 @@ VALUES
person = person_module.newContent(portal_type='Person',title=title, person = person_module.newContent(portal_type='Person',title=title,
description=description) description=description)
person_uid = person.getUid() person_uid = person.getUid()
person.immediateReindexObject() self.tic()
folder_object_list = person_module.searchFolder(uid=person_uid, select_dict={'title': None}) folder_object_list = person_module.searchFolder(uid=person_uid, select_dict={'title': None})
new_title = 'bar' new_title = 'bar'
new_description = 'foobarfoo' new_description = 'foobarfoo'
......
...@@ -42,7 +42,7 @@ class TestERP5CatalogSecurityUidOptimization(ERP5TypeTestCase): ...@@ -42,7 +42,7 @@ class TestERP5CatalogSecurityUidOptimization(ERP5TypeTestCase):
XXX: Inherit from TestERP5Catalog so we test default and security_uid optmization with same tests. XXX: Inherit from TestERP5Catalog so we test default and security_uid optmization with same tests.
""" """
business_template_list = ['erp5_security_uid_innodb_catalog', business_template_list = ['erp5_security_uid_innodb_catalog',
'erp5_full_text_myisam_catalog','erp5_base'] 'erp5_full_text_mroonga_catalog','erp5_base']
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return self.business_template_list return self.business_template_list
......
...@@ -85,7 +85,7 @@ class TestLiveConfiguratorWorkflowMixin(SecurityTestCase): ...@@ -85,7 +85,7 @@ class TestLiveConfiguratorWorkflowMixin(SecurityTestCase):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ('erp5_core_proxy_field_legacy', return ('erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_workflow', 'erp5_workflow',
'erp5_configurator', 'erp5_configurator',
......
...@@ -43,7 +43,7 @@ class TestConfiguratorItem(TestLiveConfiguratorWorkflowMixin): ...@@ -43,7 +43,7 @@ class TestConfiguratorItem(TestLiveConfiguratorWorkflowMixin):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ('erp5_core_proxy_field_legacy', return ('erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_workflow', 'erp5_workflow',
'erp5_configurator', 'erp5_configurator',
......
...@@ -38,7 +38,7 @@ class TestConfiguratorTool(TestLiveConfiguratorWorkflowMixin): ...@@ -38,7 +38,7 @@ class TestConfiguratorTool(TestLiveConfiguratorWorkflowMixin):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ('erp5_core_proxy_field_legacy', return ('erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_workflow', 'erp5_workflow',
'erp5_configurator', 'erp5_configurator',
......
...@@ -47,7 +47,7 @@ class TestZeleniumConfiguratorStandard(ERP5TypeFunctionalTestCase): ...@@ -47,7 +47,7 @@ class TestZeleniumConfiguratorStandard(ERP5TypeFunctionalTestCase):
""" """
Return the list of business templates. Return the list of business templates.
""" """
return ('erp5_core_proxy_field_legacy', 'erp5_full_text_myisam_catalog', return ('erp5_core_proxy_field_legacy', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_workflow', 'erp5_configurator', 'erp5_base', 'erp5_workflow', 'erp5_configurator',
'erp5_configurator_standard', 'erp5_jquery', 'erp5_configurator_standard', 'erp5_jquery',
'erp5_ui_test_core', 'erp5_accounting', 'erp5_ui_test_core', 'erp5_accounting',
......
...@@ -1271,7 +1271,8 @@ class SelectionTool( BaseTool, SimpleItem ): ...@@ -1271,7 +1271,8 @@ class SelectionTool( BaseTool, SimpleItem ):
if len(field_value): if len(field_value):
sql_catalog = self.portal_catalog.getSQLCatalog() sql_catalog = self.portal_catalog.getSQLCatalog()
field_value = sql_catalog.buildQuery({ field_value = sql_catalog.buildQuery({
catalog_index: field_value.splitlines() catalog_index:{'query':field_value.splitlines(),
'key':'ExactMatch',},
}).asSearchTextExpression(sql_catalog, column='') }).asSearchTextExpression(sql_catalog, column='')
REQUEST.form[field_key] = field_value REQUEST.form[field_key] = field_value
......
...@@ -103,7 +103,7 @@ class TestDocumentMixin(ERP5TypeTestCase): ...@@ -103,7 +103,7 @@ class TestDocumentMixin(ERP5TypeTestCase):
business_template_list = ['erp5_core_proxy_field_legacy', business_template_list = ['erp5_core_proxy_field_legacy',
'erp5_jquery', 'erp5_jquery',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_ingestion_mysql_innodb_catalog', 'erp5_ingestion_mysql_innodb_catalog',
'erp5_ingestion', 'erp5_ingestion',
...@@ -1155,13 +1155,13 @@ class TestDocument(TestDocumentMixin): ...@@ -1155,13 +1155,13 @@ class TestDocument(TestDocumentMixin):
self.assertSameSet([document_1,web_page_1], getAdvancedSearchStringResultList(**kw)) self.assertSameSet([document_1,web_page_1], getAdvancedSearchStringResultList(**kw))
# exact word search # exact word search
kw = {'searchabletext_any': '*', kw = {'searchabletext_any': '',
'searchabletext_phrase': 'linux python'} 'searchabletext_phrase': 'linux python'}
self.assertSameSet([document_1], getAdvancedSearchStringResultList(**kw)) self.assertSameSet([document_1], getAdvancedSearchStringResultList(**kw))
kw = {'searchabletext_any': '*', kw = {'searchabletext_any': '',
'searchabletext_phrase': 'python linux'} 'searchabletext_phrase': 'python linux'}
self.assertSameSet([document_2], getAdvancedSearchStringResultList(**kw)) self.assertSameSet([document_2], getAdvancedSearchStringResultList(**kw))
kw = {'searchabletext_any': '*', kw = {'searchabletext_any': '',
'searchabletext_phrase': 'python linux knowledge system'} 'searchabletext_phrase': 'python linux knowledge system'}
self.assertSameSet([document_2], getAdvancedSearchStringResultList(**kw)) self.assertSameSet([document_2], getAdvancedSearchStringResultList(**kw))
...@@ -1243,7 +1243,7 @@ class TestDocument(TestDocumentMixin): ...@@ -1243,7 +1243,7 @@ class TestDocument(TestDocumentMixin):
# should return all documents matching a word no matter of contributor # should return all documents matching a word no matter of contributor
self.assertSameSet([web_page_1, document_4], getAdvancedSearchStringResultList(**kw)) self.assertSameSet([web_page_1, document_4], getAdvancedSearchStringResultList(**kw))
kw = {'searchabletext_any': 'owner', kw = {'searchabletext_any': 'owner',
'contributor_title': '%Contributor%'} 'contributor_title': 'Contributor'}
self.assertSameSet([document_4], getAdvancedSearchStringResultList(**kw)) self.assertSameSet([document_4], getAdvancedSearchStringResultList(**kw))
# multiple portal_type specified # multiple portal_type specified
......
...@@ -52,7 +52,7 @@ class TestOooDynamicStyle(ERP5TypeTestCase): ...@@ -52,7 +52,7 @@ class TestOooDynamicStyle(ERP5TypeTestCase):
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return ('erp5_core_proxy_field_legacy', return ('erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_ingestion_mysql_innodb_catalog', 'erp5_ingestion_mysql_innodb_catalog',
'erp5_ingestion', 'erp5_ingestion',
......
...@@ -49,7 +49,7 @@ class testTioSafeMixin(ERP5TypeTestCase): ...@@ -49,7 +49,7 @@ class testTioSafeMixin(ERP5TypeTestCase):
""" Return the list of BT required by unit tests. """ """ Return the list of BT required by unit tests. """
return ( return (
'erp5_core_proxy_field_legacy', 'erp5_core_proxy_field_legacy',
'erp5_full_text_myisam_catalog', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_base',
'erp5_pdm', 'erp5_pdm',
'erp5_simulation', 'erp5_simulation',
......
...@@ -41,7 +41,7 @@ class TestAnonymousSelection(TestZeleniumCore): ...@@ -41,7 +41,7 @@ class TestAnonymousSelection(TestZeleniumCore):
""" """
Return the list of business templates. Return the list of business templates.
""" """
return ('erp5_core_proxy_field_legacy', 'erp5_full_text_myisam_catalog', return ('erp5_core_proxy_field_legacy', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_ui_test_core', 'erp5_ui_test', 'erp5_forge', 'erp5_base', 'erp5_ui_test_core', 'erp5_ui_test', 'erp5_forge',
) )
......
...@@ -39,7 +39,7 @@ class TestZeleniumCore(ERP5TypeFunctionalTestCase): ...@@ -39,7 +39,7 @@ class TestZeleniumCore(ERP5TypeFunctionalTestCase):
""" """
Return the list of business templates. Return the list of business templates.
""" """
return ('erp5_core_proxy_field_legacy', 'erp5_full_text_myisam_catalog', return ('erp5_core_proxy_field_legacy', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_ui_test_core', 'erp5_ui_test', 'erp5_forge', 'erp5_base', 'erp5_ui_test_core', 'erp5_ui_test', 'erp5_forge',
'erp5_dhtml_style', 'erp5_dhtml_ui_test', 'erp5_dhtml_style', 'erp5_dhtml_ui_test',
'erp5_jquery', 'erp5_jquery_ui', 'erp5_jquery', 'erp5_jquery_ui',
......
...@@ -43,7 +43,7 @@ class TestZeleniumKM(ERP5TypeFunctionalTestCase): ...@@ -43,7 +43,7 @@ class TestZeleniumKM(ERP5TypeFunctionalTestCase):
""" """
# XXX This is a rough list, we should drop as much as we can, and # XXX This is a rough list, we should drop as much as we can, and
# keep only minimal # keep only minimal
return ('erp5_core_proxy_field_legacy', 'erp5_full_text_myisam_catalog', return ('erp5_core_proxy_field_legacy', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_ui_test_core', 'erp5_ui_test', 'erp5_forge', 'erp5_base', 'erp5_ui_test_core', 'erp5_ui_test', 'erp5_forge',
'erp5_dhtml_style', 'erp5_dhtml_ui_test', 'erp5_dhtml_style', 'erp5_dhtml_ui_test',
'erp5_jquery', 'erp5_jquery_ui', 'erp5_jquery', 'erp5_jquery_ui',
......
...@@ -48,7 +48,7 @@ class TestZeleniumStandaloneUserTutorial(ERP5TypeFunctionalTestCase): ...@@ -48,7 +48,7 @@ class TestZeleniumStandaloneUserTutorial(ERP5TypeFunctionalTestCase):
""" """
Return the list of business templates. Return the list of business templates.
""" """
return ('erp5_core_proxy_field_legacy', 'erp5_full_text_myisam_catalog', return ('erp5_core_proxy_field_legacy', 'erp5_full_text_mroonga_catalog',
'erp5_base', 'erp5_ui_test_core', 'erp5_forge', 'erp5_base', 'erp5_ui_test_core', 'erp5_forge',
'erp5_dhtml_style', 'erp5_dhtml_style',
'erp5_jquery', 'erp5_jquery_ui', 'erp5_jquery', 'erp5_jquery_ui',
......
...@@ -65,7 +65,7 @@ class TestDeferredConnection(ERP5TypeTestCase): ...@@ -65,7 +65,7 @@ class TestDeferredConnection(ERP5TypeTestCase):
""" """
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return 'erp5_full_text_myisam_catalog', return 'erp5_full_text_mroonga_catalog',
def getTitle(self): def getTitle(self):
return "Deferred Connection" return "Deferred Connection"
......
...@@ -33,6 +33,7 @@ from Products.ZSQLCatalog.SQLExpression import SQLExpression ...@@ -33,6 +33,7 @@ from Products.ZSQLCatalog.SQLExpression import SQLExpression
from Products.ZSQLCatalog.interfaces.operator import IOperator from Products.ZSQLCatalog.interfaces.operator import IOperator
from zope.interface.verify import verifyClass from zope.interface.verify import verifyClass
from Products.ZSQLCatalog.SQLCatalog import list_type_list from Products.ZSQLCatalog.SQLCatalog import list_type_list
import re
class ComparisonOperatorBase(OperatorBase): class ComparisonOperatorBase(OperatorBase):
def asSQLExpression(self, column, value_list, only_group_columns): def asSQLExpression(self, column, value_list, only_group_columns):
...@@ -105,13 +106,17 @@ class MatchComparisonOperator(MonovaluedComparisonOperator): ...@@ -105,13 +106,17 @@ class MatchComparisonOperator(MonovaluedComparisonOperator):
This operator can emit a select expression, so it overrides This operator can emit a select expression, so it overrides
asSQLExpression inseatd of just defining a render method. asSQLExpression inseatd of just defining a render method.
""" """
# No need to do full text search for an empty string.
if value_list == '':
column, value_list = self.render(column, value_list)
return SQLExpression(self, where_expression='%s %s %s' % (column, '=', value_list))
match_string = self.where_expression_format_string % { match_string = self.where_expression_format_string % {
'column': column, 'column': column,
'value_list': self.renderValue(value_list), 'value_list': self.renderValue(value_list),
} }
select_dict = {} select_dict = {}
if not only_group_columns: if not only_group_columns:
select_dict[column.replace('`', '').split('.')[-1]] = match_string select_dict['%s__score__' % column.replace('`', '').rsplit('.', 1)[-1]] = match_string
# Sort on this column uses relevance. # Sort on this column uses relevance.
# TODO: Add a way to allow sorting by raw column value. # TODO: Add a way to allow sorting by raw column value.
order_by_dict = { order_by_dict = {
...@@ -127,6 +132,50 @@ class MatchComparisonOperator(MonovaluedComparisonOperator): ...@@ -127,6 +132,50 @@ class MatchComparisonOperator(MonovaluedComparisonOperator):
verifyClass(IOperator, MatchComparisonOperator) verifyClass(IOperator, MatchComparisonOperator)
class MroongaComparisonOperator(MatchComparisonOperator):
fulltext_boolean_splitter = re.compile(r'(\s|\(.+?\)|".+?")')
fulltext_boolean_detector = re.compile(r'(^[+-]|^.+\*$|^["(].+[")]$)')
def __init__(self, operator, force_boolean=False):
MatchComparisonOperator.__init__(self, operator, ' IN BOOLEAN MODE')
self.force_boolean = force_boolean
def renderValue(self, value_list):
"""
Special Query renderer for MroongaFullText queries:
* by default 'AND' search by using '*D+' pragma.
* similarity search for non-boolean queries by using '*S"..."' operator.
"""
if isinstance(value_list, list_type_list):
try:
value_list, = value_list
except ValueError:
raise ValueError, '%r: value_list must not contain more than one item. Got %r' % (self, value_list)
if self.force_boolean:
fulltext_query = '*D+ %s' % value_list
return self._renderValue(fulltext_query)
else:
match_query_list = []
match_boolean_query_list = []
for token in self.fulltext_boolean_splitter.split(value_list):
token = token.strip()
if not token:
continue
elif self.fulltext_boolean_detector.match(token):
match_boolean_query_list.append(token)
else:
match_query_list.append(token)
# Always use BOOLEAN MODE to combine similarity search and boolean search.
fulltext_query = '*D+'
if match_query_list:
fulltext_query += ' *S"%s"' % ' '.join(match_query_list)
if match_boolean_query_list:
fulltext_query += ' %s' % ' '.join(match_boolean_query_list)
return self._renderValue(fulltext_query)
verifyClass(IOperator, MroongaComparisonOperator)
class SphinxSEComparisonOperator(MonovaluedComparisonOperator): class SphinxSEComparisonOperator(MonovaluedComparisonOperator):
def __init__(self, operator, mode=''): def __init__(self, operator, mode=''):
MonovaluedComparisonOperator.__init__(self, operator, '') MonovaluedComparisonOperator.__init__(self, operator, '')
...@@ -173,9 +222,10 @@ operator_dict = { ...@@ -173,9 +222,10 @@ operator_dict = {
'match': MatchComparisonOperator('match'), 'match': MatchComparisonOperator('match'),
'match_boolean': MatchComparisonOperator('match_boolean', mode=' IN BOOLEAN MODE'), 'match_boolean': MatchComparisonOperator('match_boolean', mode=' IN BOOLEAN MODE'),
'match_expansion': MatchComparisonOperator('match_expansion', mode=' WITH QUERY EXPANSION'), 'match_expansion': MatchComparisonOperator('match_expansion', mode=' WITH QUERY EXPANSION'),
'mroonga': MroongaComparisonOperator('mroonga'),
'mroonga_boolean': MroongaComparisonOperator('mroonga_boolean', force_boolean=True),
'sphinxse': SphinxSEComparisonOperator('sphinxse'), 'sphinxse': SphinxSEComparisonOperator('sphinxse'),
'in': MultivaluedComparisonOperator('in'), 'in': MultivaluedComparisonOperator('in'),
'is': MonovaluedComparisonOperator('is'), 'is': MonovaluedComparisonOperator('is'),
'is not': MonovaluedComparisonOperator('is not', '!='), 'is not': MonovaluedComparisonOperator('is not', '!='),
} }
...@@ -62,12 +62,14 @@ class RelatedQuery(Query): ...@@ -62,12 +62,14 @@ class RelatedQuery(Query):
self.table_alias_list = table_alias_list self.table_alias_list = table_alias_list
def _asSearchTextExpression(self, sql_catalog, column=None): def _asSearchTextExpression(self, sql_catalog, column=None):
assert column is None assert column in (None, '')
if column is None:
column = self.search_key.getColumn()
join_condition = self.join_condition join_condition = self.join_condition
if join_condition is None: if join_condition is None:
result = None result = None
else: else:
result = join_condition.asSearchTextExpression(sql_catalog, column=self.search_key.getColumn()) result = join_condition.asSearchTextExpression(sql_catalog, column=column)
return False, result return False, result
def asSQLExpression(self, sql_catalog, column_map, only_group_columns): def asSQLExpression(self, sql_catalog, column_map, only_group_columns):
......
...@@ -334,6 +334,9 @@ class SQLExpression(object): ...@@ -334,6 +334,9 @@ class SQLExpression(object):
if can_merge_sql_expression and alias in mergeable_set: if can_merge_sql_expression and alias in mergeable_set:
# Custom conflict resolution # Custom conflict resolution
column = '%s + %s' % (existing_value, column) column = '%s + %s' % (existing_value, column)
elif alias.endswith('__score__'):
# We only support the first full text score in select dict.
pass
else: else:
message = '%r is a known alias for column %r, can\'t alias it now to column %r' % (alias, existing_value, column) message = '%r is a known alias for column %r, can\'t alias it now to column %r' % (alias, existing_value, column)
if DEBUG: if DEBUG:
......
...@@ -28,29 +28,30 @@ ...@@ -28,29 +28,30 @@
# #
############################################################################## ##############################################################################
from SearchKey import SearchKey from DefaultKey import DefaultKey
from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery
from Products.ZSQLCatalog.SearchText import parse
from Products.ZSQLCatalog.interfaces.search_key import ISearchKey from Products.ZSQLCatalog.interfaces.search_key import ISearchKey
from Products.ZSQLCatalog.SearchText import dequote
from zope.interface.verify import verifyClass from zope.interface.verify import verifyClass
import re import re
FULLTEXT_BOOLEAN_DETECTOR = re.compile(r'.*((^|\s)[\+\-<>\(\~]|[\*\)](\s|$))') FULLTEXT_BOOLEAN_DETECTOR = re.compile(r'.*((^|\s)[\+\-<>\(\~]|[\*\)](\s|$))')
class FullTextKey(SearchKey): class FullTextKey(DefaultKey):
""" """
This SearchKey generates SQL fulltext comparisons. This SearchKey generates SQL fulltext comparisons.
""" """
default_comparison_operator = 'match' default_comparison_operator = 'match'
get_operator_from_value = False get_operator_from_value = False
def parseSearchText(self, value, is_column):
return parse(value, is_column)
def dequoteParsedText(self): def dequoteParsedText(self):
return False return False
def _renderValueAsSearchText(self, value, operator): def _renderValueAsSearchText(self, value, operator):
# XXX:
# return value for 'a b' here is '(a b), but keyword search with
# '(a b)' means fulltext search with (a OR b), that is different
# from fulltext='a b' that means fulltext search with (a AND b).
return '(%s)' % (value, ) return '(%s)' % (value, )
def _processSearchValue(self, search_value, logical_operator, def _processSearchValue(self, search_value, logical_operator,
...@@ -61,8 +62,8 @@ class FullTextKey(SearchKey): ...@@ -61,8 +62,8 @@ class FullTextKey(SearchKey):
mode, make the operator for that value be 'match_boolean'. mode, make the operator for that value be 'match_boolean'.
""" """
operator_value_dict, logical_operator, parsed = \ operator_value_dict, logical_operator, parsed = \
SearchKey._processSearchValue(self, search_value, logical_operator, super(FullTextKey, self)._processSearchValue(
comparison_operator) search_value, logical_operator, comparison_operator)
new_value_list = [] new_value_list = []
append = new_value_list.append append = new_value_list.append
for value in operator_value_dict.pop('match', []): for value in operator_value_dict.pop('match', []):
...@@ -77,6 +78,13 @@ class FullTextKey(SearchKey): ...@@ -77,6 +78,13 @@ class FullTextKey(SearchKey):
operator_value_dict['match_boolean'].extend(new_value_list) operator_value_dict['match_boolean'].extend(new_value_list)
else: else:
operator_value_dict['match'] = new_value_list operator_value_dict['match'] = new_value_list
# Dequote for non full-text queries.
for comparison_operator, value_list in operator_value_dict.iteritems():
if comparison_operator not in ('match', 'match_boolean'):
operator_value_dict[comparison_operator] = [
isinstance(value, basestring) and dequote(value) or value
for value in value_list
]
return operator_value_dict, logical_operator, parsed return operator_value_dict, logical_operator, parsed
def _buildQuery(self, operator_value_dict, logical_operator, parsed, group): def _buildQuery(self, operator_value_dict, logical_operator, parsed, group):
...@@ -88,10 +96,20 @@ class FullTextKey(SearchKey): ...@@ -88,10 +96,20 @@ class FullTextKey(SearchKey):
column = self.getColumn() column = self.getColumn()
query_list = [] query_list = []
append = query_list.append append = query_list.append
for comparison_operator, value_list in operator_value_dict.iteritems(): for comparison_operator in ('match', 'match_boolean'):
value_list = operator_value_dict.pop(comparison_operator, [])
if not value_list:
continue
# XXX:
# In MySQL FTS, no operator implies OR so that we should not merge
# AND queries into one...
append(SimpleQuery(search_key=self, append(SimpleQuery(search_key=self,
comparison_operator=comparison_operator, comparison_operator=comparison_operator,
group=group, **{column: ' '.join(value_list)})) group=group, **{column: ' '.join(value_list)}))
# Other comparison operators are handled by the super class.
if operator_value_dict:
query_list += super(FullTextKey, self)._buildQuery(
operator_value_dict, logical_operator, parsed, group)
return query_list return query_list
verifyClass(ISearchKey, FullTextKey) verifyClass(ISearchKey, FullTextKey)
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2014 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from MroongaFullTextKey import MroongaFullTextKey
from Products.ZSQLCatalog.interfaces.search_key import ISearchKey
from zope.interface.verify import verifyClass
class MroongaBooleanFullTextKey(MroongaFullTextKey):
"""
This SearchKey generates SQL fulltext comparisons for Mroonga whose
default comparison operator is mroonga_boolean.
"""
default_comparison_operator = 'mroonga_boolean'
verifyClass(ISearchKey, MroongaBooleanFullTextKey)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2014-2006 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from DefaultKey import DefaultKey
from Products.ZSQLCatalog.Query.SimpleQuery import SimpleQuery
from Products.ZSQLCatalog.interfaces.search_key import ISearchKey
from Products.ZSQLCatalog.SearchText import dequote
from zope.interface.verify import verifyClass
class MroongaFullTextKey(DefaultKey):
default_comparison_operator = 'mroonga'
def dequoteParsedText(self):
return False
def _renderValueAsSearchText(self, value, operator):
# XXX:
# return value for 'a b' here is '(a b), but keyword search with
# '(a b)' means fulltext search with (a OR b), that is different
# from fulltext='a b' that means fulltext search with (a AND b).
return '(%s)' % (value, )
def _processSearchValue(self, search_value, logical_operator,
comparison_operator):
operator_value_dict, logical_operator, parsed = \
super(MroongaFullTextKey, self)._processSearchValue(
search_value, logical_operator, comparison_operator)
# Dequote for non full-text queries.
for comparison_operator, value_list in operator_value_dict.iteritems():
if comparison_operator not in ('mroonga', 'mroonga_boolean'):
operator_value_dict[comparison_operator] = [
isinstance(value, basestring) and dequote(value) or value
for value in value_list
]
return operator_value_dict, logical_operator, parsed
def _buildQuery(self, operator_value_dict, logical_operator, parsed, group):
"""
Special Query builder for FullText queries: merge all values having the
same operator into just one query, to save SQL server from the burden to
do multiple fulltext lookups when one would suit the purpose.
"""
column = self.getColumn()
query_list = []
append = query_list.append
for comparison_operator in ('mroonga', 'mroonga_boolean'):
value_list = operator_value_dict.pop(comparison_operator, [])
if not value_list:
continue
if logical_operator == 'and':
joined_value = ' '.join(value_list)
append(SimpleQuery(search_key=self,
comparison_operator=comparison_operator,
group=group, **{column:joined_value}))
else:
# TODO : We can join to one query like 'aaa OR (bbb ccc) OR "ddd eee"'.
for value in value_list:
append(SimpleQuery(search_key=self,
comparison_operator=comparison_operator,
group=group, **{column:value}))
# Other comparison operators are handled by the super class.
if operator_value_dict:
query_list += super(MroongaFullTextKey, self)._buildQuery(
operator_value_dict, logical_operator, parsed, group)
return query_list
verifyClass(ISearchKey, MroongaFullTextKey)
...@@ -168,8 +168,10 @@ class DummyCatalog(SQLCatalog): ...@@ -168,8 +168,10 @@ class DummyCatalog(SQLCatalog):
sql_catalog_keyword_search_keys = ('keyword', ) sql_catalog_keyword_search_keys = ('keyword', )
sql_catalog_datetime_search_keys = ('date', ) sql_catalog_datetime_search_keys = ('date', )
sql_catalog_full_text_search_keys = ('fulltext', ) sql_catalog_full_text_search_keys = ('old_fulltext', )
sql_catalog_scriptable_keys = ('scriptable_keyword | scriptableKeyScript', ) sql_catalog_scriptable_keys = ('scriptable_keyword | scriptableKeyScript', )
sql_catalog_search_keys = ('fulltext | MroongaFullTextKey',
'fulltext_boolean | MroongaBooleanFullTextKey',)
def getColumnMap(self): def getColumnMap(self):
""" """
...@@ -180,7 +182,9 @@ class DummyCatalog(SQLCatalog): ...@@ -180,7 +182,9 @@ class DummyCatalog(SQLCatalog):
'default': ['foo', ], 'default': ['foo', ],
'keyword': ['foo', ], 'keyword': ['foo', ],
'date': ['foo', ], 'date': ['foo', ],
'old_fulltext': ['foo', ],
'fulltext': ['foo', ], 'fulltext': ['foo', ],
'fulltext_boolean': ['foo', ],
'other_uid': ['bar', ], 'other_uid': ['bar', ],
'ambiguous_mapping': ['foo', 'bar'], 'ambiguous_mapping': ['foo', 'bar'],
} }
...@@ -495,8 +499,8 @@ class TestSQLCatalog(ERP5TypeTestCase): ...@@ -495,8 +499,8 @@ class TestSQLCatalog(ERP5TypeTestCase):
# This example introduces impossible-to-merge search text criterion, which # This example introduces impossible-to-merge search text criterion, which
# is allowed as long as # is allowed as long as
reference_query = ReferenceQuery( reference_query = ReferenceQuery(
ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'), ReferenceQuery(ReferenceQuery(operator='mroonga', fulltext='a'),
ReferenceQuery(ReferenceQuery(operator='match', fulltext='b'), ReferenceQuery(ReferenceQuery(operator='mroonga', fulltext='b'),
operator='not'), operator='and'), operator='and') operator='not'), operator='and'), operator='and')
self.catalog(reference_query, {'fulltext': 'a NOT b'}) self.catalog(reference_query, {'fulltext': 'a NOT b'})
# The same, with an order by, must raise # The same, with an order by, must raise
...@@ -504,15 +508,15 @@ class TestSQLCatalog(ERP5TypeTestCase): ...@@ -504,15 +508,15 @@ class TestSQLCatalog(ERP5TypeTestCase):
{'fulltext': 'a NOT b', 'order_by_list': [('fulltext', ), ]}, {'fulltext': 'a NOT b', 'order_by_list': [('fulltext', ), ]},
check_search_text=False) check_search_text=False)
# If one want to sort on, he must use the equivalent FullText syntax: # If one want to sort on, he must use the equivalent FullText syntax:
self.catalog(ReferenceQuery(ReferenceQuery(operator='match_boolean', self.catalog(ReferenceQuery(ReferenceQuery(operator='mroonga',
fulltext=MatchList(['a -b', '-b a'])), operator='and'), fulltext=MatchList(['a -b', '-b a'])), operator='and'),
{'fulltext': 'a -b', 'order_by_list': [('fulltext', ), ]}, {'fulltext': 'a -b', 'order_by_list': [('fulltext', ), ]},
check_search_text=False) check_search_text=False)
self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'), self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='mroonga', fulltext='a'),
ReferenceQuery(ReferenceQuery(operator='match', fulltext='b'), operator='not'), operator='or'), operator='and'), ReferenceQuery(ReferenceQuery(operator='mroonga', fulltext='b'), operator='not'), operator='or'), operator='and'),
{'fulltext': 'a OR NOT b'}) {'fulltext': 'a OR NOT b'})
self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'), self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='mroonga', fulltext='a'),
ReferenceQuery(ReferenceQuery(operator='match', fulltext='b'), operator='not'), operator='and'), operator='and'), ReferenceQuery(ReferenceQuery(operator='mroonga', fulltext='b'), operator='not'), operator='and'), operator='and'),
{'fulltext': 'a AND NOT b'}) {'fulltext': 'a AND NOT b'})
def test_006_testRelatedKey_with_multiple_join(self): def test_006_testRelatedKey_with_multiple_join(self):
...@@ -540,7 +544,7 @@ class TestSQLCatalog(ERP5TypeTestCase): ...@@ -540,7 +544,7 @@ class TestSQLCatalog(ERP5TypeTestCase):
check_search_text=False) check_search_text=False)
def test_009_testFullTextKey(self): def test_009_testFullTextKey(self):
self.catalog(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'), operator='and'), self.catalog(ReferenceQuery(ReferenceQuery(operator='mroonga', fulltext='a'), operator='and'),
{'fulltext': 'a'}) {'fulltext': 'a'})
def test_isAdvancedSearchText(self): def test_isAdvancedSearchText(self):
...@@ -551,15 +555,20 @@ class TestSQLCatalog(ERP5TypeTestCase): ...@@ -551,15 +555,20 @@ class TestSQLCatalog(ERP5TypeTestCase):
def test_FullTextSearchMergesQueries(self): def test_FullTextSearchMergesQueries(self):
""" """
XXX this test is for old FullTextKey, not for MroongaFullTextKey
that merges queries only when logical_operator is 'and'. Also
_renderValueAsSearchText it not perfect so that we cannot use the
test codes below for mroonga search key.
FullText criterion on the same scope must be merged into one query. FullText criterion on the same scope must be merged into one query.
Logical operator is ignored, as fulltext operators are expected instead. Logical operator is ignored, as fulltext operators are expected instead.
""" """
self.catalog(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a b'), operator='and'), self.catalog(ReferenceQuery(ReferenceQuery(operator='match', old_fulltext='a b'), operator='and'),
{'fulltext': 'a AND b'}) {'old_fulltext': 'a AND b'})
self.catalog(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a b'), operator='and'), self.catalog(ReferenceQuery(ReferenceQuery(operator='match', old_fulltext='a b'), operator='and'),
{'fulltext': 'a OR b'}) {'old_fulltext': 'a OR b'})
self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a b'), operator='not'), operator='and'), self.catalog(ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', old_fulltext='a b'), operator='not'), operator='and'),
{'fulltext': 'NOT (a b)'}) {'old_fulltext': 'NOT (a b)'})
def test_NoneValueToSimpleQuery(self): def test_NoneValueToSimpleQuery(self):
""" """
...@@ -583,60 +592,69 @@ class TestSQLCatalog(ERP5TypeTestCase): ...@@ -583,60 +592,69 @@ class TestSQLCatalog(ERP5TypeTestCase):
def test_FullTextBooleanMode(self): def test_FullTextBooleanMode(self):
""" """
XXX this test is for old FullTextKey, not for MroongaFullTextKey
that does no automatic mode switch.
Fulltext searches must switch automatically to boolean mode if boolean Fulltext searches must switch automatically to boolean mode if boolean
operators are found in search value. operators are found in search value.
""" """
self.catalog(ReferenceQuery(ReferenceQuery(operator='match_boolean', self.catalog(ReferenceQuery(ReferenceQuery(operator='match_boolean',
fulltext=MatchList(['a*'])), operator='and'), old_fulltext=MatchList(['a*'])), operator='and'),
{'fulltext': 'a*'}) {'old_fulltext': 'a*'})
self.catalog(ReferenceQuery(ReferenceQuery(operator='match_boolean', self.catalog(ReferenceQuery(ReferenceQuery(operator='match_boolean',
fulltext=MatchList(['a* b'])), operator='and'), old_fulltext=MatchList(['a* b'])), operator='and'),
{'fulltext': 'a* b'}) {'old_fulltext': 'a* b'})
self.catalog(ReferenceQuery(ReferenceQuery(operator='match', fulltext='*a'), self.catalog(ReferenceQuery(ReferenceQuery(operator='match', old_fulltext='*a'),
operator='and'), operator='and'),
{'fulltext': '*a'}) {'old_fulltext': '*a'})
self.catalog(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a'), self.catalog(ReferenceQuery(ReferenceQuery(operator='match', old_fulltext='a'),
operator='and'), operator='and'),
{'fulltext': 'a'}) {'old_fulltext': 'a'})
self.catalog(ReferenceQuery(ReferenceQuery(operator='match', fulltext='a+b'), operator='and'), self.catalog(ReferenceQuery(ReferenceQuery(operator='match', old_fulltext='a+b'), operator='and'),
{'fulltext': 'a+b'}) {'old_fulltext': 'a+b'})
self.catalog(ReferenceQuery(ReferenceQuery(operator='match_boolean', self.catalog(ReferenceQuery(ReferenceQuery(operator='match_boolean',
fulltext=MatchList(['a +b', '+b a'])), operator='and'), old_fulltext=MatchList(['a +b', '+b a'])), operator='and'),
{'fulltext': 'a +b'}, check_search_text=False) {'old_fulltext': 'a +b'}, check_search_text=False)
self.catalog(ReferenceQuery(ReferenceQuery( self.catalog(ReferenceQuery(ReferenceQuery(
ReferenceQuery(operator='=', uid='foo'), ReferenceQuery(operator='=', uid='foo'),
ReferenceQuery(operator='match_boolean', ReferenceQuery(operator='match_boolean',
fulltext=MatchList(['+a b', 'b +a'])), old_fulltext=MatchList(['+a b', 'b +a'])),
operator='and'), operator='and'), {'fulltext': '+a b uid:foo'}) operator='and'), operator='and'), {'old_fulltext': '+a b uid:foo'})
def test_FullTextQuoting(self): def test_FullTextQuoting(self):
"""
XXX this test is for old FullTextKey, not for MroongaFullTextKey
that merges queries only when logical_operator is 'and'. Also
_renderValueAsSearchText it not perfect so that we cannot use the
test codes below for mroonga search key.
"""
# Quotes must be kept # Quotes must be kept
self.catalog(ReferenceQuery(ReferenceQuery(operator='match', self.catalog(ReferenceQuery(ReferenceQuery(operator='match',
fulltext='"a"'), operator='and'), old_fulltext='"a"'), operator='and'),
{'fulltext': '"a"'}) {'old_fulltext': '"a"'})
self.catalog(ReferenceQuery(ReferenceQuery(operator='match', self.catalog(ReferenceQuery(ReferenceQuery(operator='match',
fulltext='"foo" bar "baz"'), operator='and'), old_fulltext='"foo" bar "baz"'), operator='and'),
{'fulltext': '"foo" bar "baz"'}) {'old_fulltext': '"foo" bar "baz"'})
# ...But each column must follow rules defined in configured SearchKey for # ...But each column must follow rules defined in configured SearchKey for
# that column (in this case: quotes must be stripped). # that column (in this case: quotes must be stripped).
ref_query = ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', ref_query = ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match',
fulltext='"foo" bar'), ReferenceQuery(operator='=', old_fulltext='"foo" bar'), ReferenceQuery(operator='=',
default='hoge \"pon'), operator='and'), operator='and') default='hoge \"pon'), operator='and'), operator='and')
self.catalog(ref_query, { self.catalog(ref_query, {
'keyword': 'default:"hoge \\"pon" AND fulltext:("foo" bar)'}) 'keyword': 'default:"hoge \\"pon" AND old_fulltext:("foo" AND bar)'})
self.catalog(ref_query, { self.catalog(ref_query, {
'fulltext': '"foo" bar AND default:"hoge \\"pon"'}) 'old_fulltext': '"foo" bar AND default:"hoge \\"pon"'})
ref_query = ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match', ref_query = ReferenceQuery(ReferenceQuery(ReferenceQuery(operator='match',
fulltext='"\\"foo\\" bar"'), ReferenceQuery(operator='=', old_fulltext='"\\"foo\\" bar"'), ReferenceQuery(operator='=',
default='hoge \"pon'), operator='and'), operator='and') default='hoge \"pon'), operator='and'), operator='and')
self.catalog(ref_query, { self.catalog(ref_query, {
'keyword': 'default:"hoge \\"pon" AND fulltext:"\\"foo\\" bar"'}) 'keyword': 'default:"hoge \\"pon" AND old_fulltext:"\\"foo\\" bar"'})
def test_DefaultKeyTextRendering(self): def test_DefaultKeyTextRendering(self):
self.catalog(ReferenceQuery(ReferenceQuery(operator='like', default='a% b'), operator='and'), self.catalog(ReferenceQuery(ReferenceQuery(operator='like', default='a% b'), operator='and'),
......
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