Skip to content

Commit

Permalink
Merge pull request #9 from eprothro/consistency
Browse files Browse the repository at this point in the history
Consistency
  • Loading branch information
eprothro committed Apr 27, 2016
2 parents 28d919a + 81e0503 commit 245820f
Show file tree
Hide file tree
Showing 12 changed files with 318 additions and 24 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -120,6 +120,8 @@ class UserByUsernameQuery < Cassie::Query
select :users_by_username

where :username, :eq

consistency :quorum
end
```

Expand Down
2 changes: 1 addition & 1 deletion cassie.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'cassie'
s.version = '1.0.0.alpha.17'
s.version = '1.0.0.alpha.19'
s.summary = "Apache Cassandra application support"
s.description = <<-EOS.strip.gsub(/\s+/, ' ')
Cassie provides database configration, versioned migrations,
Expand Down
6 changes: 6 additions & 0 deletions lib/cassie/connection_handler/cluster.rb
@@ -1,6 +1,12 @@
require 'benchmark'

module Cassie::ConnectionHandler
# ## Cassie::ConnectionHandler::Cluster
#
# Adds cluster instance configuration and memoization.
#
# Include in any class or module that responds to `configuration` with
# a cassandra cluster options hash.
module Cluster

def cluster
Expand Down
56 changes: 56 additions & 0 deletions lib/cassie/queries/README.md
Expand Up @@ -148,6 +148,62 @@ or
end
```

#### Consistency configuration

The [consistency level](http://datastax.github.io/ruby-driver/v2.1.6/api/cassandra/#consistencies-constant) for a query is determined by your `Cassie::configuration` by default, falling to back to the `Cassandra` default if none is given.

```ruby
Cassie.configuration[:consistency]
=> nil

Cassie.cluster.instance_variable_get(:@execution_options).consistency
=> :one
```

A Cassie::Query looks for a consistency level defined on the object, subclass, then base class levels. If one is found, it will override the `Cassandra` default when the query is executed.

```ruby
select :posts_by_author_category

where :author_id, :eq
where :category, :eq, if: :filter_by_category?

def filter_by_category?
#true or false, as makes sense for your query
end

def consistency
#dynamically determine a query object's consistency level
if filter_by_category?
:quorum
else
super
end
end
```

```ruby
select :posts_by_author_category

where :author_id, :eq
where :category, :eq

consistency :quorum
```

```ruby
# lib/tasks/interesting_task.rake
require_relative "interesting_worker"

task :interesting_task do
Cassandra::Query.consistency = :all

InterestingWorker.new.perform
end


```

#### Object Mapping
For Selection Queries, resources are returned as structs by default for manipulation using accessor methods.

Expand Down
10 changes: 9 additions & 1 deletion lib/cassie/queries/logging/cql_execution_event.rb
Expand Up @@ -9,7 +9,7 @@ def duration # in milliseconds
end

def message
color "(#{duration.round(1)}ms) #{statement}"
color "(#{duration.round(1)}ms) #{statement} [#{consistency}]"
end

protected
Expand All @@ -29,6 +29,14 @@ def statement
end
end

def consistency
if execution_info
execution_info.consistency
else
"consistency level unknown"
end
end

def traced?
execution_info && !!trace
end
Expand Down
22 changes: 2 additions & 20 deletions lib/cassie/queries/statement.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/string/filters'
require 'active_support/hash_with_indifferent_access'
require_relative 'statement/execution'
require_relative 'statement/preparation'
require_relative 'statement/callbacks'
require_relative 'statement/limiting'
Expand All @@ -16,6 +17,7 @@ module Statement
extend ::ActiveSupport::Concern

included do
include Execution
include Preparation
include Callbacks
include Limiting
Expand All @@ -37,13 +39,6 @@ def table
self.class.table
end

# Executes the statment, populates result
# returns true or false indicating a successful execution or not
def execute
@result = session.execute(statement)
execution_successful?
end

# returns a CQL string, or a Cassandra::Statement
# that is ready for execution
def statement
Expand All @@ -60,19 +55,6 @@ def build_cql_and_bindings
end
end

def execution_successful?
#TODO: rethink this, it knows too much
raise "execution not complete, no results to parse" unless result

# empty select
return true if result.empty?

# failed upsert
return false if (!result.rows.first["[applied]"].nil?) && (result.rows.first["[applied]"] == false)

true
end

private

def eval_if_opt?(value)
Expand Down
33 changes: 33 additions & 0 deletions lib/cassie/queries/statement/consistency.rb
@@ -0,0 +1,33 @@
module Cassie::Queries::Statement
module Consistency
extend ActiveSupport::Concern

included do
attr_writer :consistency
end

module ClassMethods
def inherited(subclass)
subclass.consistency = consistency
super
end

def consistency=(val)
@consistency = val
end

def consistency(val=:get)
if val == :get
@consistency if defined?(@consistency)
else
self.consistency = val
end
end
end

def consistency
return @consistency if defined?(@consistency)
self.class.consistency
end
end
end
41 changes: 41 additions & 0 deletions lib/cassie/queries/statement/execution.rb
@@ -0,0 +1,41 @@
require_relative 'consistency'

module Cassie::Queries::Statement
module Execution
extend ActiveSupport::Concern

included do
include Consistency
end

# Executes the statment, populates result
# returns true or false indicating a successful execution or not
def execute
@result = session.execute(statement, execution_options)
execution_successful?
end

def execution_options
{}.tap do |opts|
#TODO: rework consistency module to be more
# abstract implementation for all execution options
opts[:consistency] = consistency if consistency
end
end

protected

def execution_successful?
#TODO: rethink this, it knows too much
raise "execution not complete, no results to parse" unless result

# empty select
return true if result.empty?

# failed upsert
return false if (!result.rows.first["[applied]"].nil?) && (result.rows.first["[applied]"] == false)

true
end
end
end
2 changes: 1 addition & 1 deletion lib/cassie/testing/fake/execution_info.rb
@@ -1,6 +1,6 @@
module Cassie::Testing::Fake
class ExecutionInfo
attr_reader :statement
attr_reader :statement, :consistency

def initialize(statement)
@statement = statement
Expand Down
6 changes: 5 additions & 1 deletion spec/lib/cassie/queries/logging/cql_execution_event_spec.rb
Expand Up @@ -16,9 +16,10 @@
let(:duration_sec){ duration_ms / 1000.0 }
let(:duration_ms){ 1.5 }
let(:payload){ {execution_info: execution_info} }
let(:execution_info) { double(statement: statement, trace: nil) }
let(:execution_info) { double(statement: statement, consistency: consistency, trace: nil) }
let(:statement){ Cassandra::Statements::Simple.new(cql) }
let(:cql){ 'some CQL' }
let(:consistency){ 'some consistency level' }

describe "#message" do
it "includes the duration" do
Expand All @@ -27,6 +28,9 @@
it "includes the statement" do
expect(object.message).to include(cql)
end
it "includes the consistency level" do
expect(object.message).to include(consistency)
end
end

end
120 changes: 120 additions & 0 deletions spec/lib/cassie/queries/statement/consistency_spec.rb
@@ -0,0 +1,120 @@
RSpec.describe Cassie::Queries::Statement::Consistency do
let(:base_class)do
Class.new do
include Cassie::Queries::Statement::Consistency
end
end
let(:subclass){ Class.new(base_class) }
let(:object) { subclass.new }
let(:consistency){ Cassandra::CONSISTENCIES.sample }
# let(:alt_consistency){ (Cassandra::CONSISTENCIES - [alt_consistency]).sample }
let(:default_consistency){ nil }

# before(:each){ @original = base_class.consistency }
# after(:each){ base_class.consistency = @original }
describe "BaseClass" do
describe ".consistency" do
it "defaults to nil" do
expect(base_class.consistency).to eq(default_consistency)
end

context "when set" do
it "overrides default" do
base_class.consistency = consistency
expect(base_class.consistency).to eq(consistency)
end
end
end
end

describe "SubClass" do
describe ".consistency" do
it "defaults to base default" do
expect(subclass.consistency).to eq(default_consistency)
end

context "when base class has been set" do
before(:each) { base_class.consistency = consistency }

it "inherits base class setting" do
expect(subclass.consistency).to eq(consistency)
end
end

context "when set with setter" do
before(:each){ subclass.consistency = consistency }

it "overrides default" do
expect(subclass.consistency).to eq(consistency)
end
it "doesn't change base class" do
expect(base_class.consistency).to eq(default_consistency)
end
end

context "when set with getter for DSL feel" do
before(:each){ subclass.consistency(consistency) }

it "overrides default" do
expect(subclass.consistency).to eq(consistency)
end
it "doesn't change base class" do
expect(base_class.consistency).to eq(default_consistency)
end
end

context "when overwritten" do
let(:consistency){ :three }
let(:subclass) do
Class.new(base_class) do
def self.consistency
:three
end
end
end

it "overrides base class setting" do
expect(subclass.consistency).to eq(consistency)
end
it "doesn't change base class" do
expect(base_class.consistency).to eq(default_consistency)
end
end
end

describe "#consistency" do
it "defaults to base_class value" do
expect(object.consistency).to eq(default_consistency)
end

context "when set" do
before(:each){ object.consistency = consistency }

it "overrides default" do
expect(object.consistency).to eq(consistency)
end
it "doesn't change subclass" do
expect(subclass.consistency).to eq(default_consistency)
end
it "doesn't change base class" do
expect(base_class.consistency).to eq(default_consistency)
end

context "when .consistency overwritten" do
let(:consistency){ :three }
let(:subclass) do
Class.new(base_class) do
def self.consistency
:three
end
end
end

it "uses object value" do
expect(object.consistency).to eq(consistency)
end
end
end
end
end
end

0 comments on commit 245820f

Please sign in to comment.