rel 0.0.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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Michel Martens
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,62 @@
1
+ BANDICOOT(1)
2
+
3
+ NAME
4
+ bandicoot -- Bandicoot client.
5
+
6
+ SYNOPSIS
7
+ db = Bandicoot.new(<host>, <port>)
8
+ db.post(<bandicoot_fn_symbol>, <data>)
9
+ db.get(<bandicoot_fn_symbol>)
10
+
11
+ DESCRIPTION
12
+ Bandicoot is a client library for the Bandicoot relational algebra
13
+ database.
14
+
15
+ USAGE
16
+ Create a client:
17
+
18
+ db = Bandicoot.new
19
+
20
+ Bandicoot receives two parameters: host, which defaults to "localhost",
21
+ and port, which defaults to 12345. It will connect via HTTP to send POST
22
+ and GET requests to the server.
23
+
24
+ If we have this server side Bandicoot program:
25
+
26
+ # cat foo.b
27
+ rel Items {
28
+ id: int,
29
+ pid: int,
30
+ ts: long,
31
+ }
32
+
33
+ queue: Items;
34
+
35
+ fn push(i: Items) {
36
+ queue += i - queue project(id);
37
+ }
38
+
39
+ fn list(): Items {
40
+ return queue;
41
+ }
42
+
43
+ Then we can use Bandicoot as follows:
44
+
45
+ db.post(:push, some_csv)
46
+ db.get(:list) #=> Returns an array of results
47
+
48
+ It also provides a model-like class for mapping Bandicoot rels:
49
+
50
+ class Item < Bandicoot::Rel
51
+ field :id, :int
52
+ field :pid, :int
53
+ field :ts, :long
54
+ end
55
+
56
+ Now you can do:
57
+
58
+ item = Item.new(id: 1, pid: 0, ts: 1310084883)
59
+ db.post(:push, item.to_csv)
60
+
61
+ INSTALLATION
62
+ $ gem install bandicoot
@@ -0,0 +1,11 @@
1
+ task :test do
2
+ require "cutest"
3
+
4
+ Cutest.run(Dir["test/*.rb"])
5
+ end
6
+
7
+ task :start do
8
+ exec "bandicoot start -p 12345 -d data/volume -c data/sample.b -s data/volume/sample"
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,53 @@
1
+ rel Items {
2
+ id: int,
3
+ pid: int,
4
+ ts: long,
5
+ }
6
+
7
+ rel Process {
8
+ pid: int
9
+ }
10
+
11
+ queue: Items;
12
+
13
+ fn push(i: Items) {
14
+ queue += i - queue project(id);
15
+ }
16
+
17
+ fn list(): Items {
18
+ return queue;
19
+ }
20
+
21
+ fn in_process(): Items {
22
+ return queue select(pid != 0);
23
+ }
24
+
25
+ fn clear() {
26
+ queue -= queue;
27
+ }
28
+
29
+ fn fetch(p: Process): Items {
30
+
31
+ # Get the oldest free item.
32
+ q1 := queue select(pid == 0)
33
+ summary(id = min(id, 0),
34
+ ts = min(ts, 0L));
35
+
36
+ # Mark the item with the process id.
37
+ q2 := q1 * p;
38
+
39
+ # Update the queue with the new item.
40
+ queue = queue - q1 extend(pid = 0) + q2;
41
+
42
+ # Return the updated record.
43
+ return q2;
44
+ }
45
+
46
+ # Remove an item from the queue.
47
+ fn delete(i: Items) {
48
+ queue = queue - i;
49
+ }
50
+
51
+ fn recent(t: long): Items {
52
+ return queue select(ts > t);
53
+ }
@@ -0,0 +1,102 @@
1
+ require "csv"
2
+ require "net/http"
3
+ require "uri"
4
+
5
+ class Rel
6
+ VERSION = "0.0.1"
7
+
8
+ # Basic HTTP client that sends and receives CSV.
9
+ class Client
10
+ attr :http
11
+
12
+ def initialize(host, port)
13
+ @http = Net::HTTP.new(host, port)
14
+ end
15
+
16
+ def get(fn)
17
+ reply(http.get("/#{fn}"))
18
+ end
19
+
20
+ def post(fn, csv)
21
+ reply(http.post("/#{fn}", csv))
22
+ end
23
+
24
+ def reply(res)
25
+ unless res.code == "200"
26
+ raise RuntimeError, res.inspect
27
+ end
28
+
29
+ res.body
30
+ end
31
+ end
32
+
33
+ attr :client
34
+
35
+ def initialize(url = "http://localhost:12345")
36
+ url = URI.parse(url)
37
+ @client = Client.new(url.host, url.port)
38
+ end
39
+
40
+ def get(fn)
41
+ _csv_to_hashes(client.get(fn))
42
+ end
43
+
44
+ def post(fn, hashes)
45
+ _csv_to_hashes(client.post(fn, _hashes_to_csv(hashes.dup)))
46
+ end
47
+
48
+ private
49
+
50
+ def _csv_to_hashes(csv)
51
+ rows = CSV.parse(csv)
52
+ head = rows.shift
53
+
54
+ rows.map do |row|
55
+ _row_to_hash(row, head)
56
+ end
57
+ end
58
+
59
+ def _hashes_to_csv(hashes)
60
+ first = hashes.shift
61
+
62
+ CSV.generate do |csv|
63
+ csv << first.keys
64
+ csv << first.values
65
+
66
+ hashes.each do |hash|
67
+ csv << hash.values
68
+ end
69
+ end
70
+ end
71
+
72
+ def _row_to_hash(row, head)
73
+ record = []
74
+
75
+ head.zip(row) do |field, value|
76
+ record << field
77
+ record << _cast(value, _type_of(field))
78
+ end
79
+
80
+ Hash[*record]
81
+ end
82
+
83
+ def _record_to_csv(record)
84
+ CSV.generate do |csv|
85
+ csv << record.keys
86
+ csv << record.values
87
+ end
88
+ end
89
+
90
+ def _type_of(field)
91
+ field.split(":").last
92
+ end
93
+
94
+ def _cast(value, type)
95
+ case type
96
+ when "int" then value.to_i
97
+ when "long" then value.to_i
98
+ when "real" then value.to_f
99
+ else value
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,45 @@
1
+ # Mapper for Bandicoot relations.
2
+ class Rel
3
+ class Model
4
+ @@fields = Hash.new { |hash, key| hash[key] = {} }
5
+
6
+ def self.field(name, type)
7
+ @@fields[self.name][name] = type
8
+ attr_accessor name
9
+ end
10
+
11
+ def initialize(attrs = {})
12
+ attrs.each do |field, value|
13
+ name, type = field.to_s.split(":")
14
+ send(:"#{name}=", value)
15
+ end
16
+ end
17
+
18
+ def to_hash
19
+ record = []
20
+
21
+ header.zip(values) do |field, value|
22
+ record << field
23
+ record << value
24
+ end
25
+
26
+ Hash[*record]
27
+ end
28
+
29
+ def values
30
+ fields.map do |field, _|
31
+ send(field)
32
+ end
33
+ end
34
+
35
+ def header
36
+ fields.map do |field, type|
37
+ "#{field}:#{type}"
38
+ end
39
+ end
40
+
41
+ def fields
42
+ @@fields[self.class.name]
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,21 @@
1
+ require "./lib/rel"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "rel"
5
+ s.version = Rel::VERSION
6
+ s.summary = "Bandicoot client."
7
+ s.description = "Client for the Bandicoot relational algebra database."
8
+ s.authors = ["Michel Martens"]
9
+ s.email = ["michel@soveran.com"]
10
+ s.homepage = "http://github.com/soveran/rel"
11
+
12
+ s.files = Dir[
13
+ "LICENSE",
14
+ "README*",
15
+ "Rakefile",
16
+ "lib/**/*.rb",
17
+ "*.gemspec",
18
+ "test/*.*",
19
+ "data/sample.b"
20
+ ]
21
+ end
@@ -0,0 +1,40 @@
1
+ $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
2
+
3
+ require "rel"
4
+ require "pp"
5
+
6
+ db = Rel.new
7
+
8
+ head = [["id:int", "pid:int", "ts:long"]]
9
+
10
+ list_csv = CSV.generate do |csv|
11
+ csv << head.first
12
+ csv << [1, 0, 1310084883]
13
+ csv << [2, 0, 1310084888]
14
+ end
15
+
16
+ list = CSV.parse(list_csv)
17
+
18
+ prepare do
19
+ db.get(:clear)
20
+ end
21
+
22
+ test "get" do
23
+ assert_equal head, CSV.parse(db.client.get(:list))
24
+ end
25
+
26
+ test "push items to the queue" do
27
+ assert_equal "", db.client.post(:push, list_csv)
28
+ assert_equal list_csv, db.client.get(:list)
29
+ end
30
+
31
+ test "primitive types in parameters" do
32
+ db.post("push", [{"id:int" => 1,
33
+ "pid:int" => 1,
34
+ "ts:long" => 100}])
35
+ db.post("push", [{"id:int" => 2,
36
+ "pid:int" => 2,
37
+ "ts:long" => 200}])
38
+
39
+ assert_equal 2, db.get("recent?t=150").first["id:int"]
40
+ end
@@ -0,0 +1,27 @@
1
+ $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
2
+
3
+ require "rel"
4
+ require "rel/model"
5
+
6
+ class Item < Rel::Model
7
+ field :id, :int
8
+ field :pid, :int
9
+ field :ts, :long
10
+
11
+ def save(db)
12
+ db.post(:push, [to_hash])
13
+ end
14
+ end
15
+
16
+ db = Rel.new
17
+
18
+ prepare do
19
+ db.get(:clear)
20
+ end
21
+
22
+ test "extending relations" do
23
+ item = Item.new(id: 1, pid: 0, ts: 1310084883)
24
+ item.save(db)
25
+
26
+ assert_equal item.to_hash, db.get(:list).first
27
+ end
@@ -0,0 +1,14 @@
1
+ $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
2
+
3
+ require "rel"
4
+
5
+ db = Rel.new
6
+
7
+ prepare do
8
+ db.get(:clear)
9
+ end
10
+
11
+ test do
12
+ db.post(:push, [{"id:int" => 1, "pid:int" => 2, "ts:long" => 3}])
13
+ assert_equal 1, db.get(:list).first["id:int"]
14
+ end
@@ -0,0 +1,39 @@
1
+ $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
2
+
3
+ require "rel"
4
+ require "rel/model"
5
+
6
+ class Item < Rel::Model
7
+ field :id, :int
8
+ field :pid, :int
9
+ field :ts, :long
10
+ end
11
+
12
+ class Other < Rel::Model
13
+ field :conflict, :string
14
+ end
15
+
16
+ item_hash = { "id:int" => 1, "pid:int" => 0, "ts:long" => 1310084883 }
17
+
18
+ test "convert item to hash" do
19
+ item = Item.new
20
+ item.id = 1
21
+ item.pid = 0
22
+ item.ts = 1310084883
23
+ assert_equal item.to_hash, item_hash
24
+ end
25
+
26
+ test "create item from hash" do
27
+ item = Item.new(item_hash)
28
+ assert_equal item.to_hash, item_hash
29
+ end
30
+
31
+ test "mass assignment of attributes on creation" do
32
+ item = Item.new(id: 1, pid: 0, ts: 1310084883)
33
+ assert_equal item.to_hash, item_hash
34
+ end
35
+
36
+ test "shouldn't have conflicts with other classes" do
37
+ item = Item.new(id: 1, pid: 0, ts: 1310084883)
38
+ other = Other.new(conflict: "None")
39
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michel Martens
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-21 00:00:00.000000000 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+ description: Client for the Bandicoot relational algebra database.
16
+ email:
17
+ - michel@soveran.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README
24
+ - Rakefile
25
+ - lib/rel/model.rb
26
+ - lib/rel.rb
27
+ - rel.gemspec
28
+ - test/bandicoot.rb
29
+ - test/bandicoot_and_models.rb
30
+ - test/hash.rb
31
+ - test/model.rb
32
+ - data/sample.b
33
+ has_rdoc: true
34
+ homepage: http://github.com/soveran/rel
35
+ licenses: []
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 1.6.2
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: Bandicoot client.
58
+ test_files: []