ciql 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b9638c378c3dd96de3e453431fb768d2446fcafe
4
+ data.tar.gz: 77a6bcc2b812511957284dbc0ab319a9abe50cc4
5
+ SHA512:
6
+ metadata.gz: 83d476cf4eb7a62b9646a5b810ab0cd9c076eed1d00dec3b76468f9f8328bbf96ca7eccf64650254ff9541bf77e350b7a3ebec316369dc1b4715eab623bac4c2
7
+ data.tar.gz: 29afea8c01852fb176295b34357fd3b8c3c4ee714baddb738cc9b9d3ef19259c31f123efb3f317b42475403f6adf617542eaea9356518c039236e6ba5bc81714
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Ciql
2
+
3
+ [![Build Status](https://travis-ci.org/Nulu/ciql.png?branch=master)](https://travis-ci.org/Nulu/ciql) [![Dependency Status](https://gemnasium.com/Nulu/ciql.png)](https://gemnasium.com/Nulu/ciql)
4
+
5
+ This project is currently small support utilities and extensions for the [cql-rb](https://github.com/iconara/cql-rb) driver.
@@ -0,0 +1,21 @@
1
+ module Ciql
2
+ module Client
3
+ class AsynchronousClient < Cql::Client::AsynchronousClient
4
+ def execute(statement, *arguments)
5
+ bind_variables = arguments.shift statement.count('?')
6
+ bound_statement = Ciql::Sanitize.sanitize statement, *bind_variables
7
+ super(bound_statement, *arguments)
8
+ end
9
+ end
10
+
11
+ class SynchronousClient < Cql::Client::SynchronousClient
12
+ def execute(statement, *arguments)
13
+ @async_client.execute(statement, *arguments).get
14
+ end
15
+ end
16
+
17
+ def self.connect(options={})
18
+ SynchronousClient.new(AsynchronousClient.new(options)).connect
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ require 'ostruct'
2
+
3
+ module Ciql
4
+ @@client = nil
5
+ def self.client
6
+ @@client ||= Client.connect(configuration.to_options)
7
+ end
8
+
9
+ @@configuration = nil
10
+ def self.configuration
11
+ @@configuration ||= Configuration.new
12
+ end
13
+
14
+ def self.configure(&block)
15
+ yield configuration
16
+ end
17
+
18
+ class Configuration < OpenStruct
19
+ def initialize
20
+ super
21
+ self.hosts = []
22
+ end
23
+
24
+ def to_options
25
+ all = [host].concat(hosts).compact.reject(&:empty?)
26
+ self.hosts = []
27
+ self.host = all.join(',') unless all.empty?
28
+ self.marshal_dump.dup.tap { |hash| hash.delete(:hosts) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ require 'simple_uuid'
2
+
3
+ module Ciql
4
+ module Sanitize
5
+
6
+ class UnescapableObjectError < Ciql::Error; end
7
+ class InvalidBindVariableError < Ciql::Error; end
8
+
9
+ def self.sanitize(statement, *variables)
10
+ variables = variables.dup
11
+ expected = statement.count('?')
12
+
13
+ if expected != variables.size
14
+ raise InvalidBindVariableError,
15
+ "Wrong number of bound variables "\
16
+ "(statement expected #{expected}, "\
17
+ "was #{variables.size})"
18
+ end
19
+
20
+ statement.gsub(/\?/) { cast(variables.shift) }
21
+ end
22
+
23
+ private
24
+
25
+ def self.quote(string)
26
+ "'" + string.gsub("'", "''") + "'"
27
+ end
28
+
29
+ def self.cast(obj)
30
+ case obj
31
+ when Hash
32
+ obj.map do |key, value|
33
+ [cast(key), cast(value)].join(':')
34
+ end.join(',')
35
+
36
+ when Enumerable
37
+ obj.map { |member| cast(member) }.join(',')
38
+
39
+ when Numeric; obj
40
+ when DateTime; (obj.to_time.to_f * 1000).to_i
41
+ when Time; (obj.to_f * 1000).to_i
42
+ when Date; quote(obj.strftime('%Y-%m-%d'))
43
+
44
+ when ::Cql::Uuid; obj.to_s
45
+ when ::SimpleUUID::UUID; obj.to_guid
46
+
47
+ when String
48
+ if obj.encoding == ::Encoding::BINARY
49
+ '0x' + obj.unpack('H*').first
50
+ else
51
+ quote obj.encode(::Encoding::UTF_8)
52
+ end
53
+
54
+ else
55
+ quote obj.to_s.dup.force_encoding(::Encoding::BINARY)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module Ciql
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/lib/ciql.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'cql'
2
+
3
+ module Ciql
4
+ Error = Class.new(StandardError)
5
+ end
6
+
7
+ require 'ciql/configuration'
8
+ require 'ciql/sanitize'
9
+ require 'ciql/client'
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ module Ciql::Client
4
+ describe AsynchronousClient do
5
+ let(:client) { described_class.new }
6
+
7
+ describe '#execute' do
8
+ before do
9
+ client.instance_variable_set(:@connected, true)
10
+ client.stub(:execute_request) do |request|
11
+ Cql::Future.completed(request)
12
+ end
13
+ end
14
+
15
+ it 'returns a Cql::Future' do
16
+ client.execute('x').should be_kind_of Cql::Future
17
+ end
18
+
19
+ it 'binds query parameters' do
20
+ client.execute('a = ? and b = ?', "a'b", [1,2].pack('C*'))
21
+ .get.cql.should == "a = 'a''b' and b = 0x0102"
22
+ end
23
+
24
+ it 'uses an extra trailing argument as the consistency level' do
25
+ client.execute('update', :any).get.consistency.should == :any
26
+ client.execute('update ?', :any).get.consistency.should == :quorum
27
+ client.execute('update ?', :any, :one).get.consistency.should == :one
28
+ end
29
+ end
30
+ end
31
+
32
+ describe SynchronousClient do
33
+ let(:async_client) { mock('async client') }
34
+ let(:client) { described_class.new(async_client) }
35
+
36
+ describe '#execute' do
37
+ before do
38
+ async_client.should_receive(:execute) do |*arguments|
39
+ Cql::Future.completed(arguments)
40
+ end
41
+ end
42
+
43
+ it "returns the value of the async client's #execute result" do
44
+ client.execute('??', 'a', 'b', :two).should == ['??', 'a', 'b', :two]
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '.connect' do
50
+ let(:reactor) { FakeReactor.new }
51
+
52
+ before(:each) do
53
+ Cql::Io::IoReactor.stub(:new).and_return(reactor)
54
+ end
55
+
56
+ subject { Ciql::Client.connect(port: 4000) }
57
+
58
+ it 'returns a SynchronousClient' do
59
+ subject.should be_instance_of Ciql::Client::SynchronousClient
60
+ end
61
+
62
+ it 'passes the options to the internal async client' do
63
+ subject.async.instance_variable_get(:@port).should == 4000
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ module Ciql
4
+ describe '.client' do
5
+ let(:reactor) { FakeReactor.new }
6
+
7
+ before(:each) do
8
+ Cql::Io::IoReactor.stub(:new).and_return(reactor)
9
+ end
10
+
11
+ after(:each) do
12
+ Ciql.class_variable_set(:@@client, nil)
13
+ Ciql.class_variable_set(:@@configuration, nil)
14
+ end
15
+
16
+ it 'returns a Client instance' do
17
+ Ciql.client.should be_instance_of Ciql::Client::SynchronousClient
18
+ end
19
+
20
+ it 'always returns the same Client instance' do
21
+ Ciql.client.should be Ciql.client
22
+ end
23
+
24
+ it 'creates the client with the configured options' do
25
+ Ciql.configure { |c| c.port = 1234 }
26
+ Ciql::Client::AsynchronousClient.should_receive(:new).with(port: 1234).and_call_original
27
+ Ciql.client
28
+ end
29
+
30
+ it 'connects the client' do
31
+ Ciql.client.should be_connected
32
+ end
33
+ end
34
+
35
+ describe '.configuration' do
36
+ after(:each) do
37
+ Ciql.class_variable_set(:@@configuration, nil)
38
+ end
39
+
40
+ it 'returns a Configuration instance' do
41
+ Ciql.configuration.should be_instance_of Ciql::Configuration
42
+ end
43
+
44
+ it 'always returns the same Configuration instance' do
45
+ Ciql.configuration.should be Ciql.configuration
46
+ end
47
+ end
48
+
49
+ describe '.configure' do
50
+ it 'yields the Configuration instance returned by .configuration' do
51
+ Ciql.configure do |c|
52
+ c.should be Ciql.configuration
53
+ end
54
+ end
55
+ end
56
+
57
+ describe Configuration do
58
+ it 'supports property access via #name' do
59
+ subject.port = 5
60
+ subject.port.should == 5
61
+ end
62
+
63
+ it '#hosts is an array' do
64
+ subject.hosts << 'remote'
65
+ subject.hosts.should == ['remote']
66
+ end
67
+
68
+ describe '#to_options' do
69
+ it 'returns a Hash with the configured options as keys' do
70
+ subject.foo = 1
71
+ subject.bar = 'a'
72
+ subject.to_options.should == {foo: 1, bar: 'a'}
73
+ end
74
+
75
+ it 'sets #host with comma-separated string of #hosts entries' do
76
+ subject.hosts << 'local'
77
+ subject.hosts << 'remote'
78
+ subject.to_options[:host].should == 'local,remote'
79
+ end
80
+
81
+ it 'combines #host and #hosts into #host' do
82
+ subject.host = 'primary'
83
+ subject.hosts << 'secondary'
84
+ subject.to_options[:host].should == 'primary,secondary'
85
+ end
86
+
87
+ it 'does not include an entry for :hosts' do
88
+ subject.to_options[:hosts].should be_nil
89
+ end
90
+
91
+ it 'clears #hosts' do
92
+ subject.hosts << 'one'
93
+ subject.to_options
94
+ subject.hosts.should be_empty
95
+ end
96
+
97
+ it 'ignores nil values and empty strings' do
98
+ subject.hosts << nil
99
+ subject.hosts << ''
100
+ subject.hosts << 'server'
101
+ subject.to_options[:host].should == 'server'
102
+ end
103
+
104
+ it 'does not change #host if #hosts is empty' do
105
+ subject.to_options[:host].should == nil
106
+ subject.host = 'local'
107
+ subject.to_options[:host].should == 'local'
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ module Ciql
4
+ describe Sanitize do
5
+ describe 'when there are no placeholders in the statement' do
6
+ before do
7
+ @statement = 'select * from table'
8
+ end
9
+
10
+ describe 'and no variables are given' do
11
+ it 'returns the statement as-is' do
12
+ subject.sanitize(@statement).should == @statement
13
+ end
14
+ end
15
+
16
+ describe 'and one or more variables are given' do
17
+ it 'throws InvalidBindVariableError' do
18
+ expect {
19
+ subject.sanitize(@statement, 1, 2)
20
+ }.to raise_error(subject::InvalidBindVariableError)
21
+ end
22
+ end
23
+ end
24
+
25
+ describe 'when there are placeholders in the query' do
26
+ describe 'and too few variables are given' do
27
+ it 'throws InvalidBindVariableError' do
28
+ expect {
29
+ subject.sanitize('?')
30
+ }.to raise_error(subject::InvalidBindVariableError)
31
+
32
+ expect {
33
+ subject.sanitize('? ?', 1)
34
+ }.to raise_error(subject::InvalidBindVariableError)
35
+ end
36
+ end
37
+
38
+ describe 'and too many variables are given' do
39
+ it 'throws InvalidBindVariableError' do
40
+ expect {
41
+ subject.sanitize('? ?', 1, 2, 3)
42
+ }.to raise_error(subject::InvalidBindVariableError)
43
+ end
44
+ end
45
+
46
+ it 'replaces placeholders with the correct variable' do
47
+ subject.sanitize('? ?', 1, 2.1).should == '1 2.1'
48
+ end
49
+
50
+ it 'quotes strings' do
51
+ subject.sanitize('?', 'string').should == "'string'"
52
+ end
53
+
54
+ it 'escapes single quotes' do
55
+ subject.sanitize('?', "a'b").should == "'a''b'"
56
+ end
57
+
58
+ it 'converts dates' do
59
+ subject.sanitize('?', Date.new(2013, 3, 26))
60
+ .should == "'2013-03-26'"
61
+ end
62
+
63
+ it 'converts times' do
64
+ subject.sanitize('?', Time.new(2013, 3, 26, 23, 1, 2.544, 0))
65
+ .should == 1364338862544.to_s
66
+ end
67
+
68
+ it 'converts DateTime instances as a time' do
69
+ subject.sanitize('?', DateTime.new(2013, 3, 26, 23, 1, 2.544, 0))
70
+ .should == 1364338862544.to_s
71
+ end
72
+
73
+ it 'converts Cql::Uuid to a bare string representation' do
74
+ subject.sanitize('?', Cql::Uuid.new(2**127 - 1))
75
+ .should == "7fffffff-ffff-ffff-ffff-ffffffffffff"
76
+ end
77
+
78
+ it 'converts SimpleUUID::UUID to a bare string representation' do
79
+ subject.sanitize('?', SimpleUUID::UUID.new(2**127 - 1))
80
+ .should == "7fffffff-ffff-ffff-ffff-ffffffffffff"
81
+ end
82
+
83
+ it 'converts binary strings into a hex blob' do
84
+ subject.sanitize('?', [1,2,3,4].pack('C*'))
85
+ .should == "0x01020304"
86
+ end
87
+
88
+ it 'joins elements of an array with a comma separator' do
89
+ subject.sanitize('?', [1,2,3]).should == '1,2,3'
90
+ end
91
+
92
+ it 'joins key/value pairs of a hash with colon and comma separators' do
93
+ subject.sanitize('?', {a: 1, b: 'z'}).should == "'a':1,'b':'z'"
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,20 @@
1
+ require 'bundler/setup'
2
+ require 'simplecov'; SimpleCov.start
3
+ require 'ciql'
4
+
5
+ class FakeReactor
6
+ def start
7
+ Cql::Future.completed([])
8
+ end
9
+
10
+ def add_connection(host, port)
11
+ Cql::Future.completed(1)
12
+ end
13
+
14
+ def queue_request(request, connection_id = nil)
15
+ Cql::Future.completed(nil)
16
+ end
17
+
18
+ def stop
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ciql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Bradford
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cql-rb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0.pre8
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0.pre8
27
+ - !ruby/object:Gem::Dependency
28
+ name: simple_uuid
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.0
41
+ description: A CQL-based Cassandra client for Ruby
42
+ email:
43
+ - justin@nulu.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/ciql/client.rb
49
+ - lib/ciql/configuration.rb
50
+ - lib/ciql/sanitize.rb
51
+ - lib/ciql/version.rb
52
+ - lib/ciql.rb
53
+ - README.md
54
+ - spec/ciql/client_spec.rb
55
+ - spec/ciql/configuration_spec.rb
56
+ - spec/ciql/sanitize_spec.rb
57
+ - spec/spec_helper.rb
58
+ homepage: https://github.com/nulu/ciql
59
+ licenses:
60
+ - Apache
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - '>='
69
+ - !ruby/object:Gem::Version
70
+ version: 1.9.2
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.0.2
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: CQL Cassandra client for Ruby
82
+ test_files:
83
+ - spec/ciql/client_spec.rb
84
+ - spec/ciql/configuration_spec.rb
85
+ - spec/ciql/sanitize_spec.rb
86
+ - spec/spec_helper.rb