waz-storage 1.3.1 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -9
- data/CHANGELOG.rdoc +72 -72
- data/Gemfile +4 -4
- data/Gemfile.lock +46 -46
- data/LICENSE +18 -18
- data/README.rdoc +310 -310
- data/lib/waz-blobs.rb +4 -4
- data/lib/waz-queues.rb +6 -6
- data/lib/waz-storage.rb +39 -39
- data/lib/waz-tables.rb +4 -4
- data/lib/waz/blobs/blob_object.rb +122 -122
- data/lib/waz/blobs/container.rb +172 -172
- data/lib/waz/blobs/exceptions.rb +10 -10
- data/lib/waz/blobs/service.rb +181 -181
- data/lib/waz/queues/exceptions.rb +28 -28
- data/lib/waz/queues/message.rb +64 -64
- data/lib/waz/queues/queue.rb +164 -164
- data/lib/waz/queues/service.rb +105 -105
- data/lib/waz/storage/base.rb +70 -70
- data/lib/waz/storage/core_service.rb +2 -1
- data/lib/waz/storage/exceptions.rb +33 -33
- data/lib/waz/storage/validation_rules.rb +25 -25
- data/lib/waz/tables/edm_type_helper.rb +44 -44
- data/lib/waz/tables/exceptions.rb +44 -44
- data/lib/waz/tables/service.rb +178 -178
- data/lib/waz/tables/table.rb +74 -74
- data/lib/waz/tables/table_array.rb +10 -10
- data/rakefile +7 -7
- data/spec/configuration.rb +22 -22
- data/spec/waz/blobs/blob_object_spec.rb +80 -80
- data/spec/waz/blobs/container_spec.rb +175 -175
- data/spec/waz/blobs/service_spec.rb +336 -336
- data/spec/waz/queues/message_spec.rb +32 -32
- data/spec/waz/queues/queue_spec.rb +205 -205
- data/spec/waz/queues/service_spec.rb +298 -298
- data/spec/waz/storage/base_tests.rb +81 -81
- data/spec/waz/storage/shared_key_core_service_spec.rb +141 -141
- data/spec/waz/tables/service_spec.rb +613 -613
- data/spec/waz/tables/table_spec.rb +97 -97
- data/waz-storage.gemspec +29 -29
- metadata +3 -3
data/lib/waz/tables/service.rb
CHANGED
@@ -1,178 +1,178 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Tables
|
3
|
-
# This is internally used by the waz-tables part of the gem and it exposes the Windows Azure Blob API REST methods
|
4
|
-
# implementation. You can use this class to perform an specific operation that isn't provided by the current API.
|
5
|
-
class Service
|
6
|
-
include WAZ::Storage::SharedKeyCoreService
|
7
|
-
|
8
|
-
DATASERVICES_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices"
|
9
|
-
DATASERVICES_METADATA_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
|
10
|
-
|
11
|
-
# Creates a table on the current Windows Azure Storage account.
|
12
|
-
def create_table(table_name)
|
13
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
14
|
-
|
15
|
-
payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
|
16
|
-
"<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
|
17
|
-
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name/></author><id/>" \
|
18
|
-
"<content type=\"application/xml\"><m:properties><d:TableName>#{table_name}</d:TableName></m:properties></content></entry>"
|
19
|
-
|
20
|
-
begin
|
21
|
-
execute :post, 'Tables', {}, default_headers, payload
|
22
|
-
return {:name => table_name, :url => "#{self.base_url}/Tables('#{table_name}"}
|
23
|
-
rescue RestClient::RequestFailed
|
24
|
-
raise WAZ::Tables::TableAlreadyExists, table_name if $!.http_code == 409
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Delete a table on the current Windows Azure Storage account.
|
29
|
-
def delete_table(table_name)
|
30
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
31
|
-
begin
|
32
|
-
execute :delete, "Tables('#{table_name}')", {}, default_headers
|
33
|
-
rescue RestClient::ResourceNotFound
|
34
|
-
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Lists all existing tables on the current storage account.
|
39
|
-
# remove Content-Type if it's not working
|
40
|
-
def list_tables(next_table_name = nil)
|
41
|
-
query = { 'NextTableName' => next_table_name } unless next_table_name.nil?
|
42
|
-
content = execute :get, "Tables", query ||= {}, default_headers
|
43
|
-
|
44
|
-
doc = REXML::Document.new(content)
|
45
|
-
tables = REXML::XPath.each(doc, '/feed/entry').map do |item|
|
46
|
-
{ :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
|
47
|
-
:url => REXML::XPath.first(item, "id").text }
|
48
|
-
end
|
49
|
-
|
50
|
-
return tables, content.headers[:x_ms_continuation_nexttablename]
|
51
|
-
end
|
52
|
-
|
53
|
-
# Retrieves an existing table on the current storage account.
|
54
|
-
# remove Content-Type if it's not working
|
55
|
-
def get_table(table_name)
|
56
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
57
|
-
|
58
|
-
begin
|
59
|
-
content = execute :get, "Tables('#{table_name}')", {}, default_headers
|
60
|
-
doc = REXML::Document.new(content)
|
61
|
-
item = REXML::XPath.first(doc, "entry")
|
62
|
-
return { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
|
63
|
-
:url => REXML::XPath.first(item, "id").text }
|
64
|
-
rescue RestClient::ResourceNotFound
|
65
|
-
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Insert a new entity on the provided table for the current storage account
|
70
|
-
# TODO: catch all api errors as described on Table Service Error Codes on MSDN (http://msdn.microsoft.com/en-us/library/dd179438.aspx)
|
71
|
-
def insert_entity(table_name, entity)
|
72
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
73
|
-
raise WAZ::Tables::TooManyProperties, entity.length if entity.length > 252
|
74
|
-
|
75
|
-
begin
|
76
|
-
response = execute(:post, table_name, {}, default_headers, generate_payload(table_name, entity))
|
77
|
-
return parse_response(response)
|
78
|
-
rescue RestClient::RequestFailed
|
79
|
-
raise WAZ::Tables::EntityAlreadyExists, entity[:row_key] if $!.http_code == 409 and $!.response.body.include?('EntityAlreadyExists')
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Update an existing entity on the current storage account.
|
84
|
-
# TODO: handle specific errors
|
85
|
-
def update_entity(table_name, entity)
|
86
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
87
|
-
response = execute(:put, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}) , generate_payload(table_name, entity))
|
88
|
-
return parse_response(response)
|
89
|
-
end
|
90
|
-
|
91
|
-
# Merge an existing entity on the current storage account.
|
92
|
-
# The Merge Entity operation updates an existing entity by updating the entity's properties.
|
93
|
-
# This operation does not replace the existing entity, as the Update Entity operation does
|
94
|
-
# TODO: handle specific errors
|
95
|
-
def merge_entity(table_name, entity)
|
96
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
97
|
-
response = execute(:merge, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}), generate_payload(table_name, entity))
|
98
|
-
return parse_response(response)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Delete an existing entity in a table.
|
102
|
-
def delete_entity(table_name, partition_key, row_key)
|
103
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
104
|
-
|
105
|
-
begin
|
106
|
-
execute :delete, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers.merge({'If-Match' => '*'})
|
107
|
-
rescue RestClient::ResourceNotFound
|
108
|
-
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404 and $!.response.body.include?('TableNotFound')
|
109
|
-
raise WAZ::Tables::EntityDoesNotExist, "(PartitionKey='#{partition_key}',RowKey='#{row_key}')" if $!.http_code == 404
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# Retrieves an existing entity on the current storage account.
|
114
|
-
# TODO: handle specific errors
|
115
|
-
def get_entity(table_name, partition_key, row_key)
|
116
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
117
|
-
response = execute(:get, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers)
|
118
|
-
return parse_response(response)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Retrieves a set of entities on the current storage account for a given query.
|
122
|
-
# When the :top => n is passed it returns only the first n rows that match with the query
|
123
|
-
# Optional parameters:
|
124
|
-
# * :headers a hash containing the request headers
|
125
|
-
# * :expression the filter query that will be executed against the table (see http://msdn.microsoft.com/en-us/library/dd179421.aspx for more information),
|
126
|
-
# * :top limits the amount of fields for this query.
|
127
|
-
# * :continuation_token the hash obtained when you perform a query that has more than 1000 records or exceeds the allowed timeout (see http://msdn.microsoft.com/en-us/library/dd135718.aspx)
|
128
|
-
def query(table_name, options = {})
|
129
|
-
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
130
|
-
query = {'$filter' => (options[:expression] or '') }
|
131
|
-
query.merge!({ '$top' => options[:top] }) unless options[:top].nil?
|
132
|
-
query.merge!(options[:continuation_token]) unless options[:continuation_token].nil?
|
133
|
-
response = execute :get, "#{table_name}()", query, default_headers
|
134
|
-
continuation_token = {'NextPartitionKey' => response.headers[:x_ms_continuation_nextpartitionkey], 'NextRowKey' => response.headers[:x_ms_continuation_nextrowkey]}
|
135
|
-
parse_response(response, continuation_token)
|
136
|
-
end
|
137
|
-
|
138
|
-
private
|
139
|
-
def generate_payload(table_name, entity)
|
140
|
-
payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
|
141
|
-
"<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
|
142
|
-
"<id>#{generate_request_uri "#{table_name}"}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')</id>" \
|
143
|
-
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name /></author><link rel=\"edit\" title=\"#{table_name}\" href=\"#{table_name}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')\" />" \
|
144
|
-
"<content type=\"application/xml\"><m:properties>"
|
145
|
-
|
146
|
-
entity.sort_by { |k| k.to_s }.each do |k,v|
|
147
|
-
value, type = EdmTypeHelper.parse_to(v)[0].to_s, EdmTypeHelper.parse_to(v)[1].to_s
|
148
|
-
payload << (!v.nil? ? "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\">#{REXML::Text.new(value, false, nil, false).to_s}</d:#{k.to_s}>" : "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\" m:null=\"true\" />") unless k.eql?(:partition_key) or k.eql?(:row_key)
|
149
|
-
end
|
150
|
-
|
151
|
-
payload << "<d:PartitionKey>#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}</d:PartitionKey>" \
|
152
|
-
"<d:RowKey>#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}</d:RowKey>" \
|
153
|
-
"</m:properties></content></entry>"
|
154
|
-
return payload
|
155
|
-
end
|
156
|
-
|
157
|
-
def parse_response(response, continuation_token = nil)
|
158
|
-
doc = REXML::Document.new(response)
|
159
|
-
entities = REXML::XPath.each(doc, '//entry').map do |entry|
|
160
|
-
fields = REXML::XPath.each(entry.elements['content'], 'm:properties/*', {"m" => DATASERVICES_METADATA_NAMESPACE}).map do |f|
|
161
|
-
{ f.name.gsub(/PartitionKey/i, 'partition_key').gsub(/RowKey/i, 'row_key').to_sym => EdmTypeHelper.parse_from(f) }
|
162
|
-
end
|
163
|
-
Hash[*fields.collect {|h| h.to_a}.flatten]
|
164
|
-
end
|
165
|
-
entities = WAZ::Tables::TableArray.new(entities)
|
166
|
-
entities.continuation_token = continuation_token
|
167
|
-
return (REXML::XPath.first(doc, '/feed')) ? entities : entities.first
|
168
|
-
end
|
169
|
-
|
170
|
-
def default_headers
|
171
|
-
{ 'Date' => Time.new.httpdate,
|
172
|
-
'Content-Type' => 'application/atom+xml',
|
173
|
-
'DataServiceVersion' => '1.0;NetFx',
|
174
|
-
'MaxDataServiceVersion' => '1.0;NetFx' }
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Tables
|
3
|
+
# This is internally used by the waz-tables part of the gem and it exposes the Windows Azure Blob API REST methods
|
4
|
+
# implementation. You can use this class to perform an specific operation that isn't provided by the current API.
|
5
|
+
class Service
|
6
|
+
include WAZ::Storage::SharedKeyCoreService
|
7
|
+
|
8
|
+
DATASERVICES_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices"
|
9
|
+
DATASERVICES_METADATA_NAMESPACE = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
|
10
|
+
|
11
|
+
# Creates a table on the current Windows Azure Storage account.
|
12
|
+
def create_table(table_name)
|
13
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
14
|
+
|
15
|
+
payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
|
16
|
+
"<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
|
17
|
+
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name/></author><id/>" \
|
18
|
+
"<content type=\"application/xml\"><m:properties><d:TableName>#{table_name}</d:TableName></m:properties></content></entry>"
|
19
|
+
|
20
|
+
begin
|
21
|
+
execute :post, 'Tables', {}, default_headers, payload
|
22
|
+
return {:name => table_name, :url => "#{self.base_url}/Tables('#{table_name}"}
|
23
|
+
rescue RestClient::RequestFailed
|
24
|
+
raise WAZ::Tables::TableAlreadyExists, table_name if $!.http_code == 409
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Delete a table on the current Windows Azure Storage account.
|
29
|
+
def delete_table(table_name)
|
30
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
31
|
+
begin
|
32
|
+
execute :delete, "Tables('#{table_name}')", {}, default_headers
|
33
|
+
rescue RestClient::ResourceNotFound
|
34
|
+
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Lists all existing tables on the current storage account.
|
39
|
+
# remove Content-Type if it's not working
|
40
|
+
def list_tables(next_table_name = nil)
|
41
|
+
query = { 'NextTableName' => next_table_name } unless next_table_name.nil?
|
42
|
+
content = execute :get, "Tables", query ||= {}, default_headers
|
43
|
+
|
44
|
+
doc = REXML::Document.new(content)
|
45
|
+
tables = REXML::XPath.each(doc, '/feed/entry').map do |item|
|
46
|
+
{ :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
|
47
|
+
:url => REXML::XPath.first(item, "id").text }
|
48
|
+
end
|
49
|
+
|
50
|
+
return tables, content.headers[:x_ms_continuation_nexttablename]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Retrieves an existing table on the current storage account.
|
54
|
+
# remove Content-Type if it's not working
|
55
|
+
def get_table(table_name)
|
56
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
57
|
+
|
58
|
+
begin
|
59
|
+
content = execute :get, "Tables('#{table_name}')", {}, default_headers
|
60
|
+
doc = REXML::Document.new(content)
|
61
|
+
item = REXML::XPath.first(doc, "entry")
|
62
|
+
return { :name => REXML::XPath.first(item.elements['content'], "m:properties/d:TableName", {"m" => DATASERVICES_METADATA_NAMESPACE, "d" => DATASERVICES_NAMESPACE}).text,
|
63
|
+
:url => REXML::XPath.first(item, "id").text }
|
64
|
+
rescue RestClient::ResourceNotFound
|
65
|
+
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Insert a new entity on the provided table for the current storage account
|
70
|
+
# TODO: catch all api errors as described on Table Service Error Codes on MSDN (http://msdn.microsoft.com/en-us/library/dd179438.aspx)
|
71
|
+
def insert_entity(table_name, entity)
|
72
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
73
|
+
raise WAZ::Tables::TooManyProperties, entity.length if entity.length > 252
|
74
|
+
|
75
|
+
begin
|
76
|
+
response = execute(:post, table_name, {}, default_headers, generate_payload(table_name, entity))
|
77
|
+
return parse_response(response)
|
78
|
+
rescue RestClient::RequestFailed
|
79
|
+
raise WAZ::Tables::EntityAlreadyExists, entity[:row_key] if $!.http_code == 409 and $!.response.body.include?('EntityAlreadyExists')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Update an existing entity on the current storage account.
|
84
|
+
# TODO: handle specific errors
|
85
|
+
def update_entity(table_name, entity)
|
86
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
87
|
+
response = execute(:put, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}) , generate_payload(table_name, entity))
|
88
|
+
return parse_response(response)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Merge an existing entity on the current storage account.
|
92
|
+
# The Merge Entity operation updates an existing entity by updating the entity's properties.
|
93
|
+
# This operation does not replace the existing entity, as the Update Entity operation does
|
94
|
+
# TODO: handle specific errors
|
95
|
+
def merge_entity(table_name, entity)
|
96
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
97
|
+
response = execute(:merge, "#{table_name}(PartitionKey='#{entity[:partition_key]}',RowKey='#{entity[:row_key]}')", {}, default_headers.merge({'If-Match' => '*'}), generate_payload(table_name, entity))
|
98
|
+
return parse_response(response)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Delete an existing entity in a table.
|
102
|
+
def delete_entity(table_name, partition_key, row_key)
|
103
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
104
|
+
|
105
|
+
begin
|
106
|
+
execute :delete, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers.merge({'If-Match' => '*'})
|
107
|
+
rescue RestClient::ResourceNotFound
|
108
|
+
raise WAZ::Tables::TableDoesNotExist, table_name if $!.http_code == 404 and $!.response.body.include?('TableNotFound')
|
109
|
+
raise WAZ::Tables::EntityDoesNotExist, "(PartitionKey='#{partition_key}',RowKey='#{row_key}')" if $!.http_code == 404
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Retrieves an existing entity on the current storage account.
|
114
|
+
# TODO: handle specific errors
|
115
|
+
def get_entity(table_name, partition_key, row_key)
|
116
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
117
|
+
response = execute(:get, "#{table_name}(PartitionKey='#{partition_key}',RowKey='#{row_key}')", {}, default_headers)
|
118
|
+
return parse_response(response)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Retrieves a set of entities on the current storage account for a given query.
|
122
|
+
# When the :top => n is passed it returns only the first n rows that match with the query
|
123
|
+
# Optional parameters:
|
124
|
+
# * :headers a hash containing the request headers
|
125
|
+
# * :expression the filter query that will be executed against the table (see http://msdn.microsoft.com/en-us/library/dd179421.aspx for more information),
|
126
|
+
# * :top limits the amount of fields for this query.
|
127
|
+
# * :continuation_token the hash obtained when you perform a query that has more than 1000 records or exceeds the allowed timeout (see http://msdn.microsoft.com/en-us/library/dd135718.aspx)
|
128
|
+
def query(table_name, options = {})
|
129
|
+
raise WAZ::Tables::InvalidTableName, table_name unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
130
|
+
query = {'$filter' => (options[:expression] or '') }
|
131
|
+
query.merge!({ '$top' => options[:top] }) unless options[:top].nil?
|
132
|
+
query.merge!(options[:continuation_token]) unless options[:continuation_token].nil?
|
133
|
+
response = execute :get, "#{table_name}()", query, default_headers
|
134
|
+
continuation_token = {'NextPartitionKey' => response.headers[:x_ms_continuation_nextpartitionkey], 'NextRowKey' => response.headers[:x_ms_continuation_nextrowkey]}
|
135
|
+
parse_response(response, continuation_token)
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def generate_payload(table_name, entity)
|
140
|
+
payload = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" \
|
141
|
+
"<entry xmlns:d=\"#{DATASERVICES_NAMESPACE}\" xmlns:m=\"#{DATASERVICES_METADATA_NAMESPACE}\" xmlns=\"http://www.w3.org/2005/Atom\">" \
|
142
|
+
"<id>#{generate_request_uri "#{table_name}"}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')</id>" \
|
143
|
+
"<title /><updated>#{Time.now.utc.iso8601}</updated><author><name /></author><link rel=\"edit\" title=\"#{table_name}\" href=\"#{table_name}(PartitionKey='#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}',RowKey='#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}')\" />" \
|
144
|
+
"<content type=\"application/xml\"><m:properties>"
|
145
|
+
|
146
|
+
entity.sort_by { |k| k.to_s }.each do |k,v|
|
147
|
+
value, type = EdmTypeHelper.parse_to(v)[0].to_s, EdmTypeHelper.parse_to(v)[1].to_s
|
148
|
+
payload << (!v.nil? ? "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\">#{REXML::Text.new(value, false, nil, false).to_s}</d:#{k.to_s}>" : "<d:#{k.to_s} m:type=\"#{k.edm_type || type}\" m:null=\"true\" />") unless k.eql?(:partition_key) or k.eql?(:row_key)
|
149
|
+
end
|
150
|
+
|
151
|
+
payload << "<d:PartitionKey>#{REXML::Text.new(entity[:partition_key], false, nil, false).to_s}</d:PartitionKey>" \
|
152
|
+
"<d:RowKey>#{REXML::Text.new(entity[:row_key], false, nil, false).to_s}</d:RowKey>" \
|
153
|
+
"</m:properties></content></entry>"
|
154
|
+
return payload
|
155
|
+
end
|
156
|
+
|
157
|
+
def parse_response(response, continuation_token = nil)
|
158
|
+
doc = REXML::Document.new(response)
|
159
|
+
entities = REXML::XPath.each(doc, '//entry').map do |entry|
|
160
|
+
fields = REXML::XPath.each(entry.elements['content'], 'm:properties/*', {"m" => DATASERVICES_METADATA_NAMESPACE}).map do |f|
|
161
|
+
{ f.name.gsub(/PartitionKey/i, 'partition_key').gsub(/RowKey/i, 'row_key').to_sym => EdmTypeHelper.parse_from(f) }
|
162
|
+
end
|
163
|
+
Hash[*fields.collect {|h| h.to_a}.flatten]
|
164
|
+
end
|
165
|
+
entities = WAZ::Tables::TableArray.new(entities)
|
166
|
+
entities.continuation_token = continuation_token
|
167
|
+
return (REXML::XPath.first(doc, '/feed')) ? entities : entities.first
|
168
|
+
end
|
169
|
+
|
170
|
+
def default_headers
|
171
|
+
{ 'Date' => Time.new.httpdate,
|
172
|
+
'Content-Type' => 'application/atom+xml',
|
173
|
+
'DataServiceVersion' => '1.0;NetFx',
|
174
|
+
'MaxDataServiceVersion' => '1.0;NetFx' }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/lib/waz/tables/table.rb
CHANGED
@@ -1,75 +1,75 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Tables
|
3
|
-
# This class represents a Table on Windows Azure Tables API. These are the methods implemented from Microsoft's API description
|
4
|
-
# available on MSDN at http://msdn.microsoft.com/en-us/library/dd179423.aspx
|
5
|
-
#
|
6
|
-
# # list available tables
|
7
|
-
# tables = WAZ::Tables::Table.list
|
8
|
-
#
|
9
|
-
# # list more tables
|
10
|
-
# WAZ::Tables::Table.list(tables.continuation_token)
|
11
|
-
#
|
12
|
-
# # get a specific table
|
13
|
-
# my_table = WAZ::Tables::Table.find('my-table')
|
14
|
-
#
|
15
|
-
# # delete table
|
16
|
-
# my_table.destroy!
|
17
|
-
#
|
18
|
-
# # create a new table
|
19
|
-
# WAZ::Tables::Table.create('new-table')
|
20
|
-
#
|
21
|
-
class Table
|
22
|
-
class << self
|
23
|
-
INVALID_TABLE_ERROR_MESSAGE = "must start with at least one lower/upper characted, can have character or any digit starting from the second position, must be from 3 through 63 characters long"
|
24
|
-
|
25
|
-
# Finds a table by name. It will return nil if no table was found.
|
26
|
-
def find(table_name)
|
27
|
-
raise WAZ::Storage::InvalidParameterValue, {:name => table_name, :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
28
|
-
begin
|
29
|
-
WAZ::Tables::Table.new(service_instance.get_table(table_name))
|
30
|
-
rescue WAZ::Tables::TableDoesNotExist
|
31
|
-
return nil
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Returns an array of the existing tables (WAZ::Tables::Table) on the current
|
36
|
-
# Windows Azure Storage account.
|
37
|
-
def list(continuation_token = {})
|
38
|
-
table_list, next_table_name = service_instance.list_tables(continuation_token['NextTableName'])
|
39
|
-
tables = TableArray.new(table_list.map { |table| WAZ::Tables::Table.new({ :name => table[:name], :url => table[:url] }) })
|
40
|
-
tables.continuation_token = {'NextTableName' => next_table_name} unless next_table_name.nil?
|
41
|
-
return tables
|
42
|
-
end
|
43
|
-
|
44
|
-
# Creates a table on the current account.
|
45
|
-
def create(table_name)
|
46
|
-
raise WAZ::Storage::InvalidParameterValue, {:name => table_name, :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
47
|
-
WAZ::Tables::Table.new(service_instance.create_table(table_name))
|
48
|
-
end
|
49
|
-
|
50
|
-
# This method is internally used by this class. It's the way we keep a single instance of the
|
51
|
-
# service that wraps the calls the Windows Azure Tables API. It's initialized with the values
|
52
|
-
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
53
|
-
def service_instance
|
54
|
-
options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "table")
|
55
|
-
(@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
attr_accessor :name, :url
|
60
|
-
|
61
|
-
def initialize(options = {})
|
62
|
-
raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name) and !options[:name].empty?
|
63
|
-
raise WAZ::Storage::InvalidOption, :url unless options.keys.include?(:url) and !options[:url].empty?
|
64
|
-
raise WAZ::Storage::InvalidParameterValue, {:name => options[:name], :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(options[:name])
|
65
|
-
self.name = options[:name]
|
66
|
-
self.url = options[:url]
|
67
|
-
end
|
68
|
-
|
69
|
-
# Removes the table from the current account.
|
70
|
-
def destroy!
|
71
|
-
self.class.service_instance.delete_table(self.name)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Tables
|
3
|
+
# This class represents a Table on Windows Azure Tables API. These are the methods implemented from Microsoft's API description
|
4
|
+
# available on MSDN at http://msdn.microsoft.com/en-us/library/dd179423.aspx
|
5
|
+
#
|
6
|
+
# # list available tables
|
7
|
+
# tables = WAZ::Tables::Table.list
|
8
|
+
#
|
9
|
+
# # list more tables
|
10
|
+
# WAZ::Tables::Table.list(tables.continuation_token)
|
11
|
+
#
|
12
|
+
# # get a specific table
|
13
|
+
# my_table = WAZ::Tables::Table.find('my-table')
|
14
|
+
#
|
15
|
+
# # delete table
|
16
|
+
# my_table.destroy!
|
17
|
+
#
|
18
|
+
# # create a new table
|
19
|
+
# WAZ::Tables::Table.create('new-table')
|
20
|
+
#
|
21
|
+
class Table
|
22
|
+
class << self
|
23
|
+
INVALID_TABLE_ERROR_MESSAGE = "must start with at least one lower/upper characted, can have character or any digit starting from the second position, must be from 3 through 63 characters long"
|
24
|
+
|
25
|
+
# Finds a table by name. It will return nil if no table was found.
|
26
|
+
def find(table_name)
|
27
|
+
raise WAZ::Storage::InvalidParameterValue, {:name => table_name, :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
28
|
+
begin
|
29
|
+
WAZ::Tables::Table.new(service_instance.get_table(table_name))
|
30
|
+
rescue WAZ::Tables::TableDoesNotExist
|
31
|
+
return nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns an array of the existing tables (WAZ::Tables::Table) on the current
|
36
|
+
# Windows Azure Storage account.
|
37
|
+
def list(continuation_token = {})
|
38
|
+
table_list, next_table_name = service_instance.list_tables(continuation_token['NextTableName'])
|
39
|
+
tables = TableArray.new(table_list.map { |table| WAZ::Tables::Table.new({ :name => table[:name], :url => table[:url] }) })
|
40
|
+
tables.continuation_token = {'NextTableName' => next_table_name} unless next_table_name.nil?
|
41
|
+
return tables
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates a table on the current account.
|
45
|
+
def create(table_name)
|
46
|
+
raise WAZ::Storage::InvalidParameterValue, {:name => table_name, :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(table_name)
|
47
|
+
WAZ::Tables::Table.new(service_instance.create_table(table_name))
|
48
|
+
end
|
49
|
+
|
50
|
+
# This method is internally used by this class. It's the way we keep a single instance of the
|
51
|
+
# service that wraps the calls the Windows Azure Tables API. It's initialized with the values
|
52
|
+
# from the default_connection on WAZ::Storage::Base initialized thru establish_connection!
|
53
|
+
def service_instance
|
54
|
+
options = WAZ::Storage::Base.default_connection.merge(:type_of_service => "table")
|
55
|
+
(@service_instances ||= {})[options[:account_name]] ||= Service.new(options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :name, :url
|
60
|
+
|
61
|
+
def initialize(options = {})
|
62
|
+
raise WAZ::Storage::InvalidOption, :name unless options.keys.include?(:name) and !options[:name].empty?
|
63
|
+
raise WAZ::Storage::InvalidOption, :url unless options.keys.include?(:url) and !options[:url].empty?
|
64
|
+
raise WAZ::Storage::InvalidParameterValue, {:name => options[:name], :values => [INVALID_TABLE_ERROR_MESSAGE]} unless WAZ::Storage::ValidationRules.valid_table_name?(options[:name])
|
65
|
+
self.name = options[:name]
|
66
|
+
self.url = options[:url]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Removes the table from the current account.
|
70
|
+
def destroy!
|
71
|
+
self.class.service_instance.delete_table(self.name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
75
|
end
|