cassie 1.0.0.alpha.3 → 1.0.0.alpha.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 970ee1be69be9f6ac3b0827c0b03d5dadbd704ee
4
- data.tar.gz: a1d420963e62bc82535a30959b3ff6e15ff7bf86
3
+ metadata.gz: b525f6072394101c053af5d655136f44cfda52a1
4
+ data.tar.gz: b70d7576dfd3b36ee4958f5e75dbfee6ce9c16ed
5
5
  SHA512:
6
- metadata.gz: 21d680ba347b41154d12d3cdc5afd6fe4d0a7df8abce59cf550c83f9a589407750574119b738f3208418604fc4ddfb6e6d515ad0b87b6259a2651355ac48ec48
7
- data.tar.gz: d753f55aa66ebc96c6d14b01300ec5efd66fca989378227f5c16a1cf92351076793211c4f1961203ee898c7bf9bb4a7bbb494feb0eb1167f484254dbce77b730
6
+ metadata.gz: 3b345d2bb1febc085c96b5deca61fcd61862242d8ea02e17c8ce7ddc2015c8cfa95af8d51ccb52b7714ed9b1ac200f678194d7b2c1a9200ba056992b0126c88a
7
+ data.tar.gz: d12cda51a8148b416963326eaebad65b67df3e096fc95e887ec6ff15802a36f052b42d0147e49f53515e9934ba2d842add0720521230ef704ce4b059dcc8c4eb
@@ -0,0 +1,24 @@
1
+ module Cassie::Queries
2
+ module Instrumentation
3
+ extend ::ActiveSupport::Concern
4
+
5
+ def execute
6
+ instrument { super }
7
+ end
8
+
9
+ def instrument #:nodoc:
10
+ instrumenter.instrument("cql.execute") do |payload|
11
+ execution_val = yield # execution populates #result
12
+
13
+ payload[:execution_info] = result.execution_info if result.respond_to?(:execution_info)
14
+ execution_val
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ def instrumenter
21
+ ActiveSupport::Notifications
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ module Cassie::Queries::Logging
2
+ class CqlExecutionEvent < ActiveSupport::Notifications::Event
3
+
4
+ def duration # in milliseconds
5
+ return super unless traced?
6
+
7
+ # trace duration is in microseconds
8
+ trace.duration / 1000.0
9
+ end
10
+
11
+ def message
12
+ color "(#{duration.round(1)}ms) #{statement}"
13
+ end
14
+
15
+ protected
16
+
17
+ def execution_info
18
+ payload[:execution_info]
19
+ end
20
+
21
+ def statement
22
+ if execution_info
23
+ statement = execution_info.statement
24
+ str = statement.cql
25
+ str += " [#{statement.params}]" if statement.respond_to? :params
26
+ str
27
+ else
28
+ "CQL executed: (`execution_info` was not present?)"
29
+ end
30
+ end
31
+
32
+ def traced?
33
+ execution_info && !!trace
34
+ end
35
+
36
+ def trace
37
+ execution_info.trace
38
+ end
39
+
40
+ def color(message)
41
+ "\e[1m\e[37m#{message}\e[0m\e[22m"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ module Cassie::Queries
2
+ module Logging
3
+
4
+ def self.logger
5
+ @logger ||= init_logger
6
+ end
7
+
8
+ def self.logger=(new_logger)
9
+ @logger = new_logger || Logger.new('/dev/null')
10
+ end
11
+
12
+ def self.init_logger(target = STDOUT)
13
+ previous_logger = defined?(@logger) ? @logger : nil
14
+
15
+ @logger = Logger.new(target)
16
+ @logger.level = Logger::INFO
17
+ @logger.formatter = log_formatter
18
+
19
+ previous_logger.close if previous_logger
20
+ @logger
21
+ end
22
+
23
+ def self.log_formatter
24
+ ActiveSupport::Logger::SimpleFormatter.new
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'cql_execution_event'
2
+
3
+ module Cassie::Queries::Logging
4
+ module Subscription
5
+ extend ::ActiveSupport::Concern
6
+
7
+ included do
8
+ ActiveSupport::Notifications.subscribe('cql.execute') do |*args|
9
+ # args:
10
+ # name # => String, name of the event (such as 'render' from above)
11
+ # start # => Time, when the instrumented block started execution
12
+ # finish # => Time, when the instrumented block ended execution
13
+ # id # => String, unique ID for this notification
14
+ # payload # => Hash, the payload
15
+ # [:exception] => if raised during event
16
+ unless args.last[:exception]
17
+ logger.debug(CqlExecutionEvent.new(*args).message)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'logging/logger'
2
+ require_relative 'logging/subscription'
3
+
4
+ module Cassie::Queries
5
+ module Logging
6
+ extend ::ActiveSupport::Concern
7
+
8
+ included do
9
+ include Subscription
10
+ end
11
+
12
+ module ClassMethods
13
+ def logger
14
+ Cassie::Queries::Logging.logger
15
+ end
16
+ end
17
+
18
+ def logger
19
+ self.class.logger
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module Cassie::Queries::Pagination
2
+ module Cursors
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def max_cursor(field, opts={})
7
+ relation field, :lteq, opts
8
+ end
9
+
10
+ def since_cursor(field, opts={})
11
+ relation field, :gt, opts
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Cassie::Queries::Pagination
2
+ module PageSize
3
+ def self.default
4
+ 100
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'pagination/cursors'
2
+ require_relative 'pagination/page_size'
3
+
4
+ module Cassie::Queries
5
+ module Pagination
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Cursors
10
+ end
11
+
12
+ module ClassMethods
13
+ def inherited(subclass)
14
+ subclass.page_size = page_size
15
+ super
16
+ end
17
+
18
+ def page_size
19
+ return @page_size if defined?(@page_size)
20
+ Cassie::Queries::Pagination::PageSize.default
21
+ end
22
+
23
+ def page_size=(val)
24
+ @page_size = val
25
+ end
26
+ end
27
+
28
+ def page_size
29
+ return @page_size if defined?(@page_size)
30
+ self.class.page_size
31
+ end
32
+
33
+ def page_size=(val)
34
+ @page_size = val
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ module Cassie::Queries
2
+ module Session
3
+ extend ::ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def session
7
+ # until cassie-configuration exists,
8
+ # we're relying on the client to
9
+ # supply the session
10
+ if defined?(super)
11
+ super
12
+ else
13
+ raise "Oops! Cassie::Queries doesn't manage a Cassandra session for you, yet. You must provide a .session class method that returns a valid session."
14
+ end
15
+ end
16
+ end
17
+
18
+ def session
19
+ self.class.session
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module Cassie::Queries::Statement
2
+ # set "username = ?", value: :username
3
+ # set "favs = favs + ?" value: "{ 'movie' : 'Cassablanca' }"
4
+ # set :username
5
+ class Assignment
6
+ # https://cassandra.apache.org/doc/cql3/CQL.html#updateStmt
7
+
8
+ attr_reader :identifier
9
+
10
+ def initialize(identifier, opts={})
11
+ if String === identifier
12
+ # custom assignment is being defined:
13
+ #
14
+ # `assign "username = ?", value: :username`
15
+ @cql = identifier
16
+ @custom = true
17
+ else
18
+ @identifier = identifier
19
+ end
20
+ end
21
+
22
+ def to_insert_cql
23
+ return @cql if defined?(@cql)
24
+ identifier
25
+ end
26
+
27
+ def to_update_cql
28
+ return @cql if defined?(@cql)
29
+ "#{identifier} = ?"
30
+ end
31
+
32
+ def custom?
33
+ !!@custom
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,67 @@
1
+ require_relative 'assignment'
2
+ require_relative 'mapping'
3
+
4
+ module Cassie::Queries::Statement
5
+ module Assignments
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Mapping
10
+ end
11
+
12
+ module ClassMethods
13
+ def set(identifier, opts={})
14
+ assignment = Assignment.new(identifier, opts)
15
+ opts[:value] ||= identifier.to_sym
16
+
17
+ if Symbol === opts[:value]
18
+ define_term_methods(opts[:value])
19
+ end
20
+
21
+ assignments[assignment] = opts
22
+ end
23
+
24
+ def assignments
25
+ @assignments ||= {}
26
+ end
27
+ end
28
+
29
+ def assignments
30
+ self.class.assignments
31
+ end
32
+
33
+ def build_update_and_bindings
34
+ cql = ""
35
+ bindings = []
36
+ assignment_strings = []
37
+
38
+ assignments.each do |a, opts|
39
+ if eval_if_opt?(opts[:if])
40
+ assignment_strings << a.to_update_cql
41
+ bindings << eval_value_opt(opts[:value])
42
+ end
43
+ end
44
+
45
+ cql = "#{assignment_strings.join(', ')}" unless bindings.empty?
46
+
47
+ [cql , bindings]
48
+ end
49
+
50
+ def build_insert_and_bindings
51
+ identifiers = []
52
+ bindings = []
53
+
54
+ assignments.each do |a, opts|
55
+ if eval_if_opt?(opts[:if])
56
+ identifiers << a.to_insert_cql
57
+ bindings << eval_value_opt(opts[:value])
58
+ end
59
+ end
60
+
61
+ identifiers_cql = "#{identifiers.join(', ')}"
62
+ terms_cql = Array.new(identifiers.count){"?"}.join(", ")
63
+
64
+ [identifiers_cql, terms_cql , bindings]
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,60 @@
1
+ require_relative 'relations'
2
+
3
+ module Cassie::Queries::Statement
4
+ module Deleting
5
+ extend ::ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ #TODO: accept block to add specific selectors and aliases
9
+ # select :table do |t|
10
+ # t.id
11
+ # t.name as: :username
12
+ # end
13
+ def delete(table)
14
+ include Relations
15
+
16
+ self.table = table
17
+ self.identifier = :delete
18
+
19
+ yield(self) if block_given?
20
+ end
21
+
22
+ # TODO rename to identifiers and extract
23
+ def selectors
24
+ @selectors ||= []
25
+ end
26
+ end
27
+
28
+ def delete(opts={})
29
+ execute
30
+ execution_successful?
31
+ end
32
+
33
+ protected
34
+
35
+ def build_delete_cql_and_bindings
36
+ where_str, bindings = build_where_and_bindings
37
+
38
+ cql = %(
39
+ DELETE #{build_delete_clause}
40
+ FROM #{table}
41
+ #{where_str}
42
+ ).squish + ";"
43
+
44
+ [cql, bindings]
45
+ end
46
+
47
+ # a select clause is built up of selectors
48
+ def selectors
49
+ self.class.selectors
50
+ end
51
+
52
+ def build_delete_clause
53
+ str = if selectors.empty?
54
+ ''
55
+ else
56
+ selectors.join(', ')
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ require_relative 'relation'
2
+ require_relative 'loading'
3
+
4
+ class Cassie::Queries::RecordNotFound < StandardError; end
5
+
6
+ module Cassie::Queries::Statement
7
+ module Fetching
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ include Loading
12
+ end
13
+
14
+ # Returns array of rows or empty array
15
+ #
16
+ # query.fetch(id: 1)
17
+ # => [{id: 1, name: 'eprothro'}]
18
+ def fetch(args={})
19
+ args.each do |k, v|
20
+ setter = "#{k}="
21
+ send(setter, v) if respond_to? setter
22
+ end
23
+
24
+ execute
25
+ result.rows
26
+ end
27
+
28
+ # Returns first result or nil
29
+ #
30
+ # query.find(id: 1)
31
+ # => {id: 1, name: 'eprothro'}
32
+ #
33
+ # query.find(id: 2)
34
+ # => nil
35
+ def find(args={})
36
+ old_limit = defined?(@limit) ? @limit : nil
37
+ self.limit = 1
38
+
39
+ fetch.first
40
+ ensure
41
+ self.limit = old_limit
42
+ end
43
+
44
+ # Returns first result or raises RecordNotFound
45
+ #
46
+ # query.find!(id: 1)
47
+ # => {id: 1, name: 'eprothro'}
48
+ #
49
+ # query.find!(id: 2)
50
+ # RecordNotFound: RecordNotFound
51
+ def find!(args={})
52
+ find || raise(Cassie::Queries::RecordNotFound)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'assignments'
2
+
3
+ module Cassie::Queries::Statement
4
+ module Inserting
5
+ extend ::ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def insert(table)
9
+ include Assignments
10
+
11
+ self.table = table
12
+ self.identifier = :insert
13
+
14
+ yield(self) if block_given?
15
+ end
16
+ end
17
+
18
+ def insert(opts={})
19
+ execute
20
+ execution_successful?
21
+ end
22
+
23
+ protected
24
+
25
+ def build_insert_cql_and_bindings
26
+ identifiers_str, terms_str, bindings = build_insert_and_bindings
27
+
28
+ cql = %(
29
+ INSERT INTO #{table}
30
+ (#{identifiers_str})
31
+ VALUES (#{terms_str})
32
+ ).squish + ";"
33
+
34
+ [cql, bindings]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ module Cassie::Queries::Statement
2
+ module Limiting
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attr_accessor :limit
7
+
8
+ class << self
9
+ attr_accessor :limit
10
+ end
11
+ end
12
+
13
+ def limit
14
+ @limit || self.class.limit
15
+ end
16
+
17
+ protected
18
+
19
+ def build_limit_str
20
+ return "" if limit.nil?
21
+
22
+ "LIMIT #{limit}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ module Cassie::Queries::Statement
2
+ module Loading
3
+ extend ActiveSupport::Concern
4
+
5
+ def fetch(args={})
6
+ rows = super(args)
7
+ rows.map {|r| build_resource(r) }
8
+ end
9
+
10
+ # When class doesn't override
11
+ # simply return a struct with the row data
12
+ def build_resource(row)
13
+ Struct.new(*row.keys).new(*row.values)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,66 @@
1
+ module Cassie::Queries::Statement
2
+ module Mapping
3
+ extend ActiveSupport::Concern
4
+
5
+ MAPPED_METHODS = [:insert, :update, :delete].freeze
6
+
7
+ included do
8
+ MAPPED_METHODS.each do |method|
9
+ next unless method_defined?(method)
10
+
11
+ define_method(method) do |resource=nil, opts={}|
12
+ return super(opts) if _resource.nil? && resource.nil?
13
+
14
+ self._resource = resource
15
+
16
+ if super(opts)
17
+ _resource
18
+ else
19
+ false
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def map_from(resource_name)
27
+ attr_accessor resource_name
28
+
29
+ define_method "_resource" do
30
+ send resource_name
31
+ end
32
+
33
+ define_method "_resource=" do |val|
34
+ send("#{resource_name}=", val)
35
+ end
36
+ end
37
+
38
+ protected
39
+
40
+ def define_term_methods(field)
41
+ getter = field
42
+ setter = "#{field}="
43
+
44
+ if method_defined?(getter) || method_defined?(setter)
45
+ raise "accessor or getter already defined for #{field}. Fix the collisions by using the `:value` option."
46
+ else
47
+ # Order of prefrence for finding term value
48
+ # 1. overriden getter instance method
49
+ # 2. value set by setter instance method
50
+ # 3. (Eventually) Mapping getter instance method
51
+ # 4. instance resource getter instance method
52
+ define_method getter do
53
+ if instance_variable_defined?("@#{field}")
54
+ return instance_variable_get("@#{field}")
55
+ end
56
+ _resource.send(field) if(_resource && _resource.respond_to?(field))
57
+ end
58
+
59
+ define_method setter do |val|
60
+ instance_variable_set("@#{field}", val)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,11 @@
1
+ module Cassie::Queries::Statement
2
+ module Ordering
3
+ extend ActiveSupport::Concern
4
+
5
+ protected
6
+
7
+ def build_order_str
8
+ ""
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,55 @@
1
+ module Cassie::Queries::Statement
2
+ module Preparation
3
+ class Cache
4
+ attr_reader :data
5
+
6
+ def initialize
7
+ clear
8
+ @monitor = Monitor.new
9
+ end
10
+
11
+ def write(key, value)
12
+ synchronize do
13
+ @data[key] = value
14
+ end
15
+ end
16
+ def read(key)
17
+ synchronize do
18
+ data[key]
19
+ end
20
+ end
21
+ def fetch(key)
22
+ return read(key) if data.has_key?(key)
23
+ write(key, yield) if block_given?
24
+ end
25
+ def clear
26
+ @data = {}
27
+ end
28
+ def close
29
+ clear
30
+ end
31
+
32
+ private
33
+
34
+ def synchronize(&block)
35
+ @monitor.synchronize(&block)
36
+ end
37
+ end
38
+
39
+ def self.cache
40
+ @cache ||= init_cache
41
+ end
42
+
43
+ def self.init_cache
44
+ previous_cache = defined?(@cache) ? @cache : nil
45
+
46
+ #TODO: research why memory story is blowing up when
47
+ # serializing the Cassandra prepared statement result
48
+ # @cache = ActiveSupport::Cache::MemoryStore.new
49
+ @cache = Cache.new
50
+
51
+ previous_cache.close if previous_cache
52
+ @cache
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ require_relative 'preparation/cache'
2
+
3
+ module Cassie::Queries::Statement
4
+ module Preparation
5
+ extend ::ActiveSupport::Concern
6
+
7
+ included do
8
+ class << self
9
+ attr_accessor :prepare
10
+ end
11
+ self.prepare = true
12
+ end
13
+
14
+ module ClassMethods
15
+ def inherited(subclass)
16
+ subclass.prepare = prepare
17
+ super
18
+ end
19
+
20
+ def prepare?
21
+ !!prepare
22
+ end
23
+ end
24
+
25
+ def statement
26
+ statement = super
27
+
28
+ if self.class.prepare?
29
+ key = statement.cql if statement.respond_to?(:cql)
30
+ key ||= statement.to_s
31
+
32
+ unbound = statement_cache.fetch(key) do
33
+ session.prepare(statement)
34
+ end
35
+ unbound.bind(statement.params)
36
+ else
37
+ statement
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def statement_cache
44
+ Cassie::Queries::Statement::Preparation.cache
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,59 @@
1
+ module Cassie::Queries::Statement
2
+ #
3
+ #
4
+ # relation "username = ?", value: :username
5
+ # relation :username, :eq
6
+ # relation :username, :eq, value: :method
7
+ # relation :phone, :in
8
+ class Relation
9
+ # https://cassandra.apache.org/doc/cql3/CQL.html#selectStmt
10
+
11
+ OPERATIONS = {
12
+ eq: "=",
13
+ lt: "<",
14
+ lteq: "<=",
15
+ gt: ">",
16
+ gteq: ">=",
17
+ contains: "CONTAINS",
18
+ contains_key: "CONTAINS KEY",
19
+ }
20
+
21
+ attr_reader :identifier,
22
+ :op_type
23
+
24
+ def initialize(identifier, op_type, opts={})
25
+ if Hash === op_type
26
+ # custom relation is being defined:
27
+ # `relation "username = ?", value: :username`
28
+
29
+ # swap the 2nd arg that sucked in options hash
30
+ opts.merge!(op_type)
31
+
32
+ @cql = identifier
33
+ @custom = true
34
+ else
35
+ @identifier = identifier
36
+ @op_type = op_type.to_sym
37
+ end
38
+ end
39
+
40
+ def custom?
41
+ !!@custom
42
+ end
43
+
44
+ def to_cql
45
+ cql
46
+ end
47
+
48
+ protected
49
+
50
+ def cql
51
+ # we always generate bound statements
52
+ @cql ||= "#{identifier} #{OPERATIONS[op_type]} ?"
53
+ end
54
+
55
+ def op_type
56
+ @op_type
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'relation'
2
+
3
+ module Cassie::Queries::Statement
4
+ module Relations
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # where :username, :eq, value: :method
9
+ # where :phone, :in
10
+ # were "username = ?", value: :username
11
+ def where(identifier, op, opts={})
12
+ relation = Relation.new(identifier, op, opts)
13
+
14
+ opts[:value] ||= identifier.to_sym
15
+
16
+ if Symbol === opts[:value]
17
+ define_term_methods(opts[:value])
18
+ end
19
+
20
+ relations[relation] = opts
21
+ end
22
+
23
+ # a where clause is built up of multiple 'relations'
24
+ def relations
25
+ @relations ||= {}
26
+ end
27
+
28
+ private
29
+
30
+ def define_term_methods(name)
31
+ #TODO: this should probably only raise
32
+ # if value option was nil and we
33
+ # are implicilty creating getter/setters.
34
+ if method_defined?(name) || method_defined?("#{name}=")
35
+ raise "accessor or getter already defined for #{name}. Fix the collions by using the `:value` option."
36
+ else
37
+ attr_accessor name
38
+ end
39
+ end
40
+ end
41
+
42
+ # a where clause is built up of multiple 'relations'
43
+ def relations
44
+ self.class.relations
45
+ end
46
+
47
+ def build_where_and_bindings
48
+ cql = ""
49
+ bindings = []
50
+ relation_strings = []
51
+
52
+ relations.each do |r, opts|
53
+ if eval_if_opt?(opts[:if])
54
+ relation_strings << r.to_cql
55
+ bindings << eval_value_opt(opts[:value])
56
+ end
57
+ end
58
+
59
+ cql = "WHERE #{relation_strings.join(' AND ')}" unless bindings.empty?
60
+
61
+ [cql , bindings]
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,63 @@
1
+ require_relative 'relations'
2
+ require_relative 'limiting'
3
+ require_relative 'ordering'
4
+ require_relative 'fetching'
5
+
6
+ module Cassie::Queries::Statement
7
+ module Selection
8
+ extend ::ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ #TODO: accept block to add specific selectors and aliases
12
+ # select :table do |t|
13
+ # t.id
14
+ # t.name as: :username
15
+ # end
16
+ def select(table)
17
+ include Relations
18
+ include Limiting
19
+ include Ordering
20
+ include Fetching
21
+
22
+ self.table = table
23
+ self.identifier = :select
24
+
25
+ yield(self) if block_given?
26
+ end
27
+
28
+ # a select clause is built up of selectors
29
+ def selectors
30
+ @selectors ||= []
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def build_select_cql_and_bindings
37
+ where_str, bindings = build_where_and_bindings
38
+
39
+ cql = %(
40
+ SELECT #{build_select_clause}
41
+ FROM #{table}
42
+ #{where_str}
43
+ #{build_order_str}
44
+ #{build_limit_str}
45
+ ).squish + ";"
46
+
47
+ [cql, bindings]
48
+ end
49
+
50
+ # a select clause is built up of selectors
51
+ def selectors
52
+ self.class.selectors
53
+ end
54
+
55
+ def build_select_clause
56
+ str = if selectors.empty?
57
+ '*'
58
+ else
59
+ selectors.join(', ')
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
1
+ require_relative 'assignments'
2
+
3
+ module Cassie::Queries::Statement
4
+ module Updating
5
+ extend ::ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def update(table)
9
+ include Relations
10
+ include Assignments
11
+
12
+ self.table = table
13
+ self.identifier = :update
14
+
15
+ yield(self) if block_given?
16
+ end
17
+ end
18
+
19
+ def update(opts={})
20
+ execute
21
+ execution_successful?
22
+ end
23
+
24
+ protected
25
+
26
+ def build_update_cql_and_bindings
27
+ assignment_str, update_bindings = build_update_and_bindings
28
+ where_str, where_bindings = build_where_and_bindings
29
+
30
+ cql = %(
31
+ UPDATE #{table}
32
+ SET #{assignment_str}
33
+ #{where_str}
34
+ ).squish + ";"
35
+
36
+ [cql, update_bindings + where_bindings]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,99 @@
1
+ require 'active_support/core_ext/string/filters'
2
+ require_relative 'statement/preparation'
3
+ require_relative 'statement/selection'
4
+ require_relative 'statement/deleting'
5
+ require_relative 'statement/updating'
6
+ require_relative 'statement/inserting'
7
+
8
+
9
+ module Cassie::Queries
10
+ module Statement
11
+ # https://cassandra.apache.org/doc/cql3/CQL.html#selectStmt
12
+ extend ::ActiveSupport::Concern
13
+
14
+ included do
15
+ include Preparation
16
+ include Selection
17
+ include Deleting
18
+ include Updating
19
+ include Inserting
20
+
21
+ attr_reader :result
22
+
23
+ class << self
24
+ attr_accessor :table
25
+ attr_accessor :identifier
26
+ end
27
+ end
28
+
29
+ def table
30
+ self.class.table
31
+ end
32
+
33
+ # Executes the statment, populates result
34
+ # returns true or false indicating a successful execution or not
35
+ def execute
36
+ @result = session.execute(statement)
37
+ execution_successful?
38
+ end
39
+
40
+ # returns a CQL string, or a Cassandra::Statement
41
+ # that is ready for execution
42
+ def statement
43
+ Cassandra::Statements::Simple.new(*build_cql_and_bindings)
44
+ end
45
+
46
+ protected
47
+
48
+ def build_cql_and_bindings
49
+ if identifier
50
+ send "build_#{identifier}_cql_and_bindings"
51
+ else
52
+ [cql, bindings]
53
+ end
54
+ end
55
+
56
+ def execution_successful?
57
+ #TODO: rethink this, it knows too much
58
+ raise "execution not complete, no results to parse" unless result
59
+
60
+ # empty select
61
+ return true if result.empty?
62
+
63
+ # failed upsert
64
+ return false if (!result.rows.first["[applied]"].nil?) && (result.rows.first["[applied]"] == false)
65
+
66
+ true
67
+ end
68
+
69
+ private
70
+
71
+ def identifier
72
+ self.class.identifier
73
+ end
74
+
75
+ def eval_if_opt?(value)
76
+ case value
77
+ when nil
78
+ true # if is true by default
79
+ when Symbol
80
+ !!send(value)
81
+ when String
82
+ !!eval(value)
83
+ else
84
+ !!value
85
+ end
86
+ end
87
+
88
+ def eval_value_opt(value)
89
+ case value
90
+ when Symbol
91
+ send(value)
92
+ when String
93
+ eval(value)
94
+ else
95
+ value
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,36 @@
1
+ module Cassie
2
+ # Active Support used for
3
+ # * include convenience via ActiveSupport::Concern
4
+ # * string extensions
5
+ # * notification pub/sub
6
+ # * log formatting
7
+ #
8
+ # We require/autoload extensions only as needed,
9
+ # this base require has almost no overhead
10
+ #
11
+ # http://guides.rubyonrails.org/active_support_core_extensions.html
12
+ require 'active_support'
13
+ require 'cassandra'
14
+ require_relative 'queries/session'
15
+ require_relative 'queries/statement'
16
+ require_relative 'queries/pagination'
17
+ require_relative 'queries/instrumentation'
18
+ require_relative 'queries/logging'
19
+
20
+ class Query
21
+ include Queries::Session
22
+ include Queries::Statement
23
+ include Queries::Pagination
24
+ include Queries::Instrumentation
25
+ include Queries::Logging
26
+
27
+ def initialize(*args)
28
+ value = super(*args)
29
+ after_initialize(*args)
30
+ value
31
+ end
32
+
33
+ def after_initialize(*args)
34
+ end
35
+ end
36
+ end
data/lib/cassie.rb CHANGED
@@ -1,8 +1 @@
1
- # components are included via
2
- # separate gems for simpler
3
- # extensibility and the help
4
- # promote loose coupling
5
-
6
- # require 'cassie-configuration'
7
- # require 'cassie-migrations'
8
- require 'cassie-queries'
1
+ require_relative 'cassie/query'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cassie
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha.3
4
+ version: 1.0.0.alpha.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Prothro
@@ -11,19 +11,33 @@ cert_chain: []
11
11
  date: 2016-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: cassie-queries
14
+ name: cassandra-driver
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.0.0.alpha
19
+ version: '2.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.0.0.alpha
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -76,6 +90,32 @@ extensions: []
76
90
  extra_rdoc_files: []
77
91
  files:
78
92
  - lib/cassie.rb
93
+ - lib/cassie/queries/instrumentation.rb
94
+ - lib/cassie/queries/logging.rb
95
+ - lib/cassie/queries/logging/cql_execution_event.rb
96
+ - lib/cassie/queries/logging/logger.rb
97
+ - lib/cassie/queries/logging/subscription.rb
98
+ - lib/cassie/queries/pagination.rb
99
+ - lib/cassie/queries/pagination/cursors.rb
100
+ - lib/cassie/queries/pagination/page_size.rb
101
+ - lib/cassie/queries/session.rb
102
+ - lib/cassie/queries/statement.rb
103
+ - lib/cassie/queries/statement/assignment.rb
104
+ - lib/cassie/queries/statement/assignments.rb
105
+ - lib/cassie/queries/statement/deleting.rb
106
+ - lib/cassie/queries/statement/fetching.rb
107
+ - lib/cassie/queries/statement/inserting.rb
108
+ - lib/cassie/queries/statement/limiting.rb
109
+ - lib/cassie/queries/statement/loading.rb
110
+ - lib/cassie/queries/statement/mapping.rb
111
+ - lib/cassie/queries/statement/ordering.rb
112
+ - lib/cassie/queries/statement/preparation.rb
113
+ - lib/cassie/queries/statement/preparation/cache.rb
114
+ - lib/cassie/queries/statement/relation.rb
115
+ - lib/cassie/queries/statement/relations.rb
116
+ - lib/cassie/queries/statement/selection.rb
117
+ - lib/cassie/queries/statement/updating.rb
118
+ - lib/cassie/query.rb
79
119
  homepage: https://github.com/eprothro/cassie
80
120
  licenses:
81
121
  - MIT