Files
elasticsearch/ts/examples/query/query-builder-example.ts

419 lines
15 KiB
TypeScript
Raw Normal View History

/**
* Comprehensive Query Builder Example
*
* Demonstrates type-safe query construction with the QueryBuilder
*/
import {
createConfig,
ElasticsearchConnectionManager,
LogLevel,
} from '../../core/index.js';
import { DocumentManager } from '../../domain/documents/index.js';
import { QueryBuilder, createQuery } from '../../domain/query/index.js';
interface Product {
name: string;
description: string;
category: string;
brand: string;
price: number;
rating: number;
stock: number;
tags: string[];
createdAt: Date;
updatedAt: Date;
}
async function main() {
console.log('=== Query Builder Example ===\n');
// ============================================================================
// Step 1: Configuration
// ============================================================================
console.log('Step 1: Configuring Elasticsearch connection...');
const config = createConfig()
.fromEnv()
.nodes(process.env.ELASTICSEARCH_URL || 'http://localhost:9200')
.basicAuth(
process.env.ELASTICSEARCH_USERNAME || 'elastic',
process.env.ELASTICSEARCH_PASSWORD || 'changeme'
)
.timeout(30000)
.retries(3)
.logLevel(LogLevel.INFO)
.enableMetrics(true)
.enableTracing(true, { serviceName: 'query-example', serviceVersion: '1.0.0' })
.build();
// ============================================================================
// Step 2: Initialize Connection
// ============================================================================
console.log('Step 2: Initializing connection manager...');
const connectionManager = ElasticsearchConnectionManager.getInstance(config);
await connectionManager.initialize();
console.log('✓ Connection manager initialized\n');
// ============================================================================
// Step 3: Setup Sample Data
// ============================================================================
console.log('Step 3: Setting up sample data...');
const products = new DocumentManager<Product>({
index: 'products-query-example',
autoCreateIndex: true,
});
await products.initialize();
// Create sample products
const sampleProducts: Array<{ id: string; data: Product }> = [
{
id: 'laptop-1',
data: {
name: 'Professional Laptop Pro',
description: 'High-performance laptop for professionals',
category: 'Electronics',
brand: 'TechBrand',
price: 1299.99,
rating: 4.5,
stock: 15,
tags: ['laptop', 'professional', 'high-end'],
createdAt: new Date('2024-01-15'),
updatedAt: new Date('2024-01-20'),
},
},
{
id: 'laptop-2',
data: {
name: 'Budget Laptop Basic',
description: 'Affordable laptop for everyday use',
category: 'Electronics',
brand: 'ValueBrand',
price: 499.99,
rating: 3.8,
stock: 30,
tags: ['laptop', 'budget', 'student'],
createdAt: new Date('2024-02-01'),
updatedAt: new Date('2024-02-05'),
},
},
{
id: 'phone-1',
data: {
name: 'Smartphone X',
description: 'Latest flagship smartphone',
category: 'Electronics',
brand: 'PhoneBrand',
price: 899.99,
rating: 4.7,
stock: 25,
tags: ['smartphone', 'flagship', '5g'],
createdAt: new Date('2024-01-20'),
updatedAt: new Date('2024-01-25'),
},
},
{
id: 'tablet-1',
data: {
name: 'Tablet Pro',
description: 'Professional tablet for creative work',
category: 'Electronics',
brand: 'TechBrand',
price: 799.99,
rating: 4.6,
stock: 20,
tags: ['tablet', 'creative', 'professional'],
createdAt: new Date('2024-02-10'),
updatedAt: new Date('2024-02-15'),
},
},
{
id: 'monitor-1',
data: {
name: '4K Monitor',
description: 'Ultra HD monitor for gaming and design',
category: 'Electronics',
brand: 'DisplayBrand',
price: 599.99,
rating: 4.4,
stock: 12,
tags: ['monitor', '4k', 'gaming'],
createdAt: new Date('2024-01-25'),
updatedAt: new Date('2024-01-30'),
},
},
];
// Index sample data
const session = products.session();
session.start();
for (const product of sampleProducts) {
session.upsert(product.id, product.data);
}
await session.commit();
console.log(`✓ Indexed ${sampleProducts.length} sample products\n`);
// Wait for indexing to complete
await new Promise((resolve) => setTimeout(resolve, 1000));
// ============================================================================
// Step 4: Simple Queries
// ============================================================================
console.log('Step 4: Running simple queries...\n');
// 4.1: Match query - search by name
console.log('4.1: Match query - search for "laptop"');
const laptopResults = await createQuery<Product>('products-query-example')
.match('name', 'laptop')
.size(10)
.execute();
console.log(`Found ${laptopResults.hits.total.value} laptops`);
console.log('Laptops:', laptopResults.hits.hits.map((h) => h._source.name));
console.log();
// 4.2: Term query - exact match on category
console.log('4.2: Term query - exact category match');
const electronicsResults = await createQuery<Product>('products-query-example')
.term('category.keyword', 'Electronics')
.execute();
console.log(`Found ${electronicsResults.hits.total.value} electronics`);
console.log();
// 4.3: Range query - price between 500 and 1000
console.log('4.3: Range query - price between $500 and $1000');
const midPriceResults = await createQuery<Product>('products-query-example')
.range('price', { gte: 500, lte: 1000 })
.sort('price', 'asc')
.execute();
console.log(`Found ${midPriceResults.hits.total.value} products in price range`);
midPriceResults.hits.hits.forEach((hit) => {
console.log(` - ${hit._source.name}: $${hit._source.price}`);
});
console.log();
// 4.4: Multi-match query - search across multiple fields
console.log('4.4: Multi-match query - search "professional" in name and description');
const professionalResults = await createQuery<Product>('products-query-example')
.multiMatch('professional', ['name', 'description'])
.execute();
console.log(`Found ${professionalResults.hits.total.value} professional products`);
console.log();
// ============================================================================
// Step 5: Boolean Queries
// ============================================================================
console.log('Step 5: Running boolean queries...\n');
// 5.1: Must + Filter - combine multiple conditions
console.log('5.1: Boolean query - TechBrand products over $700');
const techBrandResults = await createQuery<Product>('products-query-example')
.term('brand.keyword', 'TechBrand')
.range('price', { gte: 700 })
.sort('price', 'desc')
.execute();
console.log(`Found ${techBrandResults.hits.total.value} matching products`);
techBrandResults.hits.hits.forEach((hit) => {
console.log(` - ${hit._source.name} (${hit._source.brand}): $${hit._source.price}`);
});
console.log();
// 5.2: Should clause - match any condition
console.log('5.2: Should query - products matching "laptop" OR "tablet"');
const laptopOrTabletResults = await new QueryBuilder<Product>('products-query-example')
.should({ match: { name: { query: 'laptop' } } })
.should({ match: { name: { query: 'tablet' } } })
.minimumMatch(1)
.execute();
console.log(`Found ${laptopOrTabletResults.hits.total.value} laptops or tablets`);
console.log();
// 5.3: Must not - exclude results
console.log('5.3: Must not query - electronics excluding laptops');
const noLaptopsResults = await createQuery<Product>('products-query-example')
.term('category.keyword', 'Electronics')
.mustNot({ match: { name: { query: 'laptop' } } })
.execute();
console.log(`Found ${noLaptopsResults.hits.total.value} non-laptop electronics`);
console.log();
// ============================================================================
// Step 6: Aggregations
// ============================================================================
console.log('Step 6: Running aggregations...\n');
// 6.1: Terms aggregation - group by brand
console.log('6.1: Terms aggregation - products by brand');
const brandAggResults = await createQuery<Product>('products-query-example')
.matchAll()
.size(0) // We only want aggregations, not documents
.aggregations((agg) => {
agg.terms('brands', 'brand.keyword', { size: 10 });
})
.execute();
if (brandAggResults.aggregations && 'brands' in brandAggResults.aggregations) {
const brandsAgg = brandAggResults.aggregations.brands as { buckets: Array<{ key: string; doc_count: number }> };
console.log('Products by brand:');
brandsAgg.buckets.forEach((bucket) => {
console.log(` - ${bucket.key}: ${bucket.doc_count} products`);
});
}
console.log();
// 6.2: Metric aggregations - price statistics
console.log('6.2: Metric aggregations - price statistics');
const priceStatsResults = await createQuery<Product>('products-query-example')
.matchAll()
.size(0)
.aggregations((agg) => {
agg.stats('price_stats', 'price');
agg.avg('avg_rating', 'rating');
agg.sum('total_stock', 'stock');
})
.execute();
if (priceStatsResults.aggregations) {
console.log('Price statistics:', priceStatsResults.aggregations.price_stats);
console.log('Average rating:', priceStatsResults.aggregations.avg_rating);
console.log('Total stock:', priceStatsResults.aggregations.total_stock);
}
console.log();
// 6.3: Nested aggregations - brands with average price
console.log('6.3: Nested aggregations - average price per brand');
const nestedAggResults = await createQuery<Product>('products-query-example')
.matchAll()
.size(0)
.aggregations((agg) => {
agg.terms('brands', 'brand.keyword', { size: 10 }).subAggregation('avg_price', (sub) => {
sub.avg('avg_price', 'price');
});
})
.execute();
if (nestedAggResults.aggregations && 'brands' in nestedAggResults.aggregations) {
const brandsAgg = nestedAggResults.aggregations.brands as {
buckets: Array<{ key: string; doc_count: number; avg_price: { value: number } }>;
};
console.log('Average price by brand:');
brandsAgg.buckets.forEach((bucket) => {
console.log(` - ${bucket.key}: $${bucket.avg_price.value.toFixed(2)} (${bucket.doc_count} products)`);
});
}
console.log();
// ============================================================================
// Step 7: Advanced Features
// ============================================================================
console.log('Step 7: Advanced query features...\n');
// 7.1: Pagination
console.log('7.1: Pagination - page 1 of results (2 per page)');
const page1Results = await createQuery<Product>('products-query-example')
.matchAll()
.paginate(1, 2)
.sort('price', 'asc')
.execute();
console.log(`Page 1: ${page1Results.hits.hits.length} results`);
page1Results.hits.hits.forEach((hit) => {
console.log(` - ${hit._source.name}: $${hit._source.price}`);
});
console.log();
// 7.2: Source filtering - only return specific fields
console.log('7.2: Source filtering - only name and price');
const filteredResults = await createQuery<Product>('products-query-example')
.matchAll()
.fields(['name', 'price'])
.size(3)
.execute();
console.log('Filtered results:');
filteredResults.hits.hits.forEach((hit) => {
console.log(` - Name: ${hit._source.name}, Price: ${hit._source.price}`);
});
console.log();
// 7.3: Count documents
console.log('7.3: Count documents matching query');
const count = await createQuery<Product>('products-query-example')
.range('price', { gte: 500 })
.count();
console.log(`Count of products over $500: ${count}`);
console.log();
// 7.4: Get only sources (convenience method)
console.log('7.4: Get sources only');
const sources = await createQuery<Product>('products-query-example')
.term('brand.keyword', 'TechBrand')
.executeAndGetSources();
console.log(`TechBrand products: ${sources.map((s) => s.name).join(', ')}`);
console.log();
// ============================================================================
// Step 8: Complex Real-World Query
// ============================================================================
console.log('Step 8: Complex real-world query...\n');
console.log('Finding high-rated electronics in stock, sorted by best deals:');
const complexResults = await createQuery<Product>('products-query-example')
.term('category.keyword', 'Electronics')
.range('rating', { gte: 4.0 })
.range('stock', { gt: 0 })
.range('price', { lte: 1000 })
.sort('rating', 'desc')
.size(5)
.aggregations((agg) => {
agg.terms('top_brands', 'brand.keyword', { size: 5 });
agg.avg('avg_price', 'price');
agg.max('max_rating', 'rating');
})
.execute();
console.log(`Found ${complexResults.hits.total.value} matching products`);
console.log('\nTop results:');
complexResults.hits.hits.forEach((hit, index) => {
console.log(` ${index + 1}. ${hit._source.name}`);
console.log(` Brand: ${hit._source.brand}`);
console.log(` Price: $${hit._source.price}`);
console.log(` Rating: ${hit._source.rating}`);
console.log(` Stock: ${hit._source.stock} units`);
});
if (complexResults.aggregations) {
console.log('\nAggregated insights:');
console.log(' Average price:', complexResults.aggregations.avg_price);
console.log(' Max rating:', complexResults.aggregations.max_rating);
if ('top_brands' in complexResults.aggregations) {
const topBrands = complexResults.aggregations.top_brands as { buckets: Array<{ key: string; doc_count: number }> };
console.log(' Top brands:');
topBrands.buckets.forEach((bucket) => {
console.log(` - ${bucket.key}: ${bucket.doc_count} products`);
});
}
}
console.log();
// ============================================================================
// Step 9: Cleanup
// ============================================================================
console.log('Step 9: Cleanup...');
await products.deleteIndex();
console.log('✓ Test index deleted');
await connectionManager.destroy();
console.log('✓ Connection closed\n');
console.log('=== Query Builder Example Complete ===');
}
// Run the example
main().catch((error) => {
console.error('Example failed:', error);
process.exit(1);
});