Skip to content

Commit 1b37f6f

Browse files
authored
IBX-9845: Added support for Solr 9.8+ (#95)
1 parent 3c2ad6b commit 1b37f6f

File tree

12 files changed

+390
-19
lines changed

12 files changed

+390
-19
lines changed

.github/init_solr.sh

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
#!/usr/bin/env bash
22

3-
default_config_files[1]='src/lib/Resources/config/solr/schema.xml'
4-
default_config_files[2]='src/lib/Resources/config/solr/custom-fields-types.xml'
5-
default_config_files[3]='src/lib/Resources/config/solr/language-fieldtypes.xml'
3+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
4+
5+
SOLR_VERSION=${SOLR_VERSION:-'9.8.1'}
6+
7+
if [[ "${SOLR_VERSION}" =~ ^9\. ]]; then
8+
default_config_files[1]="${SCRIPT_DIR}/../src/lib/Resources/config/solr/managed-schema.xml"
9+
default_config_files[2]="${SCRIPT_DIR}/../src/lib/Resources/config/solr/custom-fields-types-solr9.xml"
10+
else
11+
default_config_files[1]="${SCRIPT_DIR}/../src/lib/Resources/config/solr/schema.xml"
12+
default_config_files[2]="${SCRIPT_DIR}/../src/lib/Resources/config/solr/custom-fields-types.xml"
13+
fi
14+
15+
default_config_files[3]="${SCRIPT_DIR}/../src/lib/Resources/config/solr/language-fieldtypes.xml"
616

717
default_cores[0]='core0'
818
default_cores[1]='core1'
@@ -16,7 +26,6 @@ default_shards=('shard0')
1626

1727
SOLR_PORT=${SOLR_PORT:-8983}
1828
SOLR_DIR=${SOLR_DIR:-'__solr'}
19-
SOLR_VERSION=${SOLR_VERSION:-'8.11.2'}
2029
SOLR_INSTALL_DIR="${SOLR_DIR}/${SOLR_VERSION}"
2130
SOLR_DEBUG=${SOLR_DEBUG:-false}
2231
SOLR_HOME=${SOLR_HOME:-'ezcloud'}
@@ -32,7 +41,13 @@ SOLR_CLOUD=${SOLR_CLOUD:-'no'}
3241

3342
INSTALL_DIR="${SOLR_DIR}/${SOLR_VERSION}"
3443
HOME_DIR="${INSTALL_DIR}/server/${SOLR_HOME}"
35-
TEMPLATE_DIR="${HOME_DIR}/template"
44+
45+
if [[ "${SOLR_VERSION}" =~ ^9\. ]]; then
46+
TEMPLATE_DIR="${HOME_DIR}/template/conf"
47+
else
48+
TEMPLATE_DIR="${HOME_DIR}/template"
49+
fi
50+
3651
START_SCRIPT="./${INSTALL_DIR}/bin/solr"
3752
ZOOKEEPER_CLI_SCRIPT="./${INSTALL_DIR}/server/scripts/cloud-scripts/zkcli.sh"
3853
ZOOKEEPER_HOST=""
@@ -45,6 +60,9 @@ fi
4560
download() {
4661
case ${SOLR_VERSION} in
4762
# PS!!: Append versions and don't remove old ones (except in major versions), used in integration tests from other packages!
63+
9.*)
64+
url="https://archive.apache.org/dist/solr/solr/${SOLR_VERSION}/solr-${SOLR_VERSION}.tgz"
65+
;;
4866
7.7.* | 8.* )
4967
url="https://archive.apache.org/dist/lucene/solr/${SOLR_VERSION}/solr-${SOLR_VERSION}.tgz"
5068
;;
@@ -104,8 +122,8 @@ copy_file() {
104122
create_dir() {
105123
local dir_name=$1
106124

107-
if [ ! -d ${dir_name} ] ; then
108-
mkdir ${dir_name} || exit_on_error "Couldn't create directory '${dir_name}'"
125+
if [ ! -d "${dir_name}" ] ; then
126+
mkdir -p "${dir_name}" || exit_on_error "Couldn't create directory '${dir_name}'"
109127
echo "Created directory '${dir_name}'"
110128
fi
111129
}
@@ -137,7 +155,11 @@ solr_run() {
137155
echo "Running with version ${SOLR_VERSION} in standalone mode"
138156
echo "Starting solr on port ${SOLR_PORT}..."
139157

140-
./${SOLR_INSTALL_DIR}/bin/solr -p ${SOLR_PORT} -s ${SOLR_HOME} -Dsolr.disable.shardsWhitelist=true || exit_on_error "Can't start Solr"
158+
if [[ "${SOLR_VERSION}" =~ ^9\. ]]; then
159+
./${SOLR_INSTALL_DIR}/bin/solr start -p ${SOLR_PORT} -s ${SOLR_HOME} || exit_on_error "Can't start Solr"
160+
else
161+
./${SOLR_INSTALL_DIR}/bin/solr -p ${SOLR_PORT} -s ${SOLR_HOME} -Dsolr.disable.shardsWhitelist=true || exit_on_error "Can't start Solr"
162+
fi
141163

142164
echo "Started"
143165

@@ -162,7 +184,11 @@ solr_create_core() {
162184
core_name=$1
163185
config_dir=$2
164186

165-
./${SOLR_INSTALL_DIR}/bin/solr create_core -p ${SOLR_PORT} -c ${core_name} -d ${config_dir} || exit_on_error "Can't create core"
187+
solr_port_flag="-p ${SOLR_PORT}"
188+
189+
abs_conf_dir="$(pwd)/${config_dir}"
190+
191+
./${SOLR_INSTALL_DIR}/bin/solr create_core ${solr_port_flag} -c ${core_name} -d "${abs_conf_dir}" || exit_on_error "Can't create core"
166192
}
167193

168194
solr_cloud_configure_nodes() {
@@ -284,10 +310,31 @@ solr_cloud_create_collection() {
284310
download
285311

286312
if [ "$SOLR_CLOUD" = "no" ]; then
313+
314+
if [[ "${SOLR_VERSION}" =~ ^9\. ]]; then
315+
TEMPLATE_CONF="template/conf"
316+
else
317+
TEMPLATE_CONF="template"
318+
fi
319+
320+
echo "Generating Solr configuration in ${SOLR_INSTALL_DIR}/server/${SOLR_HOME}/${TEMPLATE_CONF}"
321+
322+
if [[ "${CORES_SETUP:-}" == "dedicated" || "${SOLR_CLOUD:-}" == "no" ]]; then
323+
# dedicated mode: point at the local core path
324+
ALLOW_URLS="localhost:${SOLR_PORT}/solr"
325+
elif [[ "${SOLR_CLOUD:-}" == "yes" && ${#default_nodes[@]} -gt 0 ]]; then
326+
# SolrCloud mode: join all nodes host:port
327+
ALLOW_URLS=$(IFS=,; echo "${default_nodes[*]}")
328+
else
329+
# fallback: single node without core-path
330+
ALLOW_URLS="localhost:${SOLR_PORT}"
331+
fi
332+
export ALLOW_URLS
333+
287334
$SCRIPT_DIR/../bin/generate-solr-config.sh \
288335
--solr-install-dir="${SOLR_INSTALL_DIR}" \
289336
--solr-version="${SOLR_VERSION}" \
290-
--destination-dir="${SOLR_INSTALL_DIR}/server/${SOLR_HOME}/template"
337+
--destination-dir="${SOLR_INSTALL_DIR}/server/${SOLR_HOME}/${TEMPLATE_CONF}"
291338
solr_run
292339
else
293340
solr_cloud_configure_nodes

.github/workflows/integration-tests.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
solr-version:
1818
- '7.7.3'
1919
- '8.11.2'
20+
- '9.8.1'
2021
cores-setup:
2122
- 'dedicated'
2223
- 'shared'

bin/generate-solr-config.sh

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ DESTINATION_DIR=.platform/configsets/solr8/conf
77
SOLR_VERSION=8.11.1
88
FORCE=false
99
SOLR_INSTALL_DIR=""
10+
ALLOW_URLS_CLI=""
1011

1112
show_help() {
1213
cat << EOF
@@ -65,6 +66,9 @@ for i in "$@"; do
6566
SOLR_INSTALL_DIR="${i#*=}"
6667
SOLR_INSTALL_DIR="${SOLR_INSTALL_DIR/#\~/$HOME}"
6768
;;
69+
--allow-urls)
70+
ALLOW_URLS_CLI="$2"; shift 2
71+
;;
6872
-h|--help)
6973
show_help
7074
exit 0
@@ -76,6 +80,7 @@ for i in "$@"; do
7680
esac
7781
done
7882

83+
: "${ALLOW_URLS_CLI:=${ALLOW_URLS:-}}"
7984

8085
if [ `whoami` == "root" ]; then
8186
echo "Error : Do not run this script as root"
@@ -112,8 +117,30 @@ cp -a ${EZ_BUNDLE_PATH}/src/lib/Resources/config/solr/* $DESTINATION_DIR
112117
cp ${SOLR_INSTALL_DIR}/server/solr/configsets/_default/conf/{solrconfig.xml,stopwords.txt,synonyms.txt} $DESTINATION_DIR
113118

114119
if [[ ! $DESTINATION_DIR =~ ^\.platform ]]; then
115-
# If we are not targeting .platform(.sh) config, we also output default solr.xml
116-
cp -f ${SOLR_INSTALL_DIR}/server/solr/solr.xml $DESTINATION_DIR/..
120+
121+
if [[ "${SOLR_VERSION}" =~ ^9\. ]]; then
122+
cp -f ${SOLR_INSTALL_DIR}/server/solr/solr.xml $DESTINATION_DIR/../..
123+
124+
URL_LIST="${ALLOW_URLS_CLI//,/ }"
125+
SOLR_XML_PATH="${DESTINATION_DIR}/../../solr.xml"
126+
127+
if [[ -f "$SOLR_XML_PATH" ]]; then
128+
# backup original
129+
cp "$SOLR_XML_PATH" "${SOLR_XML_PATH}.bak"
130+
# replace inner text of the allowUrls element
131+
sed -i \
132+
-e "s|\(<str name=\"allowUrls\">\)[^<]*\(<\/str>\)|\1${URL_LIST}\2|" \
133+
"$SOLR_XML_PATH"
134+
echo "NOTE: Updated <str name=\"allowUrls\"> to: ${URL_LIST}"
135+
else
136+
echo "WARNING: solr.xml not found at '$SOLR_XML_PATH'; skipping allowUrls patch"
137+
fi
138+
else
139+
# If we are not targeting .platform(.sh) config, we also output default solr.xml
140+
echo "Copying ${SOLR_INSTALL_DIR}/server/solr/solr.xml to $DESTINATION_DIR/.."
141+
142+
cp -f ${SOLR_INSTALL_DIR}/server/solr/solr.xml $DESTINATION_DIR/..
143+
fi
117144
else
118145
echo "NOTE: Skipped copying ${SOLR_INSTALL_DIR}/server/solr/solr.xml given destination dir is a '.platform/' config folder"
119146
fi

src/bundle/DependencyInjection/Configuration.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Configuration implements ConfigurationInterface
1414
{
1515
public const SOLR_HTTP_CLIENT_DEFAULT_TIMEOUT = 10;
1616
public const SOLR_HTTP_CLIENT_DEFAULT_MAX_RETRIES = 3;
17+
public const SOLR_DEFAULT_VERSION = '7.7.3';
1718

1819
protected $rootNodeName;
1920

@@ -105,6 +106,10 @@ protected function addEndpointsSection(ArrayNodeDefinition $node)
105106
protected function addConnectionsSection(ArrayNodeDefinition $node)
106107
{
107108
$node->children()
109+
->scalarNode('version')
110+
->info('Version of the Solr Search Engine to use')
111+
->defaultValue(self::SOLR_DEFAULT_VERSION)
112+
->end()
108113
->scalarNode('default_connection')
109114
->info('Name of the default connection')
110115
->end()

src/bundle/DependencyInjection/IbexaSolrExtension.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ protected function processConnectionConfiguration(ContainerBuilder $container, a
159159
);
160160
}
161161

162+
if (isset($config['version'])) {
163+
$container->setParameter(
164+
"{$alias}.version",
165+
$config['version']
166+
);
167+
}
168+
162169
foreach ($config['connections'] as $name => $params) {
163170
$this->configureSearchServices($container, $name, $params);
164171
$this->configureBoostMap($container, $name, $params);

src/bundle/Resources/config/services.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ parameters:
22
ibexa.solr.default_connection: ~
33
ibexa.solr.http_client.timeout: !php/const \Ibexa\Bundle\Solr\DependencyInjection\Configuration::SOLR_HTTP_CLIENT_DEFAULT_TIMEOUT
44
ibexa.solr.http_client.max_retries: !php/const \Ibexa\Bundle\Solr\DependencyInjection\Configuration::SOLR_HTTP_CLIENT_DEFAULT_MAX_RETRIES
5+
ibexa.solr.version: !php/const \Ibexa\Bundle\Solr\DependencyInjection\Configuration::SOLR_DEFAULT_VERSION
56

67
services:
78
ibexa.solr.http_client.retryable:

src/lib/Query/Common/CriterionVisitor/MapLocation/MapLocationDistanceRange.php

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,29 @@
1010
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Operator;
1111
use Ibexa\Contracts\Solr\Query\CriterionVisitor;
1212
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
13+
use Ibexa\Core\Search\Common\FieldNameResolver;
1314
use Ibexa\Solr\Query\Common\CriterionVisitor\MapLocation;
1415

1516
/**
1617
* Visits the MapLocationDistance criterion.
1718
*/
1819
class MapLocationDistanceRange extends MapLocation
1920
{
21+
private const MAX_EARTH_DISTANCE_KM = 63510;
22+
23+
private string $solrVersion;
24+
25+
public function __construct(
26+
FieldNameResolver $fieldNameResolver,
27+
$fieldTypeIdentifier,
28+
$fieldName,
29+
string $solrVersion
30+
) {
31+
parent::__construct($fieldNameResolver, $fieldTypeIdentifier, $fieldName);
32+
33+
$this->solrVersion = $solrVersion;
34+
}
35+
2036
/**
2137
* Check if visitor is applicable to current criterion.
2238
*
@@ -29,10 +45,10 @@ public function canVisit(Criterion $criterion)
2945
return
3046
$criterion instanceof Criterion\MapLocationDistance &&
3147
($criterion->operator === Operator::LT ||
32-
$criterion->operator === Operator::LTE ||
33-
$criterion->operator === Operator::GT ||
34-
$criterion->operator === Operator::GTE ||
35-
$criterion->operator === Operator::BETWEEN);
48+
$criterion->operator === Operator::LTE ||
49+
$criterion->operator === Operator::GT ||
50+
$criterion->operator === Operator::GTE ||
51+
$criterion->operator === Operator::BETWEEN);
3652
}
3753

3854
/**
@@ -47,10 +63,13 @@ public function canVisit(Criterion $criterion)
4763
*/
4864
public function visit(Criterion $criterion, CriterionVisitor $subVisitor = null)
4965
{
66+
if (!$this->isSolrInMaxVersion('9.3.0')) {
67+
return $this->visitForSolr9($criterion);
68+
}
5069
$criterion->value = (array)$criterion->value;
5170

5271
$start = $criterion->value[0];
53-
$end = isset($criterion->value[1]) ? $criterion->value[1] : 63510;
72+
$end = isset($criterion->value[1]) ? $criterion->value[1] : self::MAX_EARTH_DISTANCE_KM;
5473

5574
if (($criterion->operator === Operator::LT) ||
5675
($criterion->operator === Operator::LTE)) {
@@ -88,6 +107,66 @@ public function visit(Criterion $criterion, CriterionVisitor $subVisitor = null)
88107

89108
return '(' . implode(' OR ', $queries) . ')';
90109
}
91-
}
92110

93-
class_alias(MapLocationDistanceRange::class, 'EzSystems\EzPlatformSolrSearchEngine\Query\Common\CriterionVisitor\MapLocation\MapLocationDistanceRange');
111+
private function visitForSolr9(Criterion $criterion): string
112+
{
113+
if (is_array($criterion->value)) {
114+
$minDistance = $criterion->value[0];
115+
$maxDistance = $criterion->value[1] ?? self::MAX_EARTH_DISTANCE_KM;
116+
} else {
117+
$minDistance = 0;
118+
$maxDistance = $criterion->value;
119+
}
120+
121+
$sign = '';
122+
if (($criterion->operator === Operator::GT) ||
123+
($criterion->operator === Operator::GTE)) {
124+
$sign = '-';
125+
}
126+
127+
$searchFields = $this->getSearchFields(
128+
$criterion,
129+
$criterion->target,
130+
$this->fieldTypeIdentifier,
131+
$this->fieldName
132+
);
133+
134+
if (empty($searchFields)) {
135+
throw new InvalidArgumentException(
136+
'$criterion->target',
137+
"No searchable Fields found for the provided Criterion target '{$criterion->target}'."
138+
);
139+
}
140+
141+
/** @var \Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Value\MapLocationValue $location */
142+
$location = $criterion->valueData;
143+
144+
$queries = [];
145+
foreach ($searchFields as $name => $fieldType) {
146+
if ($criterion->operator === Operator::BETWEEN) {
147+
$query = sprintf(
148+
'{!geofilt sfield=%s pt=%F,%F d=%s} AND -{!geofilt sfield=%s pt=%F,%F d=%s}',
149+
$name,
150+
$location->latitude,
151+
$location->longitude,
152+
$maxDistance,
153+
$name,
154+
$location->latitude,
155+
$location->longitude,
156+
$minDistance
157+
);
158+
} else {
159+
$query = sprintf('%s{!geofilt sfield=%s pt=%F,%F d=%s}', $sign, $name, $location->latitude, $location->longitude, $maxDistance);
160+
}
161+
162+
$queries[] = "{$query} AND {$name}:[* TO *]";
163+
}
164+
165+
return '(' . implode(' OR ', $queries) . ')';
166+
}
167+
168+
private function isSolrInMaxVersion(string $maxVersion): bool
169+
{
170+
return version_compare($this->solrVersion, $maxVersion, '<');
171+
}
172+
}

src/lib/Resources/config/container/solr/criterion_visitors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ services:
115115
- '@Ibexa\Core\Search\Common\FieldNameResolver'
116116
- 'ezgmaplocation'
117117
- 'value_location'
118+
- '%ibexa.solr.version%'
118119
tags:
119120
- {name: ibexa.search.solr.query.content.criterion.visitor}
120121
- {name: ibexa.search.solr.query.location.criterion.visitor}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!--
2+
This is additional custom example fields. You can come up with similar
3+
fields on your own, if you require custom indexing / query rules.
4+
5+
Instead of using the type "text" you might even want to define a custom
6+
type. In the custom type you can define your dedicated index and query
7+
rules. You can copy any existing fields into the custom field as seen
8+
below.
9+
10+
In this case we copy the full user name and index it as a text field.
11+
-->
12+
<field name="custom_field" type="text" indexed="true" stored="false" required="false" multiValued="true" />
13+
<copyField source="user_first_name_value_s" dest="custom_field" />
14+
<copyField source="user_last_name_value_s" dest="custom_field" />
15+
16+
<field name="custom_geolocation_field" type="location" indexed="true" stored="false" required="false" />
17+
18+
<copyField source="testtype_maplocation_value_location_gl" dest="custom_geolocation_field" />

0 commit comments

Comments
 (0)