ciql 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +5 -0
- data/lib/ciql/client.rb +21 -0
- data/lib/ciql/configuration.rb +31 -0
- data/lib/ciql/sanitize.rb +59 -0
- data/lib/ciql/version.rb +3 -0
- data/lib/ciql.rb +9 -0
- data/spec/ciql/client_spec.rb +66 -0
- data/spec/ciql/configuration_spec.rb +111 -0
- data/spec/ciql/sanitize_spec.rb +97 -0
- data/spec/spec_helper.rb +20 -0
- metadata +86 -0
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.
|
data/lib/ciql/client.rb
ADDED
@@ -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
|
data/lib/ciql/version.rb
ADDED
data/lib/ciql.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|