Skip to content

Commit

Permalink
Merge pull request #25 from theablefew/feature/opensearch_compatibility
Browse files Browse the repository at this point in the history
Opensearch compatibility
  • Loading branch information
esmarkowski committed Mar 6, 2024
2 parents 8c7b301 + b732aea commit 54df41f
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 11 deletions.
46 changes: 45 additions & 1 deletion .github/workflows/spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:

jobs:
test:
elasticsearch:

runs-on: ${{matrix.os}}-latest

Expand Down Expand Up @@ -37,3 +37,47 @@ jobs:
run: bundle install
- name: Run tests
run: bundle exec rspec

opensearch:

runs-on: ${{matrix.os}}-latest
# services:
# opensearch:
# image: opensearchproject/opensearch:2
# ports:
# - 9200:9200
# env:
# discovery.type: single-node
# OPENSEARCH_JAVA_OPTS: "-Xms512m -Xmx512m"
# DISABLE_INSTALL_DEMO_CONFIG: true
# DISABLE_SECURITY_PLUGIN: true
# bootstrap.memory_lock: true

strategy:
matrix:
os: ['ubuntu']
ruby: ['3.1']
opensearch: ['2.12.0']

env:
BACKEND: opensearch
OPENSEARCH_JAVA_OPTS: "-Xms512m -Xmx512m"
DISABLE_INSTALL_DEMO_CONFIG: true

steps:
- uses: actions/checkout@v4
- name: Set up OpenSearch ${{ matrix.opensearch }}
uses: theablefew/opensearch-github-actions/opensearch@main
with:
version: ${{ matrix.opensearch }}
security-disabled: true

- name: Set up Ruby ${{ matrix.ruby }}
uses: ruby/setup-ruby@ec02537da5712d66d4d50a0f33b7eb52773b5ed1
with:
ruby-version: ${{ matrix.ruby }}

- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec rspec
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
/tmp/
PLAN.md
.irb-history
Gemfile.lock
Gemfile.lock
.gem
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ After checking out the repo, run `bin/setup` to install dependencies. You can al
> Full documentation on [Elasticsearch Query DSL and Aggregation options](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-persistence)
## Testing
<details>
<summary>Elasticsearch</summary>


```
docker-compose up elasticsearch
```
Expand All @@ -162,6 +166,21 @@ docker-compose up elasticsearch
bundle exec rspec
```

</details>

<details>
<summary>Opensearch</summary>


```
docker-compose up opensearch
```

```
ENV['BACKEND']=opensearch bundle rspec
```
</details>

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/theablefew/stretchy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/theablefew/stretchy/blob/master/CODE_OF_CONDUCT.md).
Expand Down
19 changes: 18 additions & 1 deletion lib/stretchy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,25 @@ class QueryOptionMissing < StandardError; end
class Configuration

attr_accessor :client
attr_accessor :opensearch

def initialize
@client = Elasticsearch::Client.new url: 'http://localhost:9200'
@opensearch = false
end

def client=(client)
@client = client
self.opensearch = true if @client.class.name =~ /OpenSearch/
end

def opensearch=(bool)
@opensearch = bool
OpenSearchCompatibility.opensearch_patch! if bool
end

def opensearch?
@opensearch
end

end
Expand All @@ -45,9 +61,10 @@ def configure
end
end


end



loader = Zeitwerk::Loader.new
loader.tag = File.basename(__FILE__, ".rb")
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
Expand Down
86 changes: 86 additions & 0 deletions lib/stretchy/open_search_compatibility.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
module Stretchy
module OpenSearchCompatibility
extend ActiveSupport::Concern

# Patches the Elasticsearch::Persistence::Repository::Search module to remove the
# document type from the request for compatability with OpenSearch
def self.opensearch_patch!
patch = Module.new do
def search(query_or_definition, options={})
request = { index: index_name }

if query_or_definition.respond_to?(:to_hash)
request[:body] = query_or_definition.to_hash
elsif query_or_definition.is_a?(String)
request[:q] = query_or_definition
else
raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String" +
" -- #{query_or_definition.class} given."
end

Elasticsearch::Persistence::Repository::Response::Results.new(self, client.search(request.merge(options)))
end

def count(query_or_definition=nil, options={})
query_or_definition ||= { query: { match_all: {} } }
request = { index: index_name}

if query_or_definition.respond_to?(:to_hash)
request[:body] = query_or_definition.to_hash
elsif query_or_definition.is_a?(String)
request[:q] = query_or_definition
else
raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String" +
" -- #{query_or_definition.class} given."
end

client.count(request.merge(options))['count']
end
end

store = Module.new do
def save(document, options={})
serialized = serialize(document)
id = __get_id_from_document(serialized)
request = { index: index_name,
id: id,
body: serialized }
client.index(request.merge(options))
end


def update(document_or_id, options = {})
if document_or_id.is_a?(String) || document_or_id.is_a?(Integer)
id = document_or_id
body = options
else
document = serialize(document_or_id)
id = __extract_id_from_document(document)
if options[:script]
body = options
else
body = { doc: document }.merge(options)
end
end
client.update(index: index_name, id: id, body: body)
end

def delete(document_or_id, options = {})
if document_or_id.is_a?(String) || document_or_id.is_a?(Integer)
id = document_or_id
else
serialized = serialize(document_or_id)
id = __get_id_from_document(serialized)
end
client.delete({ index: index_name, id: id }.merge(options))
end
end


::Elasticsearch::Persistence::Repository.send(:include, patch)
::Elasticsearch::Persistence::Repository.send(:include, store)
end


end
end
22 changes: 21 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,30 @@
end
require 'stretchy'
require 'active_support/core_ext'
# require 'elasticsearch/persistence'

backend = ENV['BACKEND'] || 'elasticsearch'

if backend == 'opensearch'
require 'opensearch'
Stretchy.configure do |config|
config.client = OpenSearch::Client.new(
host: 'http://localhost:9200',
user: 'admin',
password: 'admin',
transport_options: { ssl: { verify: false } } # For testing only. Use certificate for validation.
)
end
else
# Configure for Elasticsearch
end



RSpec.configure do |config|
if ENV['BACKEND'] == 'opensearch'
config.filter_run_excluding opensearch_incompatible: true
end

# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
Expand Down
10 changes: 5 additions & 5 deletions spec/stretchy/aggregations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@
end

context 'boxplot' do
it 'returns boxplot statistics' do
it 'returns boxplot statistics', opensearch_incompatible: true do
boxplot_agg = described_class.size(0).aggregation(:age_stats, boxplot: {field: :age})
expect(boxplot_agg.aggregations.age_stats).to be_a(Hash)
expect(boxplot_agg.aggregations.age_stats).to include(:q1, :q3, :min, :max)
Expand Down Expand Up @@ -452,7 +452,7 @@
end

context 'rate' do
it 'returns rate' do
it 'returns rate', opensearch_incompatible: true do
rate_agg = described_class.size(0).aggregation(:by_date,
date_histogram: {
field: :created_at,
Expand Down Expand Up @@ -502,7 +502,7 @@
end

context 'string_stats' do
it 'returns string statistics' do
it 'returns string statistics', opensearch_incompatible: true do
string_stats_agg = described_class.size(0).aggregation(:position_stats, string_stats: {field: 'position.name'})
expect(string_stats_agg.aggregations.position_stats).to be_a(Hash)
expect(string_stats_agg.aggregations.position_stats).to include(:count, :min_length, :max_length, :avg_length, :entropy)
Expand All @@ -520,7 +520,7 @@
end

context 't_test' do
it 'returns t-test statistics' do
it 'returns t-test statistics', opensearch_incompatible: true do
t_test_agg = described_class.size(0).aggregation(:t_test,
t_test: {
a: {field: :income},
Expand Down Expand Up @@ -555,7 +555,7 @@
end

context 'top_metrics' do
it 'returns top metrics' do
it 'returns top metrics', opensearch_incompatible: true do
top_metrics_agg = described_class.size(0).aggregation(:top_metrics,
top_metrics: {
metrics: [{field: :income}, {field: :age}],
Expand Down
4 changes: 2 additions & 2 deletions spec/stretchy/instrumentation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
"(#{(finish.to_time - start.to_time).round(2)}ms)",
Stretchy::Utils.to_curl(payload[:klass].constantize, payload[:search])
].join(" ")
expect(message).to eq("Post(0.0ms) curl -XGET 'http://localhost:9200/posts/_search' -d '{\"query\":{\"term\":{\"title\":\"hello\"}}}'")
expect(message).to match(/Post\(0.0ms\) curl -XGET 'https?:\/\/localhost:9200\/posts\/_search' -d '\{\"query\":\{\"term\":\{\"title\":\"hello\"\}\}\}'/)
end
ActiveSupport::Notifications.unsubscribe(subscription)
end

it 'converts the payload to a curl command' do
curl = Stretchy::Utils.to_curl(Post, {index: Post.index_name, body: {query: {term: {title: "hello"}}}})
expect(curl).to eq("curl -XGET 'http://localhost:9200/posts/_search' -d '{\"query\":{\"term\":{\"title\":\"hello\"}}}'\n")
expect(curl).to match(%r{curl -XGET 'https?://localhost:9200/posts/_search' -d '\{"query":\{"term":\{"title":"hello"\}\}\}'\n})
end
end
1 change: 1 addition & 0 deletions stretchy-model.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "rspec", "~> 3.9"
spec.add_development_dependency "simplecov", "~> 0.21.2"
spec.add_development_dependency "yard", "~> 0.9.36"
spec.add_development_dependency "opensearch-ruby", "~> 3.0"
# For more information and examples about making a new gem, check out our
# guide at: https://bundler.io/guides/creating_gem.html
end

0 comments on commit 54df41f

Please sign in to comment.