dm-tokyo-adapter 0.4.1
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.
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +86 -0
- data/Rakefile +36 -0
- data/VERSION.yml +4 -0
- data/dm-tokyo-adapter.gemspec +62 -0
- data/lib/dm-tokyo-adapter.rb +12 -0
- data/lib/dm-tokyo-adapter/cabinet.rb +90 -0
- data/lib/dm-tokyo-adapter/query.rb +141 -0
- data/lib/dm-tokyo-adapter/tyrant.rb +33 -0
- data/lib/dm-tokyo-adapter/version.rb +7 -0
- data/test/helper.rb +44 -0
- data/test/test_cabinet.rb +143 -0
- data/test/test_query.rb +58 -0
- data/test/test_tyrant.rb +7 -0
- metadata +133 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2009 "Shane Hanna"
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
= DataMapper Tokyo Cabinet/Tyrant Table Store Adapter
|
|
2
|
+
|
|
3
|
+
* http://github.com/shanna/dm-tokyo-adapter/tree/master
|
|
4
|
+
|
|
5
|
+
== Description
|
|
6
|
+
|
|
7
|
+
A DataMapper Tokyo Cabinet/Tyrant table store adapter.
|
|
8
|
+
|
|
9
|
+
=== Table Store
|
|
10
|
+
|
|
11
|
+
http://tokyocabinet.sourceforge.net/spex-en.html#features_tctdb
|
|
12
|
+
|
|
13
|
+
The Tokyo Cabinet table storage engine doesn't require a predefined schema and as such properties in your resource are
|
|
14
|
+
only used for by the adapter for typecasting. There is no need to migrate your resource when you create, update or
|
|
15
|
+
delete properties.
|
|
16
|
+
|
|
17
|
+
== Dependencies
|
|
18
|
+
|
|
19
|
+
Ruby::
|
|
20
|
+
* dm-core ~> 0.10.0
|
|
21
|
+
* rufus-tokyo ~> 0.1.12
|
|
22
|
+
|
|
23
|
+
== Install
|
|
24
|
+
|
|
25
|
+
* Via gem:
|
|
26
|
+
|
|
27
|
+
gem install shanna-dm-tokyo-adapter -s http://gems.github.com
|
|
28
|
+
|
|
29
|
+
* Via git:
|
|
30
|
+
|
|
31
|
+
git clone git://github.com/shanna/dm-tokyo-adapter.git
|
|
32
|
+
rake install
|
|
33
|
+
|
|
34
|
+
== Synopsis
|
|
35
|
+
|
|
36
|
+
# Tokyo Cabinet DB files will be located in #{path}/#{database}/#{resource}.tdb
|
|
37
|
+
DataMapper.setup(:default,
|
|
38
|
+
:adapter => 'tokyo_cabinet',
|
|
39
|
+
:database => 'tc',
|
|
40
|
+
:path => File.dirname(__FILE__)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Tokyo Tyrant connection.
|
|
44
|
+
DataMapper.setup(:default,
|
|
45
|
+
:adapter => 'tokyo_tyrant',
|
|
46
|
+
:host => 'localhost',
|
|
47
|
+
:port => '1978'
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Define your DataMapper resource and start saving:
|
|
51
|
+
class User
|
|
52
|
+
include DataMapper::Resource
|
|
53
|
+
property :id, Serial
|
|
54
|
+
property :name, String
|
|
55
|
+
property :age, Integer
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# No need to (auto_)migrate!
|
|
59
|
+
User.create(:name => 'Fred', :age => '25')
|
|
60
|
+
|
|
61
|
+
# Conditions:
|
|
62
|
+
users = User.all(:age.gte => 10, :limit => 20, :order => [:age.asc])
|
|
63
|
+
|
|
64
|
+
== TODO
|
|
65
|
+
|
|
66
|
+
* Documentation. It's undocumented at the moment.
|
|
67
|
+
* Give access to the <tt>Rufus::Tokyo::Table</tt> object through the adapter. Handy if you want to add indexes and
|
|
68
|
+
other things that can't be done through the DataMapper API.
|
|
69
|
+
* Better tests. I haven't really tested all the DM primitives and query operators yet.
|
|
70
|
+
* Better typecasting. DataTime and Time should typecast to Integer so that they can be searched using the numeric
|
|
71
|
+
operators.
|
|
72
|
+
|
|
73
|
+
Ideally in the future I'd like to contribute to these broader goals:
|
|
74
|
+
|
|
75
|
+
* All the TokyoCabinet stores equally supported in DM.
|
|
76
|
+
* DataMapper define a public/semipublic API for key => value and search stores through Moneta (memcachedb, memcacheq,
|
|
77
|
+
couchdb, mtokyo cabinet bdb, etc.)
|
|
78
|
+
* DataMapper per adapter query operators. You can't always shoehorn everything into an SQL mindset.
|
|
79
|
+
|
|
80
|
+
== Contributing
|
|
81
|
+
|
|
82
|
+
Go nuts. Just send me a pull request (github or otherwise) when you are happy with your code.
|
|
83
|
+
|
|
84
|
+
== Copyright
|
|
85
|
+
|
|
86
|
+
Copyright (c) 2009 "Shane Hanna". See LICENSE for details.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'jeweler'
|
|
6
|
+
Jeweler::Tasks.new do |gem|
|
|
7
|
+
gem.name = 'dm-tokyo-adapter'
|
|
8
|
+
gem.summary = %Q{Tokyo Cabinet/Tyrant Table DataMapper Adapter.}
|
|
9
|
+
gem.email = 'shane.hanna@gmail.com'
|
|
10
|
+
gem.homepage = 'http://github.com/shanna/dm-tokyo-adapter'
|
|
11
|
+
gem.authors = ['Shane Hanna']
|
|
12
|
+
gem.files.reject!{|f| f =~ /\.tdb$/}
|
|
13
|
+
gem.add_dependency 'dm-core', '~> 0.10.0'
|
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
15
|
+
end
|
|
16
|
+
rescue LoadError
|
|
17
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
require 'rake/rdoctask'
|
|
21
|
+
Rake::RDocTask.new do |rdoc|
|
|
22
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
23
|
+
rdoc.title = 'dm-tokyo-cabinet-adapter'
|
|
24
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
|
25
|
+
rdoc.rdoc_files.include('README*')
|
|
26
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
require 'rake/testtask'
|
|
30
|
+
Rake::TestTask.new(:test) do |test|
|
|
31
|
+
test.libs << 'lib' << 'test'
|
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
|
33
|
+
test.verbose = true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
task :default => :test
|
data/VERSION.yml
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = %q{dm-tokyo-adapter}
|
|
5
|
+
s.version = "0.4.1"
|
|
6
|
+
|
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
8
|
+
s.authors = ["Joshua Partogi", "Shane Hanna"]
|
|
9
|
+
s.date = %q{2010-10-14 2009-08-09}
|
|
10
|
+
s.email = %q{joshua.partogi@gmail.com shane.hanna@gmail.com}
|
|
11
|
+
s.extra_rdoc_files = [
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"README.rdoc"
|
|
14
|
+
]
|
|
15
|
+
s.files = [
|
|
16
|
+
".gitignore",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.rdoc",
|
|
19
|
+
"Rakefile",
|
|
20
|
+
"VERSION.yml",
|
|
21
|
+
"dm-tokyo-adapter.gemspec",
|
|
22
|
+
"lib/dm-tokyo-adapter.rb",
|
|
23
|
+
"lib/dm-tokyo-adapter/cabinet.rb",
|
|
24
|
+
"lib/dm-tokyo-adapter/query.rb",
|
|
25
|
+
"lib/dm-tokyo-adapter/tyrant.rb",
|
|
26
|
+
"lib/dm-tokyo-adapter/version.rb",
|
|
27
|
+
"test/helper.rb",
|
|
28
|
+
"test/test_cabinet.rb",
|
|
29
|
+
"test/test_query.rb",
|
|
30
|
+
"test/test_tyrant.rb"
|
|
31
|
+
]
|
|
32
|
+
s.homepage = %q{http://github.com/scrum8/dm-tokyo-adapter}
|
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
|
34
|
+
s.require_paths = ["lib"]
|
|
35
|
+
s.rubygems_version = %q{1.3.4}
|
|
36
|
+
s.summary = %q{Tokyo Cabinet/Tyrant Table DataMapper Adapter.}
|
|
37
|
+
s.test_files = [
|
|
38
|
+
"test/helper.rb",
|
|
39
|
+
"test/test_cabinet.rb",
|
|
40
|
+
"test/test_query.rb",
|
|
41
|
+
"test/test_tyrant.rb"
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
if s.respond_to? :specification_version then
|
|
45
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
46
|
+
s.specification_version = 3
|
|
47
|
+
|
|
48
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
49
|
+
s.add_runtime_dependency(%q<dm-core>, [">= 1.0.2"])
|
|
50
|
+
s.add_dependency(%q<rufus-tokyo>, ">= 1.0.7")
|
|
51
|
+
s.add_dependency(%q<addressable>, ">= 2.2.2")
|
|
52
|
+
else
|
|
53
|
+
s.add_dependency(%q<dm-core>, [">= 1.0.2"])
|
|
54
|
+
s.add_dependency(%q<rufus-tokyo>, ">= 1.0.7")
|
|
55
|
+
s.add_dependency(%q<addressable>, ">= 2.2.2")
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
s.add_dependency(%q<dm-core>, [">= 1.0.2"])
|
|
59
|
+
s.add_dependency(%q<rufus-tokyo>, ">= 1.0.7")
|
|
60
|
+
s.add_dependency(%q<addressable>, ">= 2.2.2")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'extlib'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
require 'dm-core'
|
|
5
|
+
|
|
6
|
+
require 'rufus/tokyo'
|
|
7
|
+
require 'rufus/tokyo/tyrant'
|
|
8
|
+
|
|
9
|
+
require 'dm-tokyo-adapter/query'
|
|
10
|
+
require 'dm-tokyo-adapter/cabinet'
|
|
11
|
+
require 'dm-tokyo-adapter/tyrant'
|
|
12
|
+
require 'dm-tokyo-adapter/version'
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Adapters
|
|
3
|
+
module Tokyo
|
|
4
|
+
|
|
5
|
+
# A DataMapper Tokyo Cabinet table store adapter.
|
|
6
|
+
#
|
|
7
|
+
# http://tokyocabinet.sourceforge.net/spex-en.html#features_tctdb
|
|
8
|
+
#
|
|
9
|
+
# The Tokyo Cabinet table storage engine doesn't require a predefined schema and as such properties in your
|
|
10
|
+
# resource are only used by the adapter for typecasting. There is no need to migrate your resource when you
|
|
11
|
+
# create, update or delete properties.
|
|
12
|
+
#
|
|
13
|
+
# == See
|
|
14
|
+
#
|
|
15
|
+
# DataMapper::Adapters::Tokyo::Query:: Table Query.
|
|
16
|
+
class CabinetAdapter < AbstractAdapter
|
|
17
|
+
def create(resources)
|
|
18
|
+
resources.map do |resource|
|
|
19
|
+
model = resource.model
|
|
20
|
+
|
|
21
|
+
with_connection(resource.model) do |connection|
|
|
22
|
+
initialize_serial(resource, connection.generate_unique_id)
|
|
23
|
+
connection[key(resource)] = attributes(resource, :field)
|
|
24
|
+
end
|
|
25
|
+
end.size
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def read(query)
|
|
29
|
+
with_connection(query.model) do |connection|
|
|
30
|
+
Tokyo::Query.new(connection, query).read
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def update(attributes, collection)
|
|
35
|
+
with_connection(collection.query.model) do |connection|
|
|
36
|
+
collection.each do |record|
|
|
37
|
+
connection[key(record)] = attributes(record, :field)
|
|
38
|
+
end.size
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def delete(collection)
|
|
43
|
+
with_connection(collection.query.model) do |connection|
|
|
44
|
+
collection.each do |record|
|
|
45
|
+
connection.delete(key(record))
|
|
46
|
+
end.size
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
def create_connection(model)
|
|
52
|
+
@tdb_path ||= FileUtils.mkdir_p(
|
|
53
|
+
File.join(*[@options[:path], (@options[:host] || @options[:database])].compact)
|
|
54
|
+
).first
|
|
55
|
+
tdb = File.join(@tdb_path, "#{model.base_model.storage_name(name)}.tdb")
|
|
56
|
+
Rufus::Tokyo::Table.new(tdb)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def close_connection(connection)
|
|
60
|
+
connection.close
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
def key(resource)
|
|
65
|
+
key = resource.key
|
|
66
|
+
(key.size > 1 ? key.join(':') : key.first).to_s
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def attributes(resource, key_on = :name)
|
|
70
|
+
resource.attributes(key_on).map{|k, v| [k, v.to_s]}.to_hash
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def with_connection(model)
|
|
74
|
+
begin
|
|
75
|
+
connection = create_connection(model)
|
|
76
|
+
return yield(connection)
|
|
77
|
+
rescue => error
|
|
78
|
+
DataMapper.logger.error(error.to_s)
|
|
79
|
+
raise error
|
|
80
|
+
ensure
|
|
81
|
+
close_connection(connection) if connection
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end # CabinetAdapter
|
|
85
|
+
end # Tokyo
|
|
86
|
+
|
|
87
|
+
TokyoCabinetAdapter = Tokyo::CabinetAdapter
|
|
88
|
+
const_added(:TokyoCabinetAdapter)
|
|
89
|
+
end # Adapters
|
|
90
|
+
end # DataMapper
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Adapters
|
|
3
|
+
module Tokyo
|
|
4
|
+
|
|
5
|
+
# Query a Tokyo Cabinet table store with a DataMapper query.
|
|
6
|
+
#
|
|
7
|
+
# == Notes
|
|
8
|
+
#
|
|
9
|
+
# Query conditions not supported natively by TC's table query will fall back to DM's in-memory query
|
|
10
|
+
# filtering. This may impact performance.
|
|
11
|
+
class Query
|
|
12
|
+
include Extlib::Assertions
|
|
13
|
+
include DataMapper::Query::Conditions
|
|
14
|
+
|
|
15
|
+
def initialize(connection, query)
|
|
16
|
+
assert_kind_of 'connection', connection, Rufus::Tokyo::Table
|
|
17
|
+
assert_kind_of 'query', query, DataMapper::Query
|
|
18
|
+
@connection, @query, @native = connection, query, []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#--
|
|
22
|
+
# TODO: connection[] if I have everything I need to fetch by the primary key.
|
|
23
|
+
def read
|
|
24
|
+
records = @connection.query do |statements|
|
|
25
|
+
if @query.conditions.kind_of?(OrOperation)
|
|
26
|
+
fail_native("Operation '#{@query.conditions.slug}'.")
|
|
27
|
+
else
|
|
28
|
+
@query.conditions.each do |condition|
|
|
29
|
+
condition_statement(statements, condition)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if native? && !@query.order.empty?
|
|
34
|
+
sort_statement(statements, @query.order)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
statements.limit(@query.limit) if native? && @query.limit
|
|
38
|
+
statements.no_pk
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
records.each do |record|
|
|
42
|
+
@query.fields.each do |property|
|
|
43
|
+
field = property.field
|
|
44
|
+
record[field] = property.typecast(record[field])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return records if native?
|
|
49
|
+
# TODO: Move log entry out to adapter sublcass and use #name?
|
|
50
|
+
DataMapper.logger.warn(
|
|
51
|
+
"TokyoAdapter: No native TableQuery support for conditions: #{@native.join(' ')}"
|
|
52
|
+
)
|
|
53
|
+
@query.filter_records(records)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def native?
|
|
57
|
+
@native.empty?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
def condition_statement(statements, conditions, affirmative = true)
|
|
62
|
+
case conditions
|
|
63
|
+
when AbstractOperation then operation_statement(statements, conditions, affirmative)
|
|
64
|
+
when AbstractComparison then comparison_statement(statements, conditions, affirmative)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def operation_statement(statements, operation, affirmative = true)
|
|
69
|
+
case operation
|
|
70
|
+
when NotOperation then condition_statement(statements, operation.first, !affirmative)
|
|
71
|
+
when AndOperation then operation.each{|op| condition_statement(statements, op, affirmative)}
|
|
72
|
+
else fail_native("Operation '#{operation.slug}'.")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def comparison_statement(statements, comparison, affirmative = true)
|
|
77
|
+
subject = comparison.subject
|
|
78
|
+
primitive = subject.primitive unless subject.kind_of?(DataMapper::Associations::Relationship)
|
|
79
|
+
value = comparison.value.kind_of?(DataMapper::Resource) ?
|
|
80
|
+
comparison.value[comparison.value.class.serial.name] :
|
|
81
|
+
comparison.value
|
|
82
|
+
|
|
83
|
+
if subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
|
|
84
|
+
statements.add(subject.child_key.first.name, :numeq, quote_value(value), affirmative)
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if subject.is_a?(DataMapper::Associations::OneToMany::Relationship)
|
|
89
|
+
value = comparison.value[subject.child_key.first.name]
|
|
90
|
+
statements.add(subject.target_key.first.name, :numeq, quote_value(value), affirmative)
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if value.kind_of?(Range) && value.exclude_end?
|
|
95
|
+
operation = BooleanOperation.new(:and,
|
|
96
|
+
Comparison.new(:gte, comparison.property, value.first),
|
|
97
|
+
Comparison.new(:lt, comparison.property, value.last)
|
|
98
|
+
)
|
|
99
|
+
operation_statement(statements, operation, affirmative)
|
|
100
|
+
return
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
operator = case comparison
|
|
104
|
+
when EqualToComparison then primitive == Integer ? :numeq : :eq
|
|
105
|
+
when InclusionComparison then primitive == Integer ? :numoreq : nil
|
|
106
|
+
when RegexpComparison then :regex
|
|
107
|
+
when LikeComparison then :regex
|
|
108
|
+
when GreaterThanComparison then :gt
|
|
109
|
+
when LessThanComparison then :lt
|
|
110
|
+
when GreaterThanOrEqualToComparison then :gte
|
|
111
|
+
when LessThanOrEqualToComparison then :lte
|
|
112
|
+
else fail_native("Comparison #{comparison.slug}'.") && return
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
statements.add(comparison.subject.field, operator, quote_value(value), affirmative)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def quote_value(value)
|
|
119
|
+
"#{value}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def sort_statement(statements, conditions)
|
|
123
|
+
fail_native("Multiple (#{conditions.size}) order conditions.") if conditions.size > 1
|
|
124
|
+
|
|
125
|
+
sort_order = conditions.first
|
|
126
|
+
primitive = sort_order.target.primitive
|
|
127
|
+
direction = case sort_order.operator
|
|
128
|
+
when :asc then primitive == Integer ? :numasc : :asc
|
|
129
|
+
when :desc then primitive == Integer ? :numdesc : :desc
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
statements.order_by(sort_order.target.field, direction)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def fail_native(why)
|
|
136
|
+
@native << why
|
|
137
|
+
end
|
|
138
|
+
end # Query
|
|
139
|
+
end # Tokyo
|
|
140
|
+
end # Adapters
|
|
141
|
+
end # DataMapper
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Adapters
|
|
3
|
+
module Tokyo
|
|
4
|
+
|
|
5
|
+
# A DataMapper Tokyo Tyrant table store adapter.
|
|
6
|
+
#
|
|
7
|
+
# http://tokyocabinet.sourceforge.net/tyrantdoc/
|
|
8
|
+
# http://tokyocabinet.sourceforge.net/spex-en.html#features_tctdb
|
|
9
|
+
#
|
|
10
|
+
# The Tokyo Cabinet table storage engine doesn't require a predefined schema and as such properties in your
|
|
11
|
+
# resource are only used by the adapter for typecasting. There is no need to migrate your resource when you
|
|
12
|
+
# create, update or delete properties.
|
|
13
|
+
#
|
|
14
|
+
# == See
|
|
15
|
+
#
|
|
16
|
+
# DataMapper::Adapters::Tokyo::Query:: Table Query.
|
|
17
|
+
class TyrantAdapter < Tokyo::CabinetAdapter
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
#--
|
|
21
|
+
# TODO: Default port to 1978?
|
|
22
|
+
def create_connection(model)
|
|
23
|
+
credentials = [@options[:socket] || @options.values_at(:host, :port)]
|
|
24
|
+
Rufus::Tokyo::TyrantTable.new(*credentials.flatten)
|
|
25
|
+
end
|
|
26
|
+
end # TyrantAdapter
|
|
27
|
+
end # Tokyo
|
|
28
|
+
|
|
29
|
+
TokyoTyrantAdapter = Tokyo::TyrantAdapter
|
|
30
|
+
const_added(:TokyoTyrantAdapter)
|
|
31
|
+
end # Adapters
|
|
32
|
+
end
|
|
33
|
+
|
data/test/helper.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require 'shoulda'
|
|
5
|
+
|
|
6
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
7
|
+
|
|
8
|
+
require 'dm-migrations'
|
|
9
|
+
require 'dm-tokyo-adapter'
|
|
10
|
+
|
|
11
|
+
class Test::Unit::TestCase
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
DataMapper::Logger.new(STDOUT, :debug) if $VERBOSE
|
|
15
|
+
DataMapper.setup(:default, {
|
|
16
|
+
:adapter => 'tokyo_cabinet',
|
|
17
|
+
:database => 'tc',
|
|
18
|
+
:path => File.dirname(__FILE__)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
DataMapper.setup(:sqlite, 'sqlite::memory:')
|
|
22
|
+
|
|
23
|
+
class Test::Unit::TestCase
|
|
24
|
+
include Extlib::Hook
|
|
25
|
+
|
|
26
|
+
# after :teardown do
|
|
27
|
+
def teardown
|
|
28
|
+
descendants = DataMapper::Model.descendants.to_a
|
|
29
|
+
while model = descendants.shift
|
|
30
|
+
descendants.concat(model.descendants.to_a - [ model ])
|
|
31
|
+
|
|
32
|
+
parts = model.name.split('::')
|
|
33
|
+
constant_name = parts.pop.to_sym
|
|
34
|
+
base = parts.empty? ? Object : Object.full_const_get(parts.join('::'))
|
|
35
|
+
|
|
36
|
+
if base.const_defined?(constant_name)
|
|
37
|
+
base.send(:remove_const, constant_name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
DataMapper::Model.descendants.delete(model)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
|
2
|
+
|
|
3
|
+
class AdapterTest < Test::Unit::TestCase
|
|
4
|
+
context DataMapper::Adapters::TokyoCabinetAdapter do
|
|
5
|
+
context 'Serial key resource' do
|
|
6
|
+
setup do
|
|
7
|
+
class ::User
|
|
8
|
+
include DataMapper::Resource
|
|
9
|
+
property :id, Serial
|
|
10
|
+
property :name, String
|
|
11
|
+
property :age, Integer
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
@user = User.create(:name => 'Joe', :age => 22)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
teardown do
|
|
18
|
+
User.all.destroy
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
should 'assign id to attributes' do
|
|
22
|
+
item = User.create
|
|
23
|
+
assert_kind_of User, item
|
|
24
|
+
assert_not_nil item.id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
should 'get an item' do
|
|
28
|
+
assert_equal @user, User.get(@user.id)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
should 'get items' do
|
|
32
|
+
assert_equal 1, User.all.size
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
should 'destroy item' do
|
|
36
|
+
assert @user.destroy
|
|
37
|
+
assert_equal 0, User.all.size
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
should 'update item' do
|
|
41
|
+
@user.name = 'Woot'
|
|
42
|
+
assert @user.save
|
|
43
|
+
assert_equal 'Woot', User.get(@user.id).name
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'Compound key resource' do
|
|
48
|
+
setup do
|
|
49
|
+
class ::User
|
|
50
|
+
include DataMapper::Resource
|
|
51
|
+
property :name, String, :key => true
|
|
52
|
+
property :age, Integer, :key => true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@user = User.create(:name => 'Joe', :age => 22)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
teardown do
|
|
59
|
+
User.all.destroy
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
should 'get an item' do
|
|
63
|
+
assert_equal @user, User.get(*@user.key)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context 'Relationship same db' do
|
|
68
|
+
setup do
|
|
69
|
+
class ::User
|
|
70
|
+
include DataMapper::Resource
|
|
71
|
+
|
|
72
|
+
has n, :comments
|
|
73
|
+
|
|
74
|
+
property :id, Serial
|
|
75
|
+
property :name, String
|
|
76
|
+
end
|
|
77
|
+
class ::Comment
|
|
78
|
+
include DataMapper::Resource
|
|
79
|
+
|
|
80
|
+
belongs_to :user
|
|
81
|
+
|
|
82
|
+
property :id, Serial
|
|
83
|
+
property :content, String
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
should 'map object' do
|
|
88
|
+
@user= User.create(:name => 'Joe')
|
|
89
|
+
@comment= Comment.create(:content => 'Foo')
|
|
90
|
+
|
|
91
|
+
@user.comments << @comment
|
|
92
|
+
@user.save
|
|
93
|
+
|
|
94
|
+
assert_equal 1, @user.comments.length
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
teardown do
|
|
98
|
+
User.all.destroy
|
|
99
|
+
Comment.all.destroy
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context 'Relationship different db' do
|
|
104
|
+
setup do
|
|
105
|
+
class ::User
|
|
106
|
+
include DataMapper::Resource
|
|
107
|
+
|
|
108
|
+
has n, :comments
|
|
109
|
+
|
|
110
|
+
property :id, Serial
|
|
111
|
+
property :name, String
|
|
112
|
+
end
|
|
113
|
+
class ::Comment
|
|
114
|
+
include DataMapper::Resource
|
|
115
|
+
|
|
116
|
+
def self.default_repository_name
|
|
117
|
+
:sqlite
|
|
118
|
+
end
|
|
119
|
+
belongs_to :user
|
|
120
|
+
|
|
121
|
+
property :id, Serial
|
|
122
|
+
property :content, String
|
|
123
|
+
end
|
|
124
|
+
DataMapper.auto_migrate!(:sqlite)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
should 'map object' do
|
|
128
|
+
@user= User.create(:name => 'Joe')
|
|
129
|
+
@comment= Comment.create(:content => 'Foo')
|
|
130
|
+
|
|
131
|
+
@user.comments << @comment
|
|
132
|
+
@user.save
|
|
133
|
+
|
|
134
|
+
assert_equal 1, @user.comments.length
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
teardown do
|
|
138
|
+
User.all.destroy
|
|
139
|
+
Comment.all.destroy
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
data/test/test_query.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
|
2
|
+
|
|
3
|
+
class QueryTest < Test::Unit::TestCase
|
|
4
|
+
context 'Resource' do
|
|
5
|
+
setup do
|
|
6
|
+
class ::User
|
|
7
|
+
include DataMapper::Resource
|
|
8
|
+
property :id, Serial
|
|
9
|
+
property :name, String
|
|
10
|
+
property :age, Integer
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
@joe = User.create(:name => 'Joe', :age => 11)
|
|
14
|
+
@jack = User.create(:name => 'Jack', :age => 22)
|
|
15
|
+
@john = User.create(:name => 'John', :age => 33)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
teardown do
|
|
19
|
+
User.all.destroy
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
should 'get items' do
|
|
23
|
+
assert_equal 3, User.all.size
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
should 'get items with sring conditions' do
|
|
27
|
+
User.create(:name => 'John', :age => 44)
|
|
28
|
+
assert_equal 2, User.all(:name => 'John').size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
should 'get items with integer equality conditions' do
|
|
32
|
+
User.create(:name => 'Fred', :age => 33)
|
|
33
|
+
assert_equal 2, User.all(:age => 33).size
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
should 'get items with integer range conditions' do
|
|
37
|
+
User.create(:name => 'Fred', :age => 33)
|
|
38
|
+
assert_equal 3, User.all(:age.gte => 22, :age.lte => 34).size
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
should 'order items by string' do
|
|
42
|
+
users = [@jack, @joe, @john]
|
|
43
|
+
assert_equal users, User.all(:order => [:name.asc])
|
|
44
|
+
assert_equal users.reverse, User.all(:order => [:name.desc])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
should 'order items by integer' do
|
|
48
|
+
users = [@joe, @jack, @john]
|
|
49
|
+
assert_equal users, User.all(:order => [:age.asc])
|
|
50
|
+
assert_equal users.reverse, User.all(:order => [:age.desc])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
should 'limit items' do
|
|
54
|
+
assert_equal 2, User.all(:limit => 2).size
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end # QueryTest
|
|
58
|
+
|
data/test/test_tyrant.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dm-tokyo-adapter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 13
|
|
5
|
+
prerelease: false
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 4
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.4.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Joshua Partogi
|
|
14
|
+
- Shane Hanna
|
|
15
|
+
autorequire:
|
|
16
|
+
bindir: bin
|
|
17
|
+
cert_chain: []
|
|
18
|
+
|
|
19
|
+
date: 2010-10-14 00:00:00 +11:00
|
|
20
|
+
default_executable:
|
|
21
|
+
dependencies:
|
|
22
|
+
- !ruby/object:Gem::Dependency
|
|
23
|
+
name: dm-core
|
|
24
|
+
prerelease: false
|
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
26
|
+
none: false
|
|
27
|
+
requirements:
|
|
28
|
+
- - ">="
|
|
29
|
+
- !ruby/object:Gem::Version
|
|
30
|
+
hash: 19
|
|
31
|
+
segments:
|
|
32
|
+
- 1
|
|
33
|
+
- 0
|
|
34
|
+
- 2
|
|
35
|
+
version: 1.0.2
|
|
36
|
+
type: :runtime
|
|
37
|
+
version_requirements: *id001
|
|
38
|
+
- !ruby/object:Gem::Dependency
|
|
39
|
+
name: rufus-tokyo
|
|
40
|
+
prerelease: false
|
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
42
|
+
none: false
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
hash: 25
|
|
47
|
+
segments:
|
|
48
|
+
- 1
|
|
49
|
+
- 0
|
|
50
|
+
- 7
|
|
51
|
+
version: 1.0.7
|
|
52
|
+
type: :runtime
|
|
53
|
+
version_requirements: *id002
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: addressable
|
|
56
|
+
prerelease: false
|
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
58
|
+
none: false
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
hash: 3
|
|
63
|
+
segments:
|
|
64
|
+
- 2
|
|
65
|
+
- 2
|
|
66
|
+
- 2
|
|
67
|
+
version: 2.2.2
|
|
68
|
+
type: :runtime
|
|
69
|
+
version_requirements: *id003
|
|
70
|
+
description:
|
|
71
|
+
email: joshua.partogi@gmail.com shane.hanna@gmail.com
|
|
72
|
+
executables: []
|
|
73
|
+
|
|
74
|
+
extensions: []
|
|
75
|
+
|
|
76
|
+
extra_rdoc_files:
|
|
77
|
+
- LICENSE
|
|
78
|
+
- README.rdoc
|
|
79
|
+
files:
|
|
80
|
+
- .gitignore
|
|
81
|
+
- LICENSE
|
|
82
|
+
- README.rdoc
|
|
83
|
+
- Rakefile
|
|
84
|
+
- VERSION.yml
|
|
85
|
+
- dm-tokyo-adapter.gemspec
|
|
86
|
+
- lib/dm-tokyo-adapter.rb
|
|
87
|
+
- lib/dm-tokyo-adapter/cabinet.rb
|
|
88
|
+
- lib/dm-tokyo-adapter/query.rb
|
|
89
|
+
- lib/dm-tokyo-adapter/tyrant.rb
|
|
90
|
+
- lib/dm-tokyo-adapter/version.rb
|
|
91
|
+
- test/helper.rb
|
|
92
|
+
- test/test_cabinet.rb
|
|
93
|
+
- test/test_query.rb
|
|
94
|
+
- test/test_tyrant.rb
|
|
95
|
+
has_rdoc: true
|
|
96
|
+
homepage: http://github.com/scrum8/dm-tokyo-adapter
|
|
97
|
+
licenses: []
|
|
98
|
+
|
|
99
|
+
post_install_message:
|
|
100
|
+
rdoc_options:
|
|
101
|
+
- --charset=UTF-8
|
|
102
|
+
require_paths:
|
|
103
|
+
- lib
|
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
105
|
+
none: false
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
hash: 3
|
|
110
|
+
segments:
|
|
111
|
+
- 0
|
|
112
|
+
version: "0"
|
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
|
+
none: false
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
hash: 3
|
|
119
|
+
segments:
|
|
120
|
+
- 0
|
|
121
|
+
version: "0"
|
|
122
|
+
requirements: []
|
|
123
|
+
|
|
124
|
+
rubyforge_project:
|
|
125
|
+
rubygems_version: 1.3.7
|
|
126
|
+
signing_key:
|
|
127
|
+
specification_version: 3
|
|
128
|
+
summary: Tokyo Cabinet/Tyrant Table DataMapper Adapter.
|
|
129
|
+
test_files:
|
|
130
|
+
- test/helper.rb
|
|
131
|
+
- test/test_cabinet.rb
|
|
132
|
+
- test/test_query.rb
|
|
133
|
+
- test/test_tyrant.rb
|