...
This commit is contained in:
@@ -70,6 +70,15 @@ MULTI/EXEC/DISCARD | ✅ | ❌ | Only supported in redb |
|
|||||||
**Encryption** | | | |
|
**Encryption** | | | |
|
||||||
Data-at-rest encryption | ✅ | ✅ | Both support [age](age.tech) encryption |
|
Data-at-rest encryption | ✅ | ✅ | Both support [age](age.tech) encryption |
|
||||||
AGE commands | ✅ | ✅ | Both support AGE crypto commands |
|
AGE commands | ✅ | ✅ | Both support AGE crypto commands |
|
||||||
|
**Full-Text Search** | | | |
|
||||||
|
FT.CREATE | ✅ | ✅ | Create search index with schema |
|
||||||
|
FT.ADD | ✅ | ✅ | Add document to search index |
|
||||||
|
FT.SEARCH | ✅ | ✅ | Search documents with query |
|
||||||
|
FT.DEL | ✅ | ✅ | Delete document from index |
|
||||||
|
FT.INFO | ✅ | ✅ | Get index information |
|
||||||
|
FT.DROP | ✅ | ✅ | Drop search index |
|
||||||
|
FT.ALTER | ✅ | ✅ | Alter index schema |
|
||||||
|
FT.AGGREGATE | ✅ | ✅ | Aggregate search results |
|
||||||
|
|
||||||
### Performance Considerations
|
### Performance Considerations
|
||||||
|
|
||||||
|
397
herodb/docs/search.md
Normal file
397
herodb/docs/search.md
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
# Full-Text Search with Tantivy
|
||||||
|
|
||||||
|
HeroDB includes powerful full-text search capabilities powered by [Tantivy](https://github.com/quickwit-oss/tantivy), a fast full-text search engine library written in Rust. This provides Redis-compatible search commands similar to RediSearch.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The search functionality allows you to:
|
||||||
|
- Create search indexes with custom schemas
|
||||||
|
- Index documents with multiple field types
|
||||||
|
- Perform complex queries with filters
|
||||||
|
- Support for text, numeric, date, and geographic data
|
||||||
|
- Real-time search with high performance
|
||||||
|
|
||||||
|
## Search Commands
|
||||||
|
|
||||||
|
### FT.CREATE - Create Search Index
|
||||||
|
|
||||||
|
Create a new search index with a defined schema.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FT.CREATE index_name SCHEMA field_name field_type [options] [field_name field_type [options] ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Field Types:**
|
||||||
|
- `TEXT` - Full-text searchable text fields
|
||||||
|
- `NUMERIC` - Numeric fields (integers, floats)
|
||||||
|
- `TAG` - Tag fields for exact matching
|
||||||
|
- `GEO` - Geographic coordinates (lat,lon)
|
||||||
|
- `DATE` - Date/timestamp fields
|
||||||
|
|
||||||
|
**Field Options:**
|
||||||
|
- `STORED` - Store field value for retrieval
|
||||||
|
- `INDEXED` - Make field searchable
|
||||||
|
- `TOKENIZED` - Enable tokenization for text fields
|
||||||
|
- `FAST` - Enable fast access for numeric fields
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# Create a product search index
|
||||||
|
FT.CREATE products SCHEMA
|
||||||
|
title TEXT STORED INDEXED TOKENIZED
|
||||||
|
description TEXT STORED INDEXED TOKENIZED
|
||||||
|
price NUMERIC STORED INDEXED FAST
|
||||||
|
category TAG STORED
|
||||||
|
location GEO STORED
|
||||||
|
created_date DATE STORED INDEXED
|
||||||
|
```
|
||||||
|
|
||||||
|
### FT.ADD - Add Document to Index
|
||||||
|
|
||||||
|
Add a document to a search index.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FT.ADD index_name doc_id [SCORE score] FIELDS field_name field_value [field_name field_value ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# Add a product document
|
||||||
|
FT.ADD products product:1 SCORE 1.0 FIELDS
|
||||||
|
title "Wireless Headphones"
|
||||||
|
description "High-quality wireless headphones with noise cancellation"
|
||||||
|
price 199.99
|
||||||
|
category "electronics"
|
||||||
|
location "37.7749,-122.4194"
|
||||||
|
created_date 1640995200000
|
||||||
|
```
|
||||||
|
|
||||||
|
### FT.SEARCH - Search Documents
|
||||||
|
|
||||||
|
Search for documents in an index.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FT.SEARCH index_name query [LIMIT offset count] [FILTER field min max] [RETURN field [field ...]]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Syntax:**
|
||||||
|
- Simple terms: `wireless headphones`
|
||||||
|
- Phrase queries: `"noise cancellation"`
|
||||||
|
- Field-specific: `title:wireless`
|
||||||
|
- Boolean operators: `wireless AND headphones`
|
||||||
|
- Wildcards: `head*`
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Simple text search
|
||||||
|
FT.SEARCH products "wireless headphones"
|
||||||
|
|
||||||
|
# Search with filters
|
||||||
|
FT.SEARCH products "headphones" FILTER price 100 300 LIMIT 0 10
|
||||||
|
|
||||||
|
# Field-specific search
|
||||||
|
FT.SEARCH products "title:wireless AND category:electronics"
|
||||||
|
|
||||||
|
# Return specific fields only
|
||||||
|
FT.SEARCH products "*" RETURN title price
|
||||||
|
```
|
||||||
|
|
||||||
|
### FT.DEL - Delete Document
|
||||||
|
|
||||||
|
Remove a document from the search index.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FT.DEL index_name doc_id
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
FT.DEL products product:1
|
||||||
|
```
|
||||||
|
|
||||||
|
### FT.INFO - Get Index Information
|
||||||
|
|
||||||
|
Get information about a search index.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FT.INFO index_name
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- Index name and document count
|
||||||
|
- Field definitions and types
|
||||||
|
- Index configuration
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
FT.INFO products
|
||||||
|
```
|
||||||
|
|
||||||
|
### FT.DROP - Drop Index
|
||||||
|
|
||||||
|
Delete an entire search index.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FT.DROP index_name
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
FT.DROP products
|
||||||
|
```
|
||||||
|
|
||||||
|
### FT.ALTER - Alter Index Schema
|
||||||
|
|
||||||
|
Add new fields to an existing index.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FT.ALTER index_name SCHEMA ADD field_name field_type [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
FT.ALTER products SCHEMA ADD brand TAG STORED
|
||||||
|
```
|
||||||
|
|
||||||
|
### FT.AGGREGATE - Aggregate Search Results
|
||||||
|
|
||||||
|
Perform aggregations on search results.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FT.AGGREGATE index_name query [GROUPBY field] [REDUCE function field AS alias]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# Group products by category and count
|
||||||
|
FT.AGGREGATE products "*" GROUPBY category REDUCE COUNT 0 AS count
|
||||||
|
```
|
||||||
|
|
||||||
|
## Field Types in Detail
|
||||||
|
|
||||||
|
### TEXT Fields
|
||||||
|
- **Purpose**: Full-text search on natural language content
|
||||||
|
- **Features**: Tokenization, stemming, stop-word removal
|
||||||
|
- **Options**: `STORED`, `INDEXED`, `TOKENIZED`
|
||||||
|
- **Example**: Product titles, descriptions, content
|
||||||
|
|
||||||
|
### NUMERIC Fields
|
||||||
|
- **Purpose**: Numeric data for range queries and sorting
|
||||||
|
- **Types**: I64, U64, F64
|
||||||
|
- **Options**: `STORED`, `INDEXED`, `FAST`
|
||||||
|
- **Example**: Prices, quantities, ratings
|
||||||
|
|
||||||
|
### TAG Fields
|
||||||
|
- **Purpose**: Exact-match categorical data
|
||||||
|
- **Features**: No tokenization, exact string matching
|
||||||
|
- **Options**: `STORED`, case sensitivity control
|
||||||
|
- **Example**: Categories, brands, status values
|
||||||
|
|
||||||
|
### GEO Fields
|
||||||
|
- **Purpose**: Geographic coordinates
|
||||||
|
- **Format**: "latitude,longitude" (e.g., "37.7749,-122.4194")
|
||||||
|
- **Features**: Geographic distance queries
|
||||||
|
- **Options**: `STORED`
|
||||||
|
|
||||||
|
### DATE Fields
|
||||||
|
- **Purpose**: Timestamp and date data
|
||||||
|
- **Format**: Unix timestamp in milliseconds
|
||||||
|
- **Features**: Range queries, temporal filtering
|
||||||
|
- **Options**: `STORED`, `INDEXED`, `FAST`
|
||||||
|
|
||||||
|
## Search Query Syntax
|
||||||
|
|
||||||
|
### Basic Queries
|
||||||
|
```bash
|
||||||
|
# Single term
|
||||||
|
FT.SEARCH products "wireless"
|
||||||
|
|
||||||
|
# Multiple terms (AND by default)
|
||||||
|
FT.SEARCH products "wireless headphones"
|
||||||
|
|
||||||
|
# Phrase query
|
||||||
|
FT.SEARCH products "\"noise cancellation\""
|
||||||
|
```
|
||||||
|
|
||||||
|
### Field-Specific Queries
|
||||||
|
```bash
|
||||||
|
# Search in specific field
|
||||||
|
FT.SEARCH products "title:wireless"
|
||||||
|
|
||||||
|
# Multiple field queries
|
||||||
|
FT.SEARCH products "title:wireless AND description:bluetooth"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boolean Operators
|
||||||
|
```bash
|
||||||
|
# AND operator
|
||||||
|
FT.SEARCH products "wireless AND headphones"
|
||||||
|
|
||||||
|
# OR operator
|
||||||
|
FT.SEARCH products "wireless OR bluetooth"
|
||||||
|
|
||||||
|
# NOT operator
|
||||||
|
FT.SEARCH products "headphones NOT wired"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wildcards and Fuzzy Search
|
||||||
|
```bash
|
||||||
|
# Wildcard search
|
||||||
|
FT.SEARCH products "head*"
|
||||||
|
|
||||||
|
# Fuzzy search (approximate matching)
|
||||||
|
FT.SEARCH products "%headphone%"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Range Queries
|
||||||
|
```bash
|
||||||
|
# Numeric range in query
|
||||||
|
FT.SEARCH products "@price:[100 300]"
|
||||||
|
|
||||||
|
# Date range
|
||||||
|
FT.SEARCH products "@created_date:[1640995200000 1672531200000]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Filtering and Sorting
|
||||||
|
|
||||||
|
### FILTER Clause
|
||||||
|
```bash
|
||||||
|
# Numeric filter
|
||||||
|
FT.SEARCH products "headphones" FILTER price 100 300
|
||||||
|
|
||||||
|
# Multiple filters
|
||||||
|
FT.SEARCH products "*" FILTER price 100 500 FILTER rating 4 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### LIMIT Clause
|
||||||
|
```bash
|
||||||
|
# Pagination
|
||||||
|
FT.SEARCH products "wireless" LIMIT 0 10 # First 10 results
|
||||||
|
FT.SEARCH products "wireless" LIMIT 10 10 # Next 10 results
|
||||||
|
```
|
||||||
|
|
||||||
|
### RETURN Clause
|
||||||
|
```bash
|
||||||
|
# Return specific fields
|
||||||
|
FT.SEARCH products "*" RETURN title price
|
||||||
|
|
||||||
|
# Return all stored fields (default)
|
||||||
|
FT.SEARCH products "*"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Indexing Strategy
|
||||||
|
- Only index fields you need to search on
|
||||||
|
- Use `FAST` option for frequently filtered numeric fields
|
||||||
|
- Consider storage vs. search performance trade-offs
|
||||||
|
|
||||||
|
### Query Optimization
|
||||||
|
- Use specific field queries when possible
|
||||||
|
- Combine filters with text queries for better performance
|
||||||
|
- Use pagination with LIMIT for large result sets
|
||||||
|
|
||||||
|
### Memory Usage
|
||||||
|
- Tantivy indexes are memory-mapped for performance
|
||||||
|
- Index size depends on document count and field configuration
|
||||||
|
- Monitor disk space for index storage
|
||||||
|
|
||||||
|
## Integration with Redis Commands
|
||||||
|
|
||||||
|
Search indexes work alongside regular Redis data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Store product data in Redis hash
|
||||||
|
HSET product:1 title "Wireless Headphones" price "199.99"
|
||||||
|
|
||||||
|
# Index the same data for search
|
||||||
|
FT.ADD products product:1 FIELDS title "Wireless Headphones" price 199.99
|
||||||
|
|
||||||
|
# Search returns document IDs that can be used with Redis commands
|
||||||
|
FT.SEARCH products "wireless"
|
||||||
|
# Returns: product:1
|
||||||
|
|
||||||
|
# Retrieve full data using Redis
|
||||||
|
HGETALL product:1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Use Cases
|
||||||
|
|
||||||
|
### E-commerce Product Search
|
||||||
|
```bash
|
||||||
|
# Create product catalog index
|
||||||
|
FT.CREATE catalog SCHEMA
|
||||||
|
name TEXT STORED INDEXED TOKENIZED
|
||||||
|
description TEXT INDEXED TOKENIZED
|
||||||
|
price NUMERIC STORED INDEXED FAST
|
||||||
|
category TAG STORED
|
||||||
|
brand TAG STORED
|
||||||
|
rating NUMERIC STORED FAST
|
||||||
|
|
||||||
|
# Add products
|
||||||
|
FT.ADD catalog prod:1 FIELDS name "iPhone 14" price 999 category "phones" brand "apple" rating 4.5
|
||||||
|
FT.ADD catalog prod:2 FIELDS name "Samsung Galaxy" price 899 category "phones" brand "samsung" rating 4.3
|
||||||
|
|
||||||
|
# Search queries
|
||||||
|
FT.SEARCH catalog "iPhone"
|
||||||
|
FT.SEARCH catalog "phones" FILTER price 800 1000
|
||||||
|
FT.SEARCH catalog "@brand:apple"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Management
|
||||||
|
```bash
|
||||||
|
# Create content index
|
||||||
|
FT.CREATE content SCHEMA
|
||||||
|
title TEXT STORED INDEXED TOKENIZED
|
||||||
|
body TEXT INDEXED TOKENIZED
|
||||||
|
author TAG STORED
|
||||||
|
published DATE STORED INDEXED
|
||||||
|
tags TAG STORED
|
||||||
|
|
||||||
|
# Search content
|
||||||
|
FT.SEARCH content "machine learning"
|
||||||
|
FT.SEARCH content "@author:john AND @tags:ai"
|
||||||
|
FT.SEARCH content "*" FILTER published 1640995200000 1672531200000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Geographic Search
|
||||||
|
```bash
|
||||||
|
# Create location-based index
|
||||||
|
FT.CREATE places SCHEMA
|
||||||
|
name TEXT STORED INDEXED TOKENIZED
|
||||||
|
location GEO STORED
|
||||||
|
type TAG STORED
|
||||||
|
|
||||||
|
# Add locations
|
||||||
|
FT.ADD places place:1 FIELDS name "Golden Gate Bridge" location "37.8199,-122.4783" type "landmark"
|
||||||
|
|
||||||
|
# Geographic queries (future feature)
|
||||||
|
FT.SEARCH places "@location:[37.7749 -122.4194 10 km]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Common error responses:
|
||||||
|
- `ERR index not found` - Index doesn't exist
|
||||||
|
- `ERR field not found` - Field not defined in schema
|
||||||
|
- `ERR invalid query syntax` - Malformed query
|
||||||
|
- `ERR document not found` - Document ID doesn't exist
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Schema Design**: Plan your schema carefully - changes require reindexing
|
||||||
|
2. **Field Selection**: Only store and index fields you actually need
|
||||||
|
3. **Batch Operations**: Add multiple documents efficiently
|
||||||
|
4. **Query Testing**: Test queries for performance with realistic data
|
||||||
|
5. **Monitoring**: Monitor index size and query performance
|
||||||
|
6. **Backup**: Include search indexes in backup strategies
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Planned features:
|
||||||
|
- Geographic distance queries
|
||||||
|
- Advanced aggregations and faceting
|
||||||
|
- Highlighting of search results
|
||||||
|
- Synonyms and custom analyzers
|
||||||
|
- Real-time suggestions and autocomplete
|
||||||
|
- Index replication and sharding
|
@@ -5,7 +5,7 @@ use tantivy::{
|
|||||||
schema::{Schema, Field, TextOptions, TextFieldIndexing,
|
schema::{Schema, Field, TextOptions, TextFieldIndexing,
|
||||||
STORED, STRING, Value},
|
STORED, STRING, Value},
|
||||||
Index, IndexWriter, IndexReader, ReloadPolicy,
|
Index, IndexWriter, IndexReader, ReloadPolicy,
|
||||||
Term, DateTime,
|
Term, DateTime, TantivyDocument,
|
||||||
tokenizer::{TokenizerManager},
|
tokenizer::{TokenizerManager},
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -231,8 +231,9 @@ impl TantivySearch {
|
|||||||
let writer = index.writer(50_000_000)
|
let writer = index.writer(50_000_000)
|
||||||
.map_err(|e| DBError(format!("Failed to create index writer: {}", e)))?;
|
.map_err(|e| DBError(format!("Failed to create index writer: {}", e)))?;
|
||||||
|
|
||||||
let reader = index.reader_builder()
|
let reader = index
|
||||||
.reload_policy(ReloadPolicy::Manual)
|
.reader_builder()
|
||||||
|
.reload_policy(ReloadPolicy::OnCommitWithDelay)
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|e| DBError(format!("Failed to create reader: {}", e)))?;
|
.map_err(|e| DBError(format!("Failed to create reader: {}", e)))?;
|
||||||
|
|
||||||
@@ -360,9 +361,7 @@ impl TantivySearch {
|
|||||||
|
|
||||||
// Apply filters if any
|
// Apply filters if any
|
||||||
let final_query = if !options.filters.is_empty() {
|
let final_query = if !options.filters.is_empty() {
|
||||||
let mut queries: Vec<(Occur, Box<dyn Query>)> = Vec::new();
|
let mut clauses: Vec<(Occur, Box<dyn Query>)> = vec![(Occur::Must, query)];
|
||||||
queries.push((Occur::Must, query));
|
|
||||||
|
|
||||||
|
|
||||||
// Add filters
|
// Add filters
|
||||||
for filter in options.filters {
|
for filter in options.filters {
|
||||||
@@ -373,28 +372,28 @@ impl TantivySearch {
|
|||||||
Term::from_field_text(*field, &value),
|
Term::from_field_text(*field, &value),
|
||||||
tantivy::schema::IndexRecordOption::Basic,
|
tantivy::schema::IndexRecordOption::Basic,
|
||||||
);
|
);
|
||||||
queries.push((Occur::Must, Box::new(term_query)));
|
clauses.push((Occur::Must, Box::new(term_query)));
|
||||||
}
|
}
|
||||||
FilterType::Range { min, max } => {
|
FilterType::Range { min: _, max: _ } => {
|
||||||
// Would need numeric field handling here
|
// Would need numeric field handling here
|
||||||
// Simplified for now
|
// Simplified for now
|
||||||
}
|
}
|
||||||
FilterType::InSet(values) => {
|
FilterType::InSet(values) => {
|
||||||
let mut sub_queries: Vec<(Occur, Box<dyn Query>)> = Vec::new();
|
let mut sub_clauses: Vec<(Occur, Box<dyn Query>)> = vec![];
|
||||||
for value in values {
|
for value in values {
|
||||||
let term_query = TermQuery::new(
|
let term_query = TermQuery::new(
|
||||||
Term::from_field_text(*field, &value),
|
Term::from_field_text(*field, &value),
|
||||||
tantivy::schema::IndexRecordOption::Basic,
|
tantivy::schema::IndexRecordOption::Basic,
|
||||||
);
|
);
|
||||||
sub_queries.push((Occur::Should, Box::new(term_query)));
|
sub_clauses.push((Occur::Should, Box::new(term_query)));
|
||||||
}
|
}
|
||||||
queries.push((Occur::Must, Box::new(BooleanQuery::new(sub_queries))));
|
clauses.push((Occur::Must, Box::new(BooleanQuery::new(sub_clauses))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box::new(BooleanQuery::new(queries))
|
Box::new(BooleanQuery::new(clauses))
|
||||||
} else {
|
} else {
|
||||||
query
|
query
|
||||||
};
|
};
|
||||||
@@ -409,7 +408,7 @@ impl TantivySearch {
|
|||||||
let mut documents = Vec::new();
|
let mut documents = Vec::new();
|
||||||
|
|
||||||
for (score, doc_address) in top_docs.iter().skip(options.offset).take(options.limit) {
|
for (score, doc_address) in top_docs.iter().skip(options.offset).take(options.limit) {
|
||||||
let retrieved_doc: tantivy::TantivyDocument = searcher.doc(*doc_address)
|
let retrieved_doc: TantivyDocument = searcher.doc(*doc_address)
|
||||||
.map_err(|e| DBError(format!("Failed to retrieve doc: {}", e)))?;
|
.map_err(|e| DBError(format!("Failed to retrieve doc: {}", e)))?;
|
||||||
|
|
||||||
let mut doc_fields = HashMap::new();
|
let mut doc_fields = HashMap::new();
|
||||||
@@ -459,13 +458,11 @@ impl TantivySearch {
|
|||||||
}
|
}
|
||||||
FieldDef::Geo { stored } => {
|
FieldDef::Geo { stored } => {
|
||||||
if *stored {
|
if *stored {
|
||||||
let lat = retrieved_doc.get_first(
|
let lat_field = self.index_schema.fields.get(&format!("{}_lat", field_name)).unwrap().0;
|
||||||
self.index_schema.fields.get(&format!("{}_lat", field_name)).unwrap().0
|
let lon_field = self.index_schema.fields.get(&format!("{}_lon", field_name)).unwrap().0;
|
||||||
).and_then(|v| v.as_f64());
|
|
||||||
|
|
||||||
let lon = retrieved_doc.get_first(
|
let lat = retrieved_doc.get_first(lat_field).and_then(|v| v.as_f64());
|
||||||
self.index_schema.fields.get(&format!("{}_lon", field_name)).unwrap().0
|
let lon = retrieved_doc.get_first(lon_field).and_then(|v| v.as_f64());
|
||||||
).and_then(|v| v.as_f64());
|
|
||||||
|
|
||||||
if let (Some(lat), Some(lon)) = (lat, lon) {
|
if let (Some(lat), Some(lon)) = (lat, lon) {
|
||||||
doc_fields.insert(field_name.clone(), format!("{},{}", lat, lon));
|
doc_fields.insert(field_name.clone(), format!("{},{}", lat, lon));
|
||||||
|
Reference in New Issue
Block a user