skydb 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +165 -1
  3. data/lib/skydb.rb +18 -61
  4. data/lib/skydb/client.rb +186 -186
  5. data/lib/skydb/event.rb +47 -76
  6. data/lib/skydb/property.rb +34 -67
  7. data/lib/skydb/table.rb +121 -41
  8. data/lib/skydb/version.rb +1 -1
  9. data/test/integration/client_test.rb +88 -0
  10. data/test/test_helper.rb +3 -51
  11. data/test/unit/client_test.rb +135 -32
  12. metadata +17 -278
  13. data/bin/sky +0 -89
  14. data/lib/ext/hash.rb +0 -11
  15. data/lib/ext/string.rb +0 -11
  16. data/lib/ext/treetop.rb +0 -19
  17. data/lib/skydb/action.rb +0 -76
  18. data/lib/skydb/import.rb +0 -7
  19. data/lib/skydb/import/importer.rb +0 -435
  20. data/lib/skydb/import/transforms/apache.yml +0 -4
  21. data/lib/skydb/import/transforms/sky.yml +0 -28
  22. data/lib/skydb/import/transforms/snowplow.yml +0 -1
  23. data/lib/skydb/import/translator.rb +0 -119
  24. data/lib/skydb/message.rb +0 -146
  25. data/lib/skydb/message/add_action.rb +0 -53
  26. data/lib/skydb/message/add_event.rb +0 -72
  27. data/lib/skydb/message/add_property.rb +0 -55
  28. data/lib/skydb/message/create_table.rb +0 -64
  29. data/lib/skydb/message/delete_table.rb +0 -66
  30. data/lib/skydb/message/get_action.rb +0 -55
  31. data/lib/skydb/message/get_actions.rb +0 -38
  32. data/lib/skydb/message/get_properties.rb +0 -38
  33. data/lib/skydb/message/get_property.rb +0 -55
  34. data/lib/skydb/message/get_table.rb +0 -74
  35. data/lib/skydb/message/get_tables.rb +0 -43
  36. data/lib/skydb/message/lookup.rb +0 -79
  37. data/lib/skydb/message/lua/aggregate.rb +0 -63
  38. data/lib/skydb/message/multi.rb +0 -57
  39. data/lib/skydb/message/next_actions.rb +0 -55
  40. data/lib/skydb/message/ping.rb +0 -32
  41. data/lib/skydb/property/type.rb +0 -40
  42. data/lib/skydb/query.rb +0 -183
  43. data/lib/skydb/query/after_condition.rb +0 -104
  44. data/lib/skydb/query/ast/selection_field_syntax_node.rb +0 -26
  45. data/lib/skydb/query/ast/selection_fields_syntax_node.rb +0 -16
  46. data/lib/skydb/query/ast/selection_group_syntax_node.rb +0 -16
  47. data/lib/skydb/query/ast/selection_groups_syntax_node.rb +0 -16
  48. data/lib/skydb/query/condition.rb +0 -113
  49. data/lib/skydb/query/on_condition.rb +0 -53
  50. data/lib/skydb/query/selection.rb +0 -398
  51. data/lib/skydb/query/selection_field.rb +0 -99
  52. data/lib/skydb/query/selection_fields_grammar.treetop +0 -46
  53. data/lib/skydb/query/selection_fields_parse_error.rb +0 -30
  54. data/lib/skydb/query/selection_group.rb +0 -78
  55. data/lib/skydb/query/selection_groups_grammar.treetop +0 -31
  56. data/lib/skydb/query/selection_groups_parse_error.rb +0 -30
  57. data/lib/skydb/query/validation_error.rb +0 -8
  58. data/lib/skydb/timestamp.rb +0 -22
  59. data/test/integration/query_test.rb +0 -102
  60. data/test/unit/event_test.rb +0 -32
  61. data/test/unit/import/importer_test.rb +0 -208
  62. data/test/unit/import/translator_test.rb +0 -88
  63. data/test/unit/message/add_action_message_test.rb +0 -34
  64. data/test/unit/message/add_event_message_test.rb +0 -35
  65. data/test/unit/message/add_property_message_test.rb +0 -41
  66. data/test/unit/message/create_table_message_test.rb +0 -34
  67. data/test/unit/message/delete_table_message_test.rb +0 -34
  68. data/test/unit/message/get_action_message_test.rb +0 -34
  69. data/test/unit/message/get_actions_message_test.rb +0 -18
  70. data/test/unit/message/get_properties_message_test.rb +0 -18
  71. data/test/unit/message/get_property_message_test.rb +0 -34
  72. data/test/unit/message/get_table_message_test.rb +0 -19
  73. data/test/unit/message/get_tables_message_test.rb +0 -18
  74. data/test/unit/message/lookup_message_test.rb +0 -27
  75. data/test/unit/message/lua_aggregate_message_test.rb +0 -19
  76. data/test/unit/message/multi_message_test.rb +0 -22
  77. data/test/unit/message/next_action_message_test.rb +0 -34
  78. data/test/unit/message/ping_message_test.rb +0 -18
  79. data/test/unit/message_test.rb +0 -15
  80. data/test/unit/query/after_test.rb +0 -89
  81. data/test/unit/query/on_test.rb +0 -71
  82. data/test/unit/query/selection_test.rb +0 -273
  83. data/test/unit/query_test.rb +0 -182
  84. data/test/unit/skydb_test.rb +0 -20
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MTYyYTgwMmM5YTIxMzdjNzk0YjhkNTc5ZmQ1NWIyODcwOTkyOGNmZA==
5
+ data.tar.gz: !binary |-
6
+ NjdmZWY1NDMxNGQ3OTJmNzk0MTMxMjIwMWRkMDczYTg1ZDQ0MTZiMA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZjE4MGM3NDJkMDU4ODEzOGYzMzVmZDkwZTVhODNkNDA5NGQzYzRhNjk2YWQ5
10
+ MDNmZmVjODg3NjdlNmE3MGU2YTE0NWY2ODgwZWE3OGZmMTY4NDY4YTI3OGEx
11
+ MjVkZGI2YjM0YmZmOTMzM2U3ZmI3NTZiNWUzOWNiYmY0MTQ1MWE=
12
+ data.tar.gz: !binary |-
13
+ MWI2NTU4ZTRlYzg3ZTI1OWI4MThlYTY1NTdmZWQ4NWFmYjlmZTQxYjJkYzQ0
14
+ ZGQ3MGM4NGRjNjc1MTlkODMzMzFjNjZiNGU0ZGFmYzYxMjQxMjI4MmE4MjFj
15
+ YzEwZGQwMDI3ODQyNmJkNzc3MWU4ZTA4MjFlODQ2ZGVhMDk3MTA=
data/README.md CHANGED
@@ -1,4 +1,168 @@
1
1
  sky.rb
2
2
  ======
3
3
 
4
- The Ruby client for the Sky database.
4
+ ## Overview
5
+
6
+ The official Ruby client for the [Sky database](http://skydb.io).
7
+ This client provides a simple wrapper around the Sky HTTP-based API.
8
+
9
+
10
+ ## API
11
+
12
+ ### The Basics
13
+
14
+ To connect to your Sky database, simply create a client pointing to the appropriate host and port.
15
+ The client will default to `localhost:8585` unless otherwise specified.
16
+
17
+ ```ruby
18
+ require "skydb"
19
+ client = SkyDB::Client.new(:host => 'localhost', :port => 8585)
20
+ ```
21
+
22
+ ### Table API
23
+
24
+ The table API allows you to retrieve table information, create tables and delete tables.
25
+
26
+ ```ruby
27
+ require "skydb"
28
+ client = SkyDB::Client.new()
29
+
30
+ # Retrieve a list of all tables.
31
+ tables = client.get_tables()
32
+
33
+ # Retrieve data about a single table.
34
+ table = client.get_table()
35
+
36
+ # Create a new table named 'foo'.
37
+ table = client.create_table(:name => 'foo')
38
+
39
+ # Delete the 'foo' table.
40
+ client.delete_table(table)
41
+ ```
42
+
43
+
44
+ ### Property API
45
+
46
+ The property API allows you to retrieve property information, create properties, update properties and delete properties from a table.
47
+
48
+ There are five data types in Sky: `string`, `integer`, `float`, `boolean` and `factor`.
49
+ The first four are self-explanatory.
50
+ The `factor` data type acts like a `string` but internally it is represented as an integer with a lookup to the original string value.
51
+ This type should be used when you have categorical data such as gender, country or any property that has a limited number of possible values.
52
+
53
+ In addition to data types, Sky properties can also be transient or permanent.
54
+ A transient property means that the value of the property exists only for a single moment in time.
55
+ Typically this would be used for data related to a specific event such as the purchase amount of a checkout on a web site.
56
+ A permanent property means that the value of the property stays in effect until changed.
57
+ An example of this would be a user's gender or their household income.
58
+
59
+ ```ruby
60
+ require "skydb"
61
+ client = SkyDB::Client.new()
62
+ table = client.get_table(:name => 'foo')
63
+
64
+ # Retrieve a list of all properties on the table.
65
+ properties = table.get_properties()
66
+
67
+ # Retrieve a single property from the table.
68
+ property = table.get_property()
69
+
70
+ # Create a new transient, integer property named 'purchase_amount'.
71
+ property = table.create_property(:name => 'purchase_amount', :transient => true, :data_type => 'integer')
72
+
73
+ # Update the 'purchase_amount' property to be named 'total_purchase_amount'.
74
+ # Note that only the name change be changed on a property -- not it's transiency or data type.
75
+ property.name = 'total_purchase_amount'
76
+ table.update_property(property)
77
+
78
+ # Delete the property.
79
+ table.delete_property(property)
80
+ ```
81
+
82
+ ### Event API
83
+
84
+ The event API allows you to add events to objects on a table.
85
+ In Sky, objects are simply a sum of their events so objects do not need to be created explicitly.
86
+ Simply add events using an object key (e.g. a user id, e-mail address, or some other uniquely identifying string).
87
+
88
+ When adding events to the database, Sky will automatically deduplicate permanent values.
89
+ For example, if you set an object's "gender" property to "male" at Jan 1st 2000 at 1pm and then set it to "male" again on Jan 1st 2000 at 2pm then Sky will remove the second gender value since it can be determined by the first event.
90
+
91
+ Sky will also merge a events if adding an event that already exists at the same time for a given object.
92
+ This is typically unlikely as Sky's time resolution is one microsecond.
93
+
94
+ ```ruby
95
+ require "skydb"
96
+ client = SkyDB::Client.new()
97
+ table = client.get_table(:name => 'foo')
98
+ table.create_property(:name => "action", :transient => true, :data_type => "factor")
99
+ table.create_property(:name => "purchase_amount", :transient => true, :data_type => "float")
100
+ table.create_property(:name => "full_name", :data_type => "string")
101
+
102
+ # Retrieve a list of all events for an object.
103
+ events = table.get_events("susy")
104
+
105
+ # Retrieve the event that occurs at a given moment for an object.
106
+ event = table.get_event("susy", DateTime.iso8601('2000-01-01T00:00:00Z'))
107
+
108
+ # Add a new event for an object (and merge with an existing event).
109
+ table.add_event("susy", :timestamp => DateTime.iso8601('2000-01-01T00:00:00Z'), :data => {"action" => "checekout", "purchase_amount" => 100, "full_name" => "Susy Que"})
110
+
111
+ # Add a new event for an object (and replace an existing event).
112
+ table.add_event("susy", {:timestamp => DateTime.iso8601('2000-01-01T00:00:00Z'), :data => {"action" => "checekout", "purchase_amount" => 100, "full_name" => "Susy Que"}}, :method => :replace)
113
+
114
+ # Delete an event at the given moment.
115
+ table.delete_event("susy", :timestamp => DateTime.iso8601('2000-01-01T00:00:00Z'))
116
+ ```
117
+
118
+ ### Query API
119
+
120
+ Once your event data is in Sky, you can quickly iterate over it and query it for information.
121
+ There are two primitives in the Sky query system: conditions & selections.
122
+ Conditions allow you to execute nested conditions or selections if its expression evaluates to true.
123
+ Selections allow you to actually retrieve the data from the current event and group it by multiple dimensions.
124
+
125
+ For a full explanation of the Sky query system, please visit the [Sky web site](http://skydb.io).
126
+
127
+ ```ruby
128
+ require "skydb"
129
+ client = SkyDB::Client.new()
130
+ table = client.get_table(:name => 'users')
131
+ table.create_property(:name => "action", :transient => true, :data_type => "factor")
132
+ table.create_property(:name => "purchase_amount", :transient => true, :data_type => "float")
133
+ table.create_property(:name => "gender", :data_type => "factor")
134
+ table.create_property(:name => "state", :data_type => "factor")
135
+
136
+ # Perform a simple count of all events in the table.
137
+ results = client.query([:type => 'selection', :fields => [{:name => 'myCount', :expression => 'count()'}]])
138
+
139
+ # Count the number of users that performed the actions 'view home page', 'sign up' and then 'checkout' within a session
140
+ # and group the results by gender and state.
141
+ results = client.query(
142
+ :sessionIdleTime => 7200,
143
+ :steps => [
144
+ {:type => 'condition', :expression => 'action == "view home page"', :steps => [
145
+ {:type => 'condition', :expression => 'action == "sign up"', :within => [1,1], :steps => [
146
+ {:type => 'condition', :expression => 'checkout', :within => [1,1], :steps => [
147
+ {:type => 'selection', :dimensions => ['gender', 'state'], :fields => [
148
+ {:name => 'count', :expression => 'count()'},
149
+ {:name => 'total_amount', :expression => 'sum(purchase_amount)'}
150
+ ]},
151
+ ]}
152
+ ]}
153
+ ]}
154
+ ]
155
+ )
156
+ #=> {'gender' => {'m' => {'state' => {'CA' => {'count' => 10, 'total_amount' => 291.93}}}}
157
+ ```
158
+
159
+ ### Utility API
160
+
161
+ To check if the server is up and running, you can use the `ping` command:
162
+
163
+
164
+ ```ruby
165
+ require "skydb"
166
+ client = SkyDB::Client.new()
167
+ is_running = client.ping()
168
+ ```
@@ -1,23 +1,7 @@
1
1
  require 'date'
2
- require 'msgpack'
3
- require 'socket'
4
- require 'treetop'
2
+ require 'net/http'
5
3
  require 'json'
6
4
 
7
- require 'skydb/action'
8
- require 'skydb/client'
9
- require 'skydb/event'
10
- require 'skydb/message'
11
- require 'skydb/property'
12
- require 'skydb/query'
13
- require 'skydb/table'
14
- require 'skydb/timestamp'
15
- require 'skydb/version'
16
-
17
- require 'ext/hash'
18
- require 'ext/string'
19
- require 'ext/treetop'
20
-
21
5
  class SkyDB
22
6
  ############################################################################
23
7
  #
@@ -25,31 +9,8 @@ class SkyDB
25
9
  #
26
10
  ############################################################################
27
11
 
28
- class TableRequiredError < StandardError; end
29
-
30
- class ObjectIdRequiredError < StandardError; end
31
- class TimestampRequiredError < StandardError; end
12
+ class SkyError < StandardError; end
32
13
 
33
- ############################################################################
34
- #
35
- # Constants
36
- #
37
- ############################################################################
38
-
39
- CLIENT_PASSTHROUGH = [
40
- :host, :host=, :port, :port=,
41
- :table_name, :table_name=,
42
- :multi, :ping, :lookup,
43
- :add_event,
44
- :create_table, :delete_table, :get_table, :get_tables,
45
- :add_action, :get_action, :get_actions,
46
- :add_property, :get_property, :get_properties,
47
- :next_actions,
48
- :aggregate,
49
- :query, :select
50
- ]
51
-
52
-
53
14
  ############################################################################
54
15
  #
55
16
  # Static Attributes
@@ -64,20 +25,6 @@ class SkyDB
64
25
  attr_accessor :debug
65
26
  end
66
27
 
67
- ######################################
68
- # Default Client
69
- ######################################
70
-
71
- # The default Sky client.
72
- def self.client
73
- @client ||= SkyDB::Client.new()
74
- return @client
75
- end
76
-
77
- def self.client=(value)
78
- @client = value
79
- end
80
-
81
28
 
82
29
  ############################################################################
83
30
  #
@@ -85,12 +32,22 @@ class SkyDB
85
32
  #
86
33
  ############################################################################
87
34
 
88
- def self.method_missing(method, *args, &block)
89
- method = method
90
- if CLIENT_PASSTHROUGH.include?(method.to_sym)
91
- client.__send__(method.to_sym, *args, &block)
35
+ ######################################
36
+ # Timestamps
37
+ ######################################
38
+
39
+ # Formats a timestamp as ISO8601 formatted string with fractional seconds.
40
+ def self.format_timestamp(timestamp)
41
+ if timestamp.nil?
42
+ return nil
92
43
  else
93
- raise NoMethodError.new("Message type not available: #{method}")
44
+ return timestamp.to_time.utc.to_datetime.strftime('%Y-%m-%dT%H:%M:%S.%6NZ')
94
45
  end
95
46
  end
96
- end
47
+ end
48
+
49
+ require 'skydb/client'
50
+ require 'skydb/table'
51
+ require 'skydb/property'
52
+ require 'skydb/event'
53
+ require 'skydb/version'
@@ -1,5 +1,20 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
1
4
  class SkyDB
2
5
  class Client
6
+ ##########################################################################
7
+ #
8
+ # Errors
9
+ #
10
+ ##########################################################################
11
+
12
+ class ServerError < SkyError
13
+ attr_accessor :status
14
+ end
15
+
16
+
17
+
3
18
  ##########################################################################
4
19
  #
5
20
  # Constants
@@ -21,8 +36,6 @@ class SkyDB
21
36
 
22
37
  # Initializes the client.
23
38
  def initialize(options={})
24
- @multi_message_max_count = 0
25
-
26
39
  self.host = options[:host] || DEFAULT_HOST
27
40
  self.port = options[:port] || DEFAULT_PORT
28
41
  end
@@ -40,9 +53,6 @@ class SkyDB
40
53
  # The port on the host to connect to.
41
54
  attr_accessor :port
42
55
 
43
- # The name of the table to connect to.
44
- attr_accessor :table_name
45
-
46
56
 
47
57
  ##########################################################################
48
58
  #
@@ -51,256 +61,246 @@ class SkyDB
51
61
  ##########################################################################
52
62
 
53
63
  ####################################
54
- # Table Messages
64
+ # Table API
55
65
  ####################################
56
66
 
67
+ # Retrieves a list of tables on the server.
68
+ def get_tables(options={})
69
+ data = send(:get, "/tables")
70
+ tables = data.map {|i| Table.new(:client => self).from_hash(i)}
71
+ return tables
72
+ end
73
+
74
+ # Retrieves a single table from the server.
75
+ def get_table(name, options={})
76
+ raise ArgumentError.new("Table name required") if name.nil?
77
+ data = send(:get, "/tables/#{name}")
78
+ table = Table.new(:client => self).from_hash(data)
79
+ return table
80
+ end
81
+
57
82
  # Creates a table on the server.
58
83
  #
59
- # @param [Table] table the table to add.
84
+ # @param [Table] table the table to create.
60
85
  def create_table(table, options={})
61
- return send_message(SkyDB::Message::CreateTable.new(table, options))
86
+ raise ArgumentError.new("Table required") if table.nil?
87
+ table = Table.new(table) if table.is_a?(Hash)
88
+ table.client = self
89
+ data = send(:post, "/tables", table.to_hash)
90
+ return table.from_hash(data)
62
91
  end
63
92
 
64
93
  # Deletes a table on the server.
65
94
  #
66
95
  # @param [Table] table the table to delete.
67
96
  def delete_table(table, options={})
68
- return send_message(SkyDB::Message::DeleteTable.new(table, options))
69
- end
70
-
71
- # Retrieves an individual table from the server, if it exists. Otherwise
72
- # returns nil.
73
- #
74
- # @param [Fixnum] name the table name to retrieve.
75
- def get_table(name, options={})
76
- return send_message(SkyDB::Message::GetTable.new(name, options))
77
- end
78
-
79
- # Retrieves a list of all tables on the server.
80
- def get_tables(options={})
81
- return send_message(SkyDB::Message::GetTables.new(options))
97
+ raise ArgumentError.new("Table required") if table.nil?
98
+ table = Table.new(table) if table.is_a?(Hash)
99
+ table.client = self
100
+ send(:delete, "/tables/#{table.name}")
101
+ return nil
82
102
  end
83
103
 
84
104
 
85
105
  ####################################
86
- # Action Messages
106
+ # Property API
87
107
  ####################################
88
108
 
89
- # Adds an action to the server.
109
+ # Retrieves a list of all properties on a table.
90
110
  #
91
- # @param [Action] action the action to add.
92
- def add_action(action, options={})
93
- return send_message(SkyDB::Message::AddAction.new(action, options))
111
+ # @return [Array] the list of properties on the table.
112
+ def get_properties(table, options={})
113
+ raise ArgumentError.new("Table required") if table.nil?
114
+ properties = send(:get, "/tables/#{table.name}/properties")
115
+ properties.map!{|p| Property.new().from_hash(p)}
116
+ return properties
94
117
  end
95
118
 
96
- # Retrieves an individual action from the server.
119
+ # Retrieves a single property by name.
97
120
  #
98
- # @param [Fixnum] action_id the identifier of the action to retrieve.
99
- def get_action(action_id, options={})
100
- return send_message(SkyDB::Message::GetAction.new(action_id, options))
101
- end
102
-
103
- # Retrieves a list of all actions from the server.
104
- def get_actions(options={})
105
- return send_message(SkyDB::Message::GetActions.new(options))
121
+ # @param [Table] table The table to retrieve from.
122
+ # @param [String] name The name of the property to retrieve.
123
+ #
124
+ # @return [Array] the list of properties on the table.
125
+ def get_property(table, name, options={})
126
+ raise ArgumentError.new("Table required") if table.nil?
127
+ data = send(:get, "/tables/#{table.name}/properties/#{name}")
128
+ return Property.new().from_hash(data)
106
129
  end
107
130
 
108
-
109
- ####################################
110
- # Property Messages
111
- ####################################
112
-
113
- # Adds a property to the server.
131
+ # Creates a property on a table.
114
132
  #
115
- # @param [Property] property the property to add.
116
- def add_property(property, options={})
117
- return send_message(SkyDB::Message::AddProperty.new(property, options))
133
+ # @param [Property] property the property to create.
134
+ def create_property(table, property, options={})
135
+ raise ArgumentError.new("Table required") if table.nil?
136
+ raise ArgumentError.new("Property required") if property.nil?
137
+ property = Property.new(property) if property.is_a?(Hash)
138
+ data = send(:post, "/tables/#{table.name}/properties", property.to_hash)
139
+ return property.from_hash(data)
118
140
  end
119
141
 
120
- # Retrieves an individual property from the server.
142
+ # Updates a property on a table.
121
143
  #
122
- # @param [Fixnum] property_id the identifier of the property to retrieve.
123
- def get_property(property_id, options={})
124
- return send_message(SkyDB::Message::GetProperty.new(property_id, options))
144
+ # @param [Property] property the property to update.
145
+ def update_property(table, property, options={})
146
+ raise ArgumentError.new("Table required") if table.nil?
147
+ raise ArgumentError.new("Property required") if property.nil?
148
+ raise ArgumentError.new("Property name required") if property.name.to_s == ''
149
+ property = Property.new(property) if property.is_a?(Hash)
150
+ data = send(:patch, "/tables/#{table.name}/properties/#{property.name}", property.to_hash)
151
+ return property.from_hash(data)
125
152
  end
126
153
 
127
- # Retrieves a list of all properties from the server.
128
- def get_properties(options={})
129
- return send_message(SkyDB::Message::GetProperties.new(options))
154
+ # Deletes a property on a table.
155
+ #
156
+ # @param [Property] property the property to delete.
157
+ def delete_property(table, property, options={})
158
+ raise ArgumentError.new("Table required") if table.nil?
159
+ raise ArgumentError.new("Property required") if property.nil?
160
+ raise ArgumentError.new("Property name required") if property.name.to_s == ''
161
+ property = Property.new(property) if property.is_a?(Hash)
162
+ send(:delete, "/tables/#{table.name}/properties/#{property.name}")
163
+ return nil
130
164
  end
131
165
 
132
166
 
133
167
  ####################################
134
- # Event Messages
168
+ # Event API
135
169
  ####################################
136
170
 
137
- # Adds an event to the server.
171
+ # Retrieves all events for a given object.
138
172
  #
139
- # @param [Event] event the event to add.
140
- def add_event(event, options={})
141
- return send_message(SkyDB::Message::AddEvent.new(event, options))
173
+ # @return [Array] the list of events on the table.
174
+ def get_events(table, object_id, options={})
175
+ raise ArgumentError.new("Table required") if table.nil?
176
+ raise ArgumentError.new("Object identifier required") if object_id.nil?
177
+ events = send(:get, "/tables/#{table.name}/objects/#{object_id}/events")
178
+ events.map!{|e| Event.new().from_hash(e)}
179
+ return events
142
180
  end
143
181
 
144
-
145
- ####################################
146
- # Path Messages
147
- ####################################
148
-
149
- # Finds a count of the action that occurs immediately after a set of
150
- # actions.
182
+ # Retrieves the event that occurred at a given point in time for an object.
151
183
  #
152
- # @param [Array] prior_action_ids the prior action ids to match on.
153
- def next_actions(prior_action_ids, options={})
154
- return send_message(SkyDB::Message::NextActions.new(prior_action_ids, options))
184
+ # @return [Event] the event.
185
+ def get_event(table, object_id, timestamp, options={})
186
+ raise ArgumentError.new("Table required") if table.nil?
187
+ raise ArgumentError.new("Object identifier required") if object_id.nil?
188
+ raise ArgumentError.new("Timestamp required") if timestamp.nil?
189
+ data = send(:get, "/tables/#{table.name}/objects/#{object_id}/events/#{SkyDB.format_timestamp(timestamp)}")
190
+ return Event.new().from_hash(data)
155
191
  end
156
192
 
157
-
158
- ####################################
159
- # Utility message
160
- ####################################
161
-
162
- # Checks if the server is up and running.
193
+ # Adds an event to an object.
163
194
  #
164
- # @returns [Boolean] a flag stating if the server is running.
165
- def ping(options={})
166
- begin
167
- send_message(SkyDB::Message::Ping.new(options))
168
- return true
169
- rescue
170
- return false
171
- end
172
- end
195
+ # @param [Table] table the table the object belongs to.
196
+ # @param [String] object_id the object's identifier.
197
+ # @param [Event] event the event to add.
198
+ #
199
+ # @return [Event] the event.
200
+ def add_event(table, object_id, event, options={})
201
+ options = {:method => :merge}.merge(options)
202
+
203
+ raise ArgumentError.new("Table required") if table.nil?
204
+ raise ArgumentError.new("Object identifier required") if object_id.nil?
205
+ raise ArgumentError.new("Event required") if event.nil?
206
+ event = Event.new(event) if event.is_a?(Hash)
207
+ raise ArgumentError.new("Event timestamp required") if event.timestamp.nil?
208
+
209
+ # The insertion method is communicated to the server through the HTTP method.
210
+ http_method = case options[:method]
211
+ when :replace then :put
212
+ when :merge then :patch
213
+ else raise ArgumentError.new("Invalid event insertion method: #{options[:method]}")
214
+ end
173
215
 
174
- # Looks up lists of actions and properties by name.
175
- def lookup(options={})
176
- send_message(SkyDB::Message::Lookup.new(options))
177
- return nil
216
+ # Send the event and parse it when it comes back. It could have changed.
217
+ data = send(http_method, "/tables/#{table.name}/objects/#{object_id}/events/#{SkyDB.format_timestamp(event.timestamp)}", event.to_hash)
218
+ return event.from_hash(data)
178
219
  end
179
220
 
180
-
181
- ####################################
182
- # Lua Messages
183
- ####################################
184
-
185
- # Executes a Lua aggregation job on the server and returns the results.
221
+ # Deletes an event for an object on a table.
186
222
  #
187
- # @param [String] source the Lua source code to execute
188
- def aggregate(source, options={})
189
- return send_message(SkyDB::Message::Lua::Aggregate.new(source, options))
223
+ # @param [Table] table the table the object belongs to.
224
+ # @param [String] object_id the object's identifier.
225
+ # @param [Event] event the event to delete.
226
+ def delete_event(table, object_id, event, options={})
227
+ raise ArgumentError.new("Table required") if table.nil?
228
+ raise ArgumentError.new("Object identifier required") if object_id.nil?
229
+ raise ArgumentError.new("Event required") if event.nil?
230
+ event = Event.new(event) if event.is_a?(Hash)
231
+ raise ArgumentError.new("Event timestamp required") if event.timestamp.nil?
232
+ send(:delete, "/tables/#{table.name}/objects/#{object_id}/events/#{SkyDB.format_timestamp(event.timestamp)}")
233
+ return nil
190
234
  end
191
235
 
192
236
 
193
237
  ####################################
194
- # Query Interface
238
+ # Query API
195
239
  ####################################
196
240
 
197
- # Starts a query against the database.
198
- def query()
199
- return SkyDB::Query.new(:client => self)
200
- end
201
-
202
- # Starts a query with a single selection against the database.
241
+ # Runs a query against a given table.
242
+ #
243
+ # @param [Table] table The table to query.
244
+ # @param [Hash] q The query definition to run.
203
245
  #
204
- # @param [String] fields a list of properties to select from the database.
205
- def select(fields)
206
- return query.select(fields)
246
+ # @return [Results] the results of the query.
247
+ def query(table, q)
248
+ raise ArgumentError.new("Table required") if table.nil?
249
+ raise ArgumentError.new("Query definition required") if q.nil?
250
+ q = {:steps => q} if q.is_a?(Array)
251
+ return send(:post, "/tables/#{table.name}/query", q)
207
252
  end
208
253
 
209
254
 
210
255
  ####################################
211
- # Multi message
256
+ # Utility API
212
257
  ####################################
213
258
 
214
- # Executes multiple messages in one call.
215
- def multi(options={})
216
- raise "Already in a multi-message block" unless @multi_message.nil?
217
-
218
- # Create multi-message.
219
- @multi_message = SkyDB::Message::Multi.new(options)
220
- @multi_message_max_count = options[:max_count].to_i
221
-
222
- # Execute the block normally and send the message.
259
+ # Pings the server to determine if it is running.
260
+ #
261
+ # @return [Boolean] true if the server is running, otherwise false.
262
+ def ping(options={})
223
263
  begin
224
- yield
225
-
226
- # Send all messages at once.
227
- if @multi_message.messages.length > 0
228
- send_message(@multi_message)
229
- end
230
-
231
- ensure
232
- @multi_message = nil
264
+ send(:get, "/ping")
265
+ rescue
266
+ return false
233
267
  end
234
-
235
- return nil
268
+ return true
236
269
  end
237
-
270
+
238
271
 
239
272
  ####################################
240
- # Send
273
+ # HTTP Utilities
241
274
  ####################################
242
-
243
- # Sends a message to the server.
244
- #
245
- # @param [SkyDB::Message] message the message to send.
246
- # @return [Object] the object returned by the server.
247
- def send_message(message)
248
- # Set the table if they're not set.
249
- message.table_name = table_name if message.table_name.nil? || message.table_name.empty?
250
-
251
- # Validate message before sending.
252
- message.validate!
253
-
254
- # If this is part of a multi message then simply append the message for
255
- # later sending.
256
- if !@multi_message.nil? && @multi_message != message
257
- @multi_message.messages << message
258
-
259
- # Send off the MULTI if the message count is above our limit.
260
- if @multi_message_max_count > 0 && @multi_message.messages.length >= @multi_message_max_count
261
- send_message(@multi_message)
262
- @multi_message = SkyDB::Message::Multi.new()
275
+
276
+ # Executes a RESTful JSON over HTTP POST.
277
+ def send(method, path, data=nil)
278
+ # Generate a JSON request.
279
+ request = case method
280
+ when :get then Net::HTTP::Get.new(path)
281
+ when :post then Net::HTTP::Post.new(path)
282
+ when :patch then Net::HTTP::Patch.new(path)
283
+ when :put then Net::HTTP::Put.new(path)
284
+ when :delete then Net::HTTP::Delete.new(path)
263
285
  end
264
-
265
- return nil
266
-
267
- # Otherwise send the message immediately.
268
- else
269
- begin
270
- # Connect to the server.
271
- socket = TCPSocket.new(host, port.to_i)
272
-
273
- # Encode and send message request.
274
- message.encode(socket)
275
-
276
- # Retrieve the respose as a buffer so we can inspect it.
277
- #msg, x = *socket.recvmsg
278
- #buffer = StringIO.new(msg)
279
- #puts "[#{message.message_name}]< #{buffer.string.to_hex}" if SkyDB.debug
286
+ request.add_field('Content-Type', 'application/json')
287
+ request.body = JSON.generate(data, :max_nesting => 200) unless data.nil?
288
+ response = Net::HTTP.new(host, port).start {|http| http.request(request) }
280
289
 
281
- # Decode msgpack response. There should only be one return object.
282
- response = nil
283
- unpacker = MessagePack::Unpacker.new(socket)
284
- unpacker.each do |obj|
285
- response = obj
286
- break
287
- end
290
+ # Parse the body as JSON.
291
+ json = JSON.parse(response.body) rescue nil
292
+ message = json['message'] rescue nil
288
293
 
289
- # Close socket.
290
- socket.close()
291
-
292
- # TODO: Exception processing.
293
-
294
- # Process response back through the message.
295
- response = message.process_response(response)
296
-
297
- # Return response.
298
- return response
294
+ warn("#{method.to_s.upcase} #{path}: #{request.body} -> #{response.body}") if SkyDB.debug
299
295
 
300
- ensure
301
- # Make sure we remove the multi-message if that's what we're sending.
302
- @multi_message = nil if @multi_message == message
303
- end
296
+ # Process based on the response code.
297
+ case response
298
+ when Net::HTTPSuccess then
299
+ return json
300
+ else
301
+ e = ServerError.new(message)
302
+ e.status = response.code
303
+ raise e
304
304
  end
305
305
  end
306
306
  end