diff --git a/examples/multi-currency/README.md b/examples/multi-currency/README.md
new file mode 100644
index 000000000..445fb86eb
--- /dev/null
+++ b/examples/multi-currency/README.md
@@ -0,0 +1,106 @@
+# Multi-Currency Vespa Application
+
+This Vespa application demonstrates multi-currency price handling using global documents holding currency conversion rates to USD.
+Item prices are stored in their local currencies, but the app can hydrate and rank items based on their USD equivalent prices.
+Price range Filtering can be done by using native currency filtering.
+
+## Architecture
+
+The application consists of two document types:
+- `currency`: Global document storing currency conversion factors to USD
+- `item`: Items with prices in different currencies, referencing currency documents
+
+The `currency.xml` file contains conversion rates between 30 different currencies.
+
+## Setup
+
+1. **Start Vespa container**:
+ ```bash
+ vespa config set target local
+ docker pull vespaengine/vespa
+ docker run --detach --name vespa --hostname vespa-container \
+ --publish 127.0.0.1:8080:8080 --publish 127.0.0.1:19071:19071 \
+ vespaengine/vespa
+ ```
+
+2. **Wait for Vespa to be ready**:
+ ```bash
+ vespa status deploy --wait 300
+ ```
+
+3. **Deploy the application**:
+ ```bash
+ vespa deploy --wait 300
+ ```
+
+## Loading Data
+
+1. **Feed currency conversion factors**:
+
+Convert the `currency.xml` file to Vespa documents with conversion factors to USD.:
+```bash
+python3 currency_xml_to_vespa_docs.py | vespa feed -
+```
+
+The documents look like:
+```jsonl
+{"put": "id:mynamespace:currency::usd", "fields": {"factor": 1.0}}
+{"put": "id:mynamespace:currency::aud", "fields": {"factor": 0.67884054}}
+{"put": "id:mynamespace:currency::cad", "fields": {"factor": 0.76028283}}
+{"put": "id:mynamespace:currency::cny", "fields": {"factor": 0.1442157}}
+```
+
+2. **Feed items with currency references**:
+
+Feed the sample documents:
+
+```bash
+vespa feed items.jsonl
+```
+
+The item documents look like:
+
+```bash
+{"put": "id:shopping:item::item-1", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 3836, "item_name": "emerald gemstone bracelet"}}
+{"put": "id:shopping:item::item-2", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 14, "item_name": "Handmade ceramic ring dish"}}
+{"put": "id:shopping:item::item-3", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 45, "item_name": "Handmade wooden cutting board"}}
+```
+
+## Querying
+
+1. **View all documents**:
+ ```bash
+ vespa visit
+ ```
+
+2. **Query items with currency-based price filtering**:
+```bash
+vespa query 'select * from item where (currency_ref matches "id:shopping:currency::usd" and price >= 4000.0)'
+```
+
+3. **Filter by all currencies within a range**:
+ ```bash
+ vespa query yql="select * from item where $(python3 generate_price_filter_query.py --min_price 20 --max_price 100 --currency USD)"
+ ```
+4. **Combine currency filtering with ranking using USD price**:
+ ```bash
+ vespa query yql="select * from item where userQuery() AND ($(python3 generate_price_filter_query.py --min_price 20 --max_price 100 --currency USD))" query="vintage"
+ ```
+
+
+## Key Features
+
+- **Global currency documents**: Currency data is replicated across all content nodes
+- **Cross-document field import**: Items can access currency factors via `currency_ref.factor`
+- **USD price calculation**: Rank profile computes `price_usd: price * currency_factor`
+
+## Schema Details
+
+### Currency Schema
+- `factor`: Double field representing conversion rate to USD
+
+### Item Schema
+- `item_name`: String field for item description
+- `price`: Double field for price in local currency
+- `currency_ref`: Reference to currency document
+- Imported field: `currency_factor` from referenced currency document
diff --git a/examples/multi-currency/currency.xml b/examples/multi-currency/currency.xml
new file mode 100644
index 000000000..5c089ed83
--- /dev/null
+++ b/examples/multi-currency/currency.xml
@@ -0,0 +1,908 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/multi-currency/currency_rates.jsonl b/examples/multi-currency/currency_rates.jsonl
new file mode 100644
index 000000000..14b37763f
--- /dev/null
+++ b/examples/multi-currency/currency_rates.jsonl
@@ -0,0 +1,30 @@
+{"put": "id:mynamespace:currency::usd", "fields": {"factor": 1.0}}
+{"put": "id:mynamespace:currency::aud", "fields": {"factor": 0.67884054}}
+{"put": "id:mynamespace:currency::cad", "fields": {"factor": 0.76028283}}
+{"put": "id:mynamespace:currency::cny", "fields": {"factor": 0.1442157}}
+{"put": "id:mynamespace:currency::czk", "fields": {"factor": 0.04914198}}
+{"put": "id:mynamespace:currency::dkk", "fields": {"factor": 0.16289298}}
+{"put": "id:mynamespace:currency::hkd", "fields": {"factor": 0.13271224}}
+{"put": "id:mynamespace:currency::huf", "fields": {"factor": 0.00303453}}
+{"put": "id:mynamespace:currency::inr", "fields": {"factor": 0.0120344}}
+{"put": "id:mynamespace:currency::idr", "fields": {"factor": 6.354e-05}}
+{"put": "id:mynamespace:currency::ils", "fields": {"factor": 0.30717248}}
+{"put": "id:mynamespace:currency::jpy", "fields": {"factor": 0.00720471}}
+{"put": "id:mynamespace:currency::myr", "fields": {"factor": 0.24654832}}
+{"put": "id:mynamespace:currency::mxn", "fields": {"factor": 0.055135}}
+{"put": "id:mynamespace:currency::mad", "fields": {"factor": 0.11377527}}
+{"put": "id:mynamespace:currency::nzd", "fields": {"factor": 0.62968327}}
+{"put": "id:mynamespace:currency::nok", "fields": {"factor": 0.10324712}}
+{"put": "id:mynamespace:currency::php", "fields": {"factor": 0.01839195}}
+{"put": "id:mynamespace:currency::sgd", "fields": {"factor": 0.81559416}}
+{"put": "id:mynamespace:currency::vnd", "fields": {"factor": 3.957e-05}}
+{"put": "id:mynamespace:currency::zar", "fields": {"factor": 0.05826327}}
+{"put": "id:mynamespace:currency::sek", "fields": {"factor": 0.11001705}}
+{"put": "id:mynamespace:currency::chf", "fields": {"factor": 1.29600829}}
+{"put": "id:mynamespace:currency::thb", "fields": {"factor": 0.03207987}}
+{"put": "id:mynamespace:currency::gbp", "fields": {"factor": 1.42450142}}
+{"put": "id:mynamespace:currency::twd", "fields": {"factor": 0.03562586}}
+{"put": "id:mynamespace:currency::try", "fields": {"factor": 0.02602804}}
+{"put": "id:mynamespace:currency::eur", "fields": {"factor": 1.21521449}}
+{"put": "id:mynamespace:currency::pln", "fields": {"factor": 0.28613941}}
+{"put": "id:mynamespace:currency::brl", "fields": {"factor": 0.18918612}}
diff --git a/examples/multi-currency/currency_xml_to_vespa_docs.py b/examples/multi-currency/currency_xml_to_vespa_docs.py
new file mode 100644
index 000000000..9a30898af
--- /dev/null
+++ b/examples/multi-currency/currency_xml_to_vespa_docs.py
@@ -0,0 +1,50 @@
+import sys
+import xml.etree.ElementTree as ET
+import json
+
+
+def parse_currencies(root) -> set[str]:
+ currencies = []
+ for currency in root.findall('.//currency'):
+ currencies.append(currency.get('code'))
+
+ return sorted(currencies)
+
+
+def convert_currency_xml_to_vespa_jsonl(xml_file) -> list[str]:
+ # Parse the XML file
+ tree = ET.parse(xml_file)
+ root = tree.getroot()
+
+ currencies = parse_currencies(root)
+
+ # Add USD to USD conversion (factor = 1.0)
+ usd_doc = {
+ "put": "id:shopping:currency::usd",
+ "fields": {"factor": 1.0}
+ }
+ currency_rates = [json.dumps(usd_doc) + '\n']
+
+ # Find all rate elements where 'to' attribute is 'USD'
+ for rate in root.findall('.//rate[@to="USD"]'):
+ currency = rate.get('from').lower()
+ factor = float(rate.get('rate'))
+
+ # Create Vespa document
+ doc = {
+ "put": f"id:shopping:currency::{currency}",
+ "fields": {
+ "code": currency,
+ "idx": currencies.index(currency.upper()),
+ "factor": factor,
+ }
+ }
+
+ currency_rates.append(json.dumps(doc))
+
+ return currency_rates
+
+# Usage
+if __name__ == "__main__":
+ currency_docs = convert_currency_xml_to_vespa_jsonl('currency.xml')
+ sys.stdout.write("\n".join(currency_docs) + "\n")
\ No newline at end of file
diff --git a/examples/multi-currency/filtering_perf_benchmark.py b/examples/multi-currency/filtering_perf_benchmark.py
new file mode 100644
index 000000000..f424e27ab
--- /dev/null
+++ b/examples/multi-currency/filtering_perf_benchmark.py
@@ -0,0 +1,219 @@
+# Benchmark filtering native with a single price_usd filter vs. the dynamic OR across multiple currencies.
+# If the dynamic OR is fast enough, using the global currency document and runtime conversions is preferable,
+# as it removes the need to update price_usd in all non-USD items for every currency conversion rate change.
+
+# - delete all Vespa documents.
+# - feed the currency conversion documents.
+# - generate and save 1mm documents with both native and usd prices.
+# - feed those docs into vespa
+# - generate and save 5,000 random queries with price filters in various currencies and random keywords.
+# - run the queries with both native and usd price filters.
+# - compare the results and timings.
+
+
+# filtering_perf_benchmark.py
+# python3 filtering_perf_benchmark.py --help to see options
+import numpy as np
+import json
+import random
+import subprocess
+import time
+from pathlib import Path
+
+from vespa.application import Vespa
+from vespa.io import VespaQueryResponse
+
+from generate_price_filter_query import load_conversion_rates
+
+
+RATE_TABLE, _ = load_conversion_rates("currency.xml")
+
+def pct(values: list[float], *percents: int) -> list[float]:
+ """
+ Returns the requested percentiles from the input list.
+
+ pct([1, 2, 3, 4], 25, 50, 75) -> [1.75, 2.5, 3.25]
+ """
+ arr = np.asarray(values, dtype=np.float64)
+ # numpy >= 1.22: use method instead of interpolation
+ return np.percentile(arr, percents, method="linear").tolist()
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+def _run_cli(cmd: list[str], stdin: bytes | None = None) -> str:
+ """
+ Runs a Vespa CLI command and returns stdout.
+ Raises `subprocess.CalledProcessError` on failure.
+ """
+ res = subprocess.run(cmd, input=stdin, check=True, text=True, stdout=subprocess.PIPE)
+ return res.stdout
+
+
+def _feed_jsonl(lines: list[str]) -> None:
+ """
+ Feeds JSONL documents using `vespa feed -`.
+ """
+ data = ("\n".join(lines) + "\n").encode()
+ _run_cli(["vespa", "feed", "-"], stdin=data)
+
+def feed_currency_documents(factors: dict[str, float], ns: str) -> None:
+ """
+ Feeds `currency` documents with conversion factors.
+ """
+ jsonl = [
+ json.dumps({"put": f"id:{ns}:currency::{code.lower()}",
+ "fields": {"factor": factor}})
+ for code, factor in factors.items()
+ ]
+ _feed_jsonl(jsonl)
+
+# Price buckets for random price generation.
+BUCKETS: list[tuple[int,int]] = [(0,5), (5,10), (10,20), (20,40), (40,80), (80,150), (150,300), (300,600), (600,1000), (1000,10000)]
+
+def random_price_cents() -> int:
+ bucket_idx = random.randint(0, len(BUCKETS) - 1)
+ return random.randint(BUCKETS[bucket_idx][0] * 100, BUCKETS[bucket_idx][1] * 100)
+
+
+CURRENCY_PROBS = {
+ "USD": 0.70304,
+ "EUR": 0.11294,
+ "GBP": 0.10312,
+ "CAD": 0.03630,
+ "AUD": 0.02066,
+ "TRY": 0.00474,
+ "INR": 0.00286,
+ "PHP": 0.00197,
+ "VND": 0.00174,
+ "HKD": 0.00167,
+ "SEK": 0.00125,
+ "IDR": 0.00115,
+ "NZD": 0.00113,
+ "CHF": 0.00110,
+ "ILS": 0.00109,
+ "MAD": 0.00103,
+ "SGD": 0.00103,
+ "MYR": 0.00077,
+ "ZAR": 0.00074,
+ "MXN": 0.00062,
+ "DKK": 0.00060,
+ "NOK": 0.00044,
+ "TWD": 0.00001,
+ "PLN": 0.00001,
+ "THB": 0.00001,
+ "JPY": 0.00001,
+ "CZK": 0.00001,
+ "CNY": 0.00001,
+}
+
+# ----------------------------------------------------------------------
+def random_currency() -> str:
+ currencies = list(CURRENCY_PROBS.keys())
+ weights = list(CURRENCY_PROBS.values())
+ return random.choices(currencies, weights=weights, k=1)[0]
+
+tokens = [f"token{i}" for i in range(1, 1001)] # Example tokens for item titles
+def generate_items():
+ """
+ Generates a list of item documents with random prices and currency references.
+ This is a placeholder function; replace with actual item generation logic.
+ """
+ items = []
+ for i in range(1, 1_000_001):
+ price_usd = random_price_cents()
+ currency = random_currency()
+ price_native = RATE_TABLE[("USD", currency.upper())] * price_usd # Convert to the target currency
+ item = {
+ "put": f"id:shopping:item::item-{i}",
+ "fields": {
+ "currency_ref": f"id:shopping:currency::{currency.lower()}",
+ "price_usd": price_usd,
+ "price": price_native,
+ }
+ }
+ if currency.lower() != "usd":
+ item["fields"][f"price_{currency.lower()}"] = price_native
+
+ items.append(json.dumps(item))
+ return items
+
+
+def vespa_feed(jsonl_file: Path) -> None:
+ _run_cli(["vespa", "feed", str(jsonl_file)])
+
+
+if __name__ == "__main__":
+ reindex = True
+ if reindex:
+ docker_feed = Vespa(url="localhost", port=8080)
+ _ = docker_feed.delete_all_docs(content_cluster_name="shopping", schema='currency')
+ _ = docker_feed.delete_all_docs(content_cluster_name="shopping", schema='item')
+
+ from currency_xml_to_vespa_docs import convert_currency_xml_to_vespa_jsonl
+ currency_docs = convert_currency_xml_to_vespa_jsonl('currency.xml')
+ currency_docs_file = Path("currency_docs.jsonl")
+ currency_docs_file.write_text("\n".join(currency_docs) + "\n")
+ vespa_feed(currency_docs_file)
+
+ # generate 1mm sample items with random prices, item_name, and currency.
+ items = generate_items()
+ items_file = Path("1mm_items.jsonl")
+ items_file.write_text("\n".join(items) + "\n")
+ vespa_feed(items_file)
+
+ app = Vespa(url="http://localhost", port=8080)
+
+ from generate_price_filter_query import generate_price_filter_query
+
+ # record the latency for both queries
+ lat_multi: list[float] = []
+ lat_single: list[float] = []
+
+ for i in range(1, 1000):
+ prices = [random_price_cents() for _ in range(2)]
+ price_usd_min = min(prices)
+ price_usd_max = max(prices)
+ currency = random_currency()
+ rate = RATE_TABLE[("USD", currency.upper())]
+ min_price = rate * price_usd_min
+ max_price = rate * price_usd_max
+
+ multi_currency_where=f"select * from item where {generate_price_filter_query(min_price, max_price, currency.lower())}"
+ single_currency_where=f"select * from item where price_usd >= {price_usd_min} and price_usd <= {price_usd_max}"
+
+ exec_plan = [
+ ("multi", multi_currency_where, lat_multi),
+ ("single", single_currency_where, lat_single),
+ ]
+ random.shuffle(exec_plan)
+
+ # run queries in randomized order
+ for tag, query_str, lat_list in exec_plan:
+ start = time.perf_counter()
+ r: VespaQueryResponse = app.query({
+ "yql": query_str,
+ "ranking": "unranked",
+ "hits": 0,
+ })
+ latency = time.perf_counter() - start
+ lat_list.append(latency)
+ results: int = r.number_documents_retrieved
+
+ if tag == "multi":
+ multi_currency_results = results
+ else:
+ single_currency_results = results
+
+ #if multi_currency_results != single_currency_results:
+ # print(f"Latency: multi={lat_multi[-1]} single={lat_single[-1]}. Total hits mismatch: {multi_currency_results} vs {single_currency_results} currency:{currency}, min_price: {min_price}, max_price: {max_price} price_usd_min: {price_usd_min}, price_usd_max: {price_usd_max} rate: {rate}")
+ #else:
+ # print(f"Latency: multi={lat_multi[-1]} single={lat_single[-1]}. Total hits: {multi_currency_results} currency:{currency}, min_price: {min_price}, max_price: {max_price} price_usd_min: {price_usd_min}, price_usd_max: {price_usd_max} rate: {rate}")
+
+ print(f"latency for multi-currency query: {pct(lat_multi, [25, 50, 75, 90, 95, 99])}")
+ print(f"latency for price_usd query: {pct(lat_single, [25, 50, 75, 90, 95, 99])}")
+
+
+
+
diff --git a/examples/multi-currency/generate_price_filter_query.py b/examples/multi-currency/generate_price_filter_query.py
new file mode 100644
index 000000000..e04093e43
--- /dev/null
+++ b/examples/multi-currency/generate_price_filter_query.py
@@ -0,0 +1,96 @@
+import sys
+import argparse
+import xml.etree.ElementTree as ET
+
+
+def load_conversion_rates(xml_file: str) -> tuple[dict[tuple[str, str], float], set[str]]:
+ """
+ Parses the currency XML file and builds a conversion rate table.
+ Returns a dictionary of rates and a sorted list of all currencies.
+ """
+ try:
+ tree = ET.parse(xml_file)
+ root = tree.getroot()
+ except (ET.ParseError, FileNotFoundError) as e:
+ print(f"Error: Could not read or parse the currency XML file '{xml_file}'.\n{e}", file=sys.stderr)
+ sys.exit(1)
+
+ rates: dict[tuple[str, str], float] = {}
+ all_currencies: set[str] = set()
+
+ for rate_element in root.findall('.//rate'):
+ from_curr: str = rate_element.get('from').upper()
+ to_curr: str = rate_element.get('to').upper()
+ rate_value: float = float(rate_element.get('rate'))
+ rates[(from_curr, to_curr)] = rate_value
+ all_currencies.add(from_curr)
+ all_currencies.add(to_curr)
+
+ all_currencies = sorted(all_currencies)
+
+ for currency in all_currencies:
+ rates[(currency, currency)] = 1.0
+
+ return rates, all_currencies
+
+
+rates, all_currencies = load_conversion_rates("currency.xml")
+
+
+def currency_idx(currency: str) -> int:
+ return all_currencies.index(currency.upper())
+
+
+def price_filter(currency: str, min_price: float, max_price: float) -> str:
+ # This is fast, too. Only ~2x slower than one price_usd query
+ return f"(currency_idx = {currency_idx(currency)} and price >= {min_price} and price <= {max_price})"
+
+ # 'matches' is roughly 80ms slower. How to speed this up?
+ # return f"(currency_ref matches \"id:shopping:currency::{currency.lower()}\" and price >= {min_price} and price <= {max_price})"
+
+ # TODO: how can we get exact matches for currency_code to work?
+ # return f"(currency_code = \"{currency.lower()}\" and price >= {min_price} and price <= {max_price})"
+
+ # using a field for each currency is fast. 1.75x the price_usd query
+ # return f"(price_{currency.lower()} >= {min_price} and price_{currency.lower()} <= {max_price})"
+
+
+def generate_price_filter_query(min_price: float, max_price: float, currency: str) -> str:
+ if min_price > max_price:
+ raise ValueError("min_price cannot be greater than max_price.")
+
+ source_currency: str = currency.upper()
+
+ or_conditions: list[str] = []
+ for target_currency in all_currencies:
+ if (source_currency, target_currency) in rates:
+ rate: float = rates[(source_currency, target_currency)]
+ converted_min: float = min_price * rate
+ converted_max: float = max_price * rate
+
+ or_conditions.append(price_filter(target_currency, converted_min, converted_max))
+ else:
+ print(f"Warning: No conversion rate from {source_currency} to {target_currency}. Skipping.",
+ file=sys.stderr)
+
+ return " or ".join(or_conditions)
+
+
+def main() -> None:
+ """
+ Main function to generate the Vespa query.
+ """
+ parser = argparse.ArgumentParser(
+ description="Generate a Vespa query to filter by price across multiple currencies."
+ )
+ parser.add_argument('--min_price', type=float, required=True, help='Minimum price.')
+ parser.add_argument('--max_price', type=float, required=True, help='Maximum price.')
+ parser.add_argument('--currency', type=str, required=True,
+ help='The currency for the given min/max price (e.g., USD).')
+
+ args = parser.parse_args()
+ print(generate_price_filter_query(args.min_price, args.max_price, args.currency))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/multi-currency/items.jsonl b/examples/multi-currency/items.jsonl
new file mode 100644
index 000000000..5a2f96ed4
--- /dev/null
+++ b/examples/multi-currency/items.jsonl
@@ -0,0 +1,100 @@
+{"put": "id:shopping:item::item-1", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 3836, "item_name": "emerald gemstone bracelet"}}
+{"put": "id:shopping:item::item-2", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 14, "item_name": "Handmade ceramic ring dish"}}
+{"put": "id:shopping:item::item-3", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 45, "item_name": "Handmade wooden cutting board"}}
+{"put": "id:shopping:item::item-4", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 34, "item_name": "Personalized wooden cutting board"}}
+{"put": "id:shopping:item::item-5", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 7, "item_name": "Vintage letterpress greeting card"}}
+{"put": "id:shopping:item::item-6", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 4172, "item_name": "14k gold hammered ring"}}
+{"put": "id:shopping:item::item-7", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 1664, "item_name": "diamond engagement ring"}}
+{"put": "id:shopping:item::item-8", "fields": {"currency_ref": "id:shopping:currency::eur", "price": 4027, "item_name": "sapphire drop earrings"}}
+{"put": "id:shopping:item::item-9", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 21, "item_name": "Handmade knitted scarf"}}
+{"put": "id:shopping:item::item-10", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 55, "item_name": "Personalized letterpress greeting card"}}
+{"put": "id:shopping:item::item-11", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 1, "item_name": "Minimalist beaded necklace"}}
+{"put": "id:shopping:item::item-12", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 24, "item_name": "Handcrafted hand stamped necklace"}}
+{"put": "id:shopping:item::item-13", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 49, "item_name": "Artisan crochet baby blanket"}}
+{"put": "id:shopping:item::item-14", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 17, "item_name": "Handcrafted macrame wall hanging"}}
+{"put": "id:shopping:item::item-15", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 20, "item_name": "Vintage crochet baby blanket"}}
+{"put": "id:shopping:item::item-16", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 29, "item_name": "Minimalist personalized leather wallet"}}
+{"put": "id:shopping:item::item-17", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 4241, "item_name": "custom engagement ring"}}
+{"put": "id:shopping:item::item-18", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 52, "item_name": "Personalized handmade pottery bowl"}}
+{"put": "id:shopping:item::item-19", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 20, "item_name": "Handmade resin keychain"}}
+{"put": "id:shopping:item::item-20", "fields": {"currency_ref": "id:shopping:currency::myr", "price": 23, "item_name": "Handcrafted macrame wall hanging"}}
+{"put": "id:shopping:item::item-21", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 23, "item_name": "Personalized personalized leather wallet"}}
+{"put": "id:shopping:item::item-22", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 63, "item_name": "Handcrafted geode coaster set"}}
+{"put": "id:shopping:item::item-23", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 63, "item_name": "Handmade leather journal"}}
+{"put": "id:shopping:item::item-24", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 3383, "item_name": "18k gold hoop earrings"}}
+{"put": "id:shopping:item::item-25", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 45, "item_name": "Rustic beaded necklace"}}
+{"put": "id:shopping:item::item-26", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 3408, "item_name": "18k gold hoop earrings"}}
+{"put": "id:shopping:item::item-27", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 36, "item_name": "Handmade personalized leather wallet"}}
+{"put": "id:shopping:item::item-28", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 41, "item_name": "Rustic beaded necklace"}}
+{"put": "id:shopping:item::item-29", "fields": {"currency_ref": "id:shopping:currency::php", "price": 52, "item_name": "Eco-friendly macrame wall hanging"}}
+{"put": "id:shopping:item::item-30", "fields": {"currency_ref": "id:shopping:currency::eur", "price": 16, "item_name": "Minimalist beaded necklace"}}
+{"put": "id:shopping:item::item-31", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 48, "item_name": "Rustic ceramic mug"}}
+{"put": "id:shopping:item::item-32", "fields": {"currency_ref": "id:shopping:currency::eur", "price": 11, "item_name": "Minimalist embroidered tea towel"}}
+{"put": "id:shopping:item::item-33", "fields": {"currency_ref": "id:shopping:currency::eur", "price": 20, "item_name": "Handmade polymer clay earrings"}}
+{"put": "id:shopping:item::item-34", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 44, "item_name": "Boho ceramic ring dish"}}
+{"put": "id:shopping:item::item-35", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 9, "item_name": "Minimalist polymer clay earrings"}}
+{"put": "id:shopping:item::item-36", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 28, "item_name": "Artisan ceramic ring dish"}}
+{"put": "id:shopping:item::item-37", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 17, "item_name": "Eco-friendly beaded necklace"}}
+{"put": "id:shopping:item::item-38", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 26, "item_name": "Eco-friendly knitted scarf"}}
+{"put": "id:shopping:item::item-39", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 1, "item_name": "Custom polymer clay earrings"}}
+{"put": "id:shopping:item::item-40", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 3, "item_name": "Handcrafted leather journal"}}
+{"put": "id:shopping:item::item-41", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 20, "item_name": "Minimalist embroidered tea towel"}}
+{"put": "id:shopping:item::item-42", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 44, "item_name": "Boho polymer clay earrings"}}
+{"put": "id:shopping:item::item-43", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 27, "item_name": "Boho ceramic ring dish"}}
+{"put": "id:shopping:item::item-44", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 30, "item_name": "Personalized polymer clay earrings"}}
+{"put": "id:shopping:item::item-45", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 40, "item_name": "Handmade hand-poured soap"}}
+{"put": "id:shopping:item::item-46", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 17, "item_name": "Artisan felted wool slippers"}}
+{"put": "id:shopping:item::item-47", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 34, "item_name": "Custom personalized leather wallet"}}
+{"put": "id:shopping:item::item-48", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 18, "item_name": "Eco-friendly ceramic ring dish"}}
+{"put": "id:shopping:item::item-49", "fields": {"currency_ref": "id:shopping:currency::eur", "price": 4, "item_name": "Artisan crochet baby blanket"}}
+{"put": "id:shopping:item::item-50", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 24, "item_name": "Handcrafted leather journal"}}
+{"put": "id:shopping:item::item-51", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 4550, "item_name": "emerald gemstone bracelet"}}
+{"put": "id:shopping:item::item-52", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 39, "item_name": "Handmade resin keychain"}}
+{"put": "id:shopping:item::item-53", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 35, "item_name": "Rustic felted wool slippers"}}
+{"put": "id:shopping:item::item-54", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 30, "item_name": "Minimalist hand-poured soap"}}
+{"put": "id:shopping:item::item-55", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 66, "item_name": "Handmade hand stamped necklace"}}
+{"put": "id:shopping:item::item-56", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 36, "item_name": "Custom personalized leather wallet"}}
+{"put": "id:shopping:item::item-57", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 2924, "item_name": "14k gold hammered ring"}}
+{"put": "id:shopping:item::item-58", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 45, "item_name": "Artisan felted wool slippers"}}
+{"put": "id:shopping:item::item-59", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 64, "item_name": "Artisan ceramic ring dish"}}
+{"put": "id:shopping:item::item-60", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 11, "item_name": "Minimalist embroidered tea towel"}}
+{"put": "id:shopping:item::item-61", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 14, "item_name": "Eco-friendly handmade pottery bowl"}}
+{"put": "id:shopping:item::item-62", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 56, "item_name": "Eco-friendly polymer clay earrings"}}
+{"put": "id:shopping:item::item-63", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 18, "item_name": "Minimalist macrame wall hanging"}}
+{"put": "id:shopping:item::item-64", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 4566, "item_name": "18k gold hoop earrings"}}
+{"put": "id:shopping:item::item-65", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 30, "item_name": "Eco-friendly personalized leather wallet"}}
+{"put": "id:shopping:item::item-66", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 46, "item_name": "Personalized letterpress greeting card"}}
+{"put": "id:shopping:item::item-67", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 43, "item_name": "Artisan knitted scarf"}}
+{"put": "id:shopping:item::item-68", "fields": {"currency_ref": "id:shopping:currency::eur", "price": 56, "item_name": "Handmade felted wool slippers"}}
+{"put": "id:shopping:item::item-69", "fields": {"currency_ref": "id:shopping:currency::eur", "price": 52, "item_name": "Vintage ceramic succulent planter"}}
+{"put": "id:shopping:item::item-70", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 46, "item_name": "Minimalist hand stamped necklace"}}
+{"put": "id:shopping:item::item-71", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 1849, "item_name": "diamond engagement ring"}}
+{"put": "id:shopping:item::item-72", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 18, "item_name": "Handcrafted hand-poured soap"}}
+{"put": "id:shopping:item::item-73", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 42, "item_name": "Eco-friendly embroidered tea towel"}}
+{"put": "id:shopping:item::item-74", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 4126, "item_name": "sapphire drop earrings"}}
+{"put": "id:shopping:item::item-75", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 35, "item_name": "Personalized polymer clay earrings"}}
+{"put": "id:shopping:item::item-76", "fields": {"currency_ref": "id:shopping:currency::cad", "price": 3714, "item_name": "custom engagement ring"}}
+{"put": "id:shopping:item::item-77", "fields": {"currency_ref": "id:shopping:currency::eur", "price": 10, "item_name": "Handmade handmade pottery bowl"}}
+{"put": "id:shopping:item::item-78", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 51, "item_name": "Handcrafted resin keychain"}}
+{"put": "id:shopping:item::item-79", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 42, "item_name": "Personalized embroidered tea towel"}}
+{"put": "id:shopping:item::item-80", "fields": {"currency_ref": "id:shopping:currency::aud", "price": 31, "item_name": "Personalized ceramic mug"}}
+{"put": "id:shopping:item::item-81", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 3876, "item_name": "18k gold hoop earrings"}}
+{"put": "id:shopping:item::item-82", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 2519, "item_name": "18k gold hoop earrings"}}
+{"put": "id:shopping:item::item-83", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 27, "item_name": "Boho wooden cutting board"}}
+{"put": "id:shopping:item::item-84", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 12, "item_name": "Minimalist hand-poured soap"}}
+{"put": "id:shopping:item::item-85", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 33, "item_name": "Custom geode coaster set"}}
+{"put": "id:shopping:item::item-86", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 37, "item_name": "Rustic ceramic ring dish"}}
+{"put": "id:shopping:item::item-87", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 18, "item_name": "Eco-friendly macrame wall hanging"}}
+{"put": "id:shopping:item::item-88", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 47, "item_name": "Vintage leather journal"}}
+{"put": "id:shopping:item::item-89", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 14, "item_name": "Minimalist personalized leather wallet"}}
+{"put": "id:shopping:item::item-90", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 1, "item_name": "Minimalist knitted scarf"}}
+{"put": "id:shopping:item::item-91", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 46, "item_name": "Handmade soy wax candle"}}
+{"put": "id:shopping:item::item-92", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 24, "item_name": "Vintage handmade pottery bowl"}}
+{"put": "id:shopping:item::item-93", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 28, "item_name": "Personalized ceramic ring dish"}}
+{"put": "id:shopping:item::item-94", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 17, "item_name": "Eco-friendly leather journal"}}
+{"put": "id:shopping:item::item-95", "fields": {"currency_ref": "id:shopping:currency::gbp", "price": 1207, "item_name": "emerald gemstone bracelet"}}
+{"put": "id:shopping:item::item-96", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 6, "item_name": "Eco-friendly hand stamped necklace"}}
+{"put": "id:shopping:item::item-97", "fields": {"currency_ref": "id:shopping:currency::cad", "price": 49, "item_name": "Handmade macrame wall hanging"}}
+{"put": "id:shopping:item::item-98", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 20, "item_name": "Vintage leather journal"}}
+{"put": "id:shopping:item::item-99", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 55, "item_name": "Personalized embroidered tea towel"}}
+{"put": "id:shopping:item::item-100", "fields": {"currency_ref": "id:shopping:currency::usd", "price": 42, "item_name": "Custom ceramic mug"}}
diff --git a/examples/multi-currency/requirements.txt b/examples/multi-currency/requirements.txt
new file mode 100644
index 000000000..c88f22c81
--- /dev/null
+++ b/examples/multi-currency/requirements.txt
@@ -0,0 +1,2 @@
+numpy
+pyvespa
\ No newline at end of file
diff --git a/examples/multi-currency/schemas/currency.sd b/examples/multi-currency/schemas/currency.sd
new file mode 100644
index 000000000..cc841c12f
--- /dev/null
+++ b/examples/multi-currency/schemas/currency.sd
@@ -0,0 +1,21 @@
+schema currency {
+ document currency {
+ field idx type byte {
+ indexing: summary | attribute
+ attribute: fast-search
+ rank: filter
+ }
+
+ field code type string {
+ indexing: attribute | summary
+ attribute: fast-search
+ }
+
+ # multiplier for converting to USD
+ field factor type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+ }
+}
diff --git a/examples/multi-currency/schemas/item.sd b/examples/multi-currency/schemas/item.sd
new file mode 100644
index 000000000..03ba08965
--- /dev/null
+++ b/examples/multi-currency/schemas/item.sd
@@ -0,0 +1,224 @@
+schema item {
+ document item {
+ field item_name type string {
+ indexing: index | summary
+ match: text
+ index: enable-bm25
+ }
+ field price type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+ field price_usd type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+ field price_aud type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_brl type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_cad type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_chf type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_cny type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_czk type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_dkk type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_eur type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_gbp type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_hkd type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_huf type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_idr type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_ils type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_inr type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_jpy type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_mad type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_mxn type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_myr type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_nok type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_nzd type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_php type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_pln type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_sgd type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_sek type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_thb type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_try type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_twd type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_vnd type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field price_zar type double {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+
+ field currency_ref type reference {
+ indexing: attribute | summary
+ attribute: fast-search
+ rank: filter
+ }
+ }
+ import field currency_ref.code as currency_code {}
+ import field currency_ref.idx as currency_idx {}
+ import field currency_ref.factor as currency_factor {}
+
+ fieldset default {
+ fields: item_name
+ }
+
+ rank-profile default {
+ function price_usd() {
+ expression: attribute(price) * attribute(currency_factor)
+ }
+ first-phase {
+ expression: bm25(item_name) * log(price_usd)
+ }
+ summary-features {
+ attribute(price)
+ price_usd
+ bm25(item_name)
+ }
+ }
+
+ document-summary default {
+ summary item_name {}
+ summary currency_factor {}
+ }
+}
\ No newline at end of file
diff --git a/examples/multi-currency/services.xml b/examples/multi-currency/services.xml
new file mode 100644
index 000000000..ca391d5c6
--- /dev/null
+++ b/examples/multi-currency/services.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+