Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Layout/LineLength:
Max: 120
Exclude:
- 'test/**/*'
- 'examples/**/*'

Layout/CaseIndentation:
EnforcedStyle: end
Expand Down Expand Up @@ -155,6 +156,10 @@ Style/SymbolProc:
Exclude:
- 'test/**/*'

Style/CombinableLoops:
Exclude:
- 'examples/**/*'

Bundler/OrderedGems:
Enabled: false

Expand Down
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,72 @@
# Unreleased

## New Features

### JSON Support
- Add comprehensive JSON command support for Redis Stack
- Implement all JSON.* commands including:
- `json_set`, `json_get`, `json_del` - Basic operations
- `json_numincrby`, `json_nummultby` - Numeric operations
- `json_strappend`, `json_strlen` - String operations
- `json_arrappend`, `json_arrinsert`, `json_arrtrim`, `json_arrpop`, `json_arrindex`, `json_arrlen` - Array operations
- `json_objlen`, `json_objkeys` - Object operations
- `json_type`, `json_clear`, `json_toggle`, `json_merge`, `json_mget`, `json_resp` - Utility operations
- Add comprehensive test suite with 95 tests and 280 assertions
- Add tutorial example: `examples/json_tutorial.rb`

### Search Support
- Add comprehensive Search command support for Redis Stack
- Implement all FT.* commands including:
- `ft_create`, `ft_dropindex`, `ft_info` - Index management
- `ft_search` - Full-text and structured search
- `ft_aggregate` - Aggregations and analytics
- `ft_profile` - Query profiling
- `ft_explain`, `ft_explaincli` - Query explanation
- `ft_aliasadd`, `ft_aliasupdate`, `ft_aliasdel` - Index aliases
- `ft_tagvals`, `ft_sugadd`, `ft_sugget`, `ft_sugdel`, `ft_suglen` - Suggestions
- `ft_syndump`, `ft_synupdate` - Synonym management
- `ft_spellcheck` - Spell checking
- `ft_dictadd`, `ft_dictdel`, `ft_dictdump` - Dictionary management
- Add Schema DSL for index creation with field types:
- `text_field`, `numeric_field`, `tag_field`, `geo_field`, `geoshape_field`, `vector_field`
- Add Query DSL for building complex queries
- Add AggregateRequest and Reducers for analytics
- Add comprehensive test suite with 165 tests and 575 assertions
- Add tutorial examples:
- `examples/search_quickstart.rb` - Basic search operations
- `examples/search_ft_queries.rb` - Advanced query patterns
- `examples/search_aggregations.rb` - Aggregations and analytics
- `examples/search_geo.rb` - Geospatial queries
- `examples/search_range.rb` - Range queries
- `examples/search_vector_similarity.rb` - Vector similarity search

### Vector Set Support
- Add comprehensive Vector Set command support for Redis 8.0+
- Implement all V* commands including:
- `vadd` - Add vectors with support for VALUES and FP32 formats
- `vcard`, `vdim` - Get cardinality and dimensionality
- `vemb` - Retrieve vector embeddings
- `vgetattr`, `vsetattr` - Get and set JSON attributes
- `vinfo` - Get vector set information
- `vismember` - Check membership
- `vlinks` - Get HNSW graph links
- `vrandmember` - Get random members
- `vrange` - Range queries
- `vrem` - Remove vectors
- `vsim` - Vector similarity search with options:
- Quantization: NOQUANT, BIN, Q8/INT8
- Dimensionality reduction: REDUCE
- Filtering: FILTER with mathematical expressions
- HNSW parameters: M/numlinks, EF
- Options: WITHSCORES, WITHATTRIBS, COUNT, TRUTH, NOTHREAD
- Add comprehensive test suite with 34 tests and 208-231 assertions
- Add tutorial example: `examples/vector_set_tutorial.rb`

## Documentation
- Update README.md with JSON, Search, and Vector Set sections
- Add 8 comprehensive tutorial examples demonstrating all new features
- All examples tested and verified on Redis 8.4.0

# 5.4.1

- Properly handle NOSCRIPT errors.
Expand Down
106 changes: 106 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,112 @@ redis.get("mykey")
All commands, their arguments, and return values are documented and
available on [RubyDoc.info][rubydoc].

## JSON Support

Redis Stack includes native JSON support via the [RedisJSON](https://redis.io/docs/stack/json/) module. The client provides full support for JSON commands:

```ruby
# Set a JSON document
redis.json_set("user:1", "$", {
name: "John Doe",
email: "john@example.com",
age: 30,
address: {
city: "New York",
country: "USA"
}
})

# Get the entire document
redis.json_get("user:1", "$")
# => [{"name"=>"John Doe", "email"=>"john@example.com", "age"=>30, "address"=>{"city"=>"New York", "country"=>"USA"}}]

# Get specific fields using JSONPath
redis.json_get("user:1", "$.name")
# => ["John Doe"]

# Update nested fields
redis.json_numincrby("user:1", "$.age", 1)
# => [31]

# Array operations
redis.json_set("user:1", "$.hobbies", ["reading", "coding"])
redis.json_arrappend("user:1", "$.hobbies", "gaming")
# => [3]
```

For a comprehensive tutorial, see [examples/json_tutorial.rb](examples/json_tutorial.rb).

## Search and Query Support

Redis Stack includes powerful search and query capabilities via [RediSearch](https://redis.io/docs/stack/search/). The client provides full support for creating indexes and querying data:

```ruby
# Create an index on JSON documents
schema = Redis::Search::Schema.build do
text_field "$.name", as: "name"
numeric_field "$.age", as: "age"
tag_field "$.city", as: "city"
end

index_def = Redis::Search::IndexDefinition.new(
index_type: Redis::Search::IndexType::JSON,
prefixes: ["user:"]
)

redis.ft_create("idx:users", schema, index_def)

# Search for users
results = redis.ft_search("idx:users", "@name:John")

# Numeric range queries
results = redis.ft_search("idx:users", "@age:[25 35]")

# Aggregations
agg = Redis::Search::AggregateRequest.new("*")
.group_by(["@city"], [Redis::Search::Reducers.count.as("count")])
.sort_by([Redis::Search::SortBy.desc("@count")])

results = redis.ft_aggregate("idx:users", agg)
```

For comprehensive tutorials, see:
- [examples/search_quickstart.rb](examples/search_quickstart.rb) - Basic search operations
- [examples/search_ft_queries.rb](examples/search_ft_queries.rb) - Advanced query patterns
- [examples/search_aggregations.rb](examples/search_aggregations.rb) - Aggregations and analytics
- [examples/search_geo.rb](examples/search_geo.rb) - Geospatial queries
- [examples/search_range.rb](examples/search_range.rb) - Range queries
- [examples/search_vector_similarity.rb](examples/search_vector_similarity.rb) - Vector similarity search

## Vector Set Support

Redis 8.0+ includes native vector sets for efficient vector similarity operations. The client provides full support for vector set commands:

```ruby
# Add vectors to a vector set
redis.vadd("vectors:products", "item:1", [0.1, 0.2, 0.3, 0.4])
redis.vadd("vectors:products", "item:2", [0.2, 0.3, 0.4, 0.5])

# Get cardinality and dimensionality
redis.vcard("vectors:products") # => 2
redis.vdim("vectors:products") # => 4

# Find similar vectors
results = redis.vsim("vectors:products", 2, [0.15, 0.25, 0.35, 0.45], with_scores: true)
# => ["item:1", "0.998", "item:2", "0.995"]

# Set attributes on vectors
redis.vsetattr("vectors:products", "item:1", {name: "Product A", price: 29.99})

# Search with filters
results = redis.vsim("vectors:products", 5, [0.1, 0.2, 0.3, 0.4],
filter: "@price >= 20 && @price <= 50",
with_attribs: true
)
```

For a comprehensive tutorial, see [examples/vector_set_tutorial.rb](examples/vector_set_tutorial.rb).

## Connection Pooling and Thread safety

The client does not provide connection pooling. Each `Redis` instance
Expand Down
173 changes: 173 additions & 0 deletions examples/vector_set_tutorial.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# frozen_string_literal: true

# EXAMPLE: vecset_tutorial
# This example demonstrates Redis Vector Set operations including:
# - VADD - Adding vectors to a vector set
# - VCARD and VDIM - Getting cardinality and dimensionality
# - VEMB - Retrieving vector embeddings
# - VSETATTR and VGETATTR - Setting and getting JSON attributes
# - VSIM - Vector similarity search with various options (WITHSCORES, WITHATTRIBS, COUNT, EF, FILTER, TRUTH, NOTHREAD)
# - VREM - Removing elements
# - Quantization options (NOQUANT, Q8, BIN)
# - REDUCE - Dimensionality reduction
# - Filtering with mathematical expressions

require 'redis'
require 'json'

# Connect to Redis on port 6400
redis = Redis.new(host: 'localhost', port: 6400)

# Clear any keys before using them
redis.del('points', 'quantSetQ8', 'quantSetNoQ', 'quantSetBin', 'setNotReduced', 'setReduced')

# STEP_START vadd
res1 = redis.vadd('points', [1.0, 1.0], 'pt:A')
puts res1 # => 1

res2 = redis.vadd('points', [-1.0, -1.0], 'pt:B')
puts res2 # => 1

res3 = redis.vadd('points', [-1.0, 1.0], 'pt:C')
puts res3 # => 1

res4 = redis.vadd('points', [1.0, -1.0], 'pt:D')
puts res4 # => 1

res5 = redis.vadd('points', [1.0, 0.0], 'pt:E')
puts res5 # => 1

res6 = redis.type('points')
puts res6 # => vectorset
# STEP_END

# STEP_START vcardvdim
res7 = redis.vcard('points')
puts res7 # => 5

res8 = redis.vdim('points')
puts res8 # => 2
# STEP_END

# STEP_START vemb
res9 = redis.vemb('points', 'pt:A')
puts res9.inspect # => [0.9999999..., 0.9999999...]

res10 = redis.vemb('points', 'pt:B')
puts res10.inspect # => [-0.9999999..., -0.9999999...]

res11 = redis.vemb('points', 'pt:C')
puts res11.inspect # => [-0.9999999..., 0.9999999...]

res12 = redis.vemb('points', 'pt:D')
puts res12.inspect # => [0.9999999..., -0.9999999...]

res13 = redis.vemb('points', 'pt:E')
puts res13.inspect # => [1.0, 0.0]
# STEP_END

# STEP_START attr
res14 = redis.vsetattr('points', 'pt:A', '{"name":"Point A","description":"First point added"}')
puts res14 # => 1

res15 = redis.vgetattr('points', 'pt:A')
puts res15.inspect
# => {"name"=>"Point A", "description"=>"First point added"}

res16 = redis.vsetattr('points', 'pt:A', '')
puts res16 # => 1

res17 = redis.vgetattr('points', 'pt:A')
puts res17.inspect # => nil
# STEP_END

# STEP_START vrem
res18 = redis.vadd('points', [0.0, 0.0], 'pt:F')
puts res18 # => 1

res19 = redis.vcard('points')
puts res19 # => 6

res20 = redis.vrem('points', 'pt:F')
puts res20 # => 1

res21 = redis.vcard('points')
puts res21 # => 5
# STEP_END

# STEP_START vsim_basic
res22 = redis.vsim('points', [0.9, 0.1])
puts res22.inspect
# => ["pt:E", "pt:A", "pt:D", "pt:C", "pt:B"]
# STEP_END

# STEP_START vsim_options
res23 = redis.vsim('points', 'pt:A', with_scores: true, count: 4)
puts res23.inspect
# => {"pt:A"=>1.0, "pt:E"≈0.85355, "pt:D"=>0.5, "pt:C"=>0.5}
# STEP_END

# STEP_START vsim_filter
res24 = redis.vsetattr('points', 'pt:A', '{"size":"large","price":18.99}')
puts res24 # => 1

res25 = redis.vsetattr('points', 'pt:B', '{"size":"large","price":35.99}')
puts res25 # => 1

res26 = redis.vsetattr('points', 'pt:C', '{"size":"large","price":25.99}')
puts res26 # => 1

res27 = redis.vsetattr('points', 'pt:D', '{"size":"small","price":21.00}')
puts res27 # => 1

res28 = redis.vsetattr('points', 'pt:E', '{"size":"small","price":17.75}')
puts res28 # => 1

res29 = redis.vsim('points', 'pt:A', filter: '.size == "large"')
puts res29.inspect # => ["pt:A", "pt:C", "pt:B"]

res30 = redis.vsim('points', 'pt:A', filter: '.size == "large" && .price > 20.00')
puts res30.inspect # => ["pt:C", "pt:B"]
# STEP_END

# STEP_START add_quant
res31 = redis.vadd('quantSetQ8', [1.262185, 1.958231], 'quantElement', quantization: 'q8')
puts res31 # => 1

res32 = redis.vemb('quantSetQ8', 'quantElement')
puts "Q8: #{res32.inspect}"
# => Q8: [~1.264, ~1.958]

res33 = redis.vadd('quantSetNoQ', [1.262185, 1.958231], 'quantElement', quantization: 'noquant')
puts res33 # => 1

res34 = redis.vemb('quantSetNoQ', 'quantElement')
puts "NOQUANT: #{res34.inspect}"
# => NOQUANT: [~1.262185, ~1.958231]

res35 = redis.vadd('quantSetBin', [1.262185, 1.958231], 'quantElement', quantization: 'bin')
puts res35 # => 1

res36 = redis.vemb('quantSetBin', 'quantElement')
puts "BIN: #{res36.inspect}"
# => BIN: [1.0, 1.0]
# STEP_END

# STEP_START add_reduce
values = Array.new(300) { |i| i / 299.0 }

res37 = redis.vadd('setNotReduced', values, 'element')
puts res37 # => 1

res38 = redis.vdim('setNotReduced')
puts res38 # => 300

res39 = redis.vadd('setReduced', values, 'element', reduce_dim: 100)
puts res39 # => 1

res40 = redis.vdim('setReduced')
puts res40 # => 100
# STEP_END

redis.close
puts "\nVector set tutorial completed successfully!"
Loading