azure-storage-table 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ #-------------------------------------------------------------------------
4
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
5
+ #
6
+ # The MIT License(MIT)
7
+
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files(the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions :
14
+
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+ #--------------------------------------------------------------------------
26
+ require "delegate"
27
+
28
+ module Azure::Storage
29
+ module Table
30
+ # Public: Wrapper around a string to represent a GUID
31
+ #
32
+ class GUID < SimpleDelegator
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,118 @@
1
+ #-------------------------------------------------------------------------
2
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
3
+ #
4
+ # The MIT License(MIT)
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files(the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions :
12
+
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #--------------------------------------------------------------------------
24
+ require "azure/storage/table/table_service"
25
+
26
+ module Azure::Storage
27
+ module Table
28
+ class Query
29
+ def initialize(table = "", partition = nil, row = nil, &block)
30
+ @table = table
31
+ @partition_key = partition
32
+ @row_key = row
33
+ @fields = []
34
+ @filters = []
35
+ @top_n = nil
36
+ @table_service = Azure::Storage::Table::TableService.create_from_env
37
+ self.instance_eval(&block) if block_given?
38
+ end
39
+
40
+ attr_reader :table
41
+ attr_reader :partition_key
42
+ attr_reader :row_key
43
+
44
+ attr_reader :fields
45
+ attr_reader :filters
46
+ attr_reader :top_n
47
+
48
+ attr_reader :next_partition_key
49
+ attr_reader :next_row_key
50
+
51
+ attr_reader :table_service
52
+
53
+ def from(table_name)
54
+ @table = table_name
55
+ self
56
+ end
57
+
58
+ def partition(partition_key)
59
+ @partition_key = partition_key
60
+ self
61
+ end
62
+
63
+ def row(row_key)
64
+ @row_key = row_key
65
+ self
66
+ end
67
+
68
+ def select(*p)
69
+ @fields.concat(p)
70
+ self
71
+ end
72
+
73
+ def where(*p)
74
+ @filters.push(p)
75
+ self
76
+ end
77
+
78
+ def top(n)
79
+ @top_n = n
80
+ self
81
+ end
82
+
83
+ def next_partition(next_partition_key)
84
+ @next_partition_key = next_partition_key
85
+ self
86
+ end
87
+
88
+ def next_row(next_row_key)
89
+ @next_row_key = next_row_key
90
+ self
91
+ end
92
+
93
+ def execute
94
+ @table_service.query_entities(@table, partition_key: @partition_key,
95
+ row_key: @row_key,
96
+ select: @fields.map { |f| f.to_s },
97
+ filter: _build_filter_string,
98
+ top: (@top_n ? @top_n.to_i : @top_n),
99
+ continuation_token: {
100
+ next_partition_key: @next_partition_key,
101
+ next_row_key: @next_row_key
102
+ })
103
+ end
104
+
105
+ def _build_filter_string
106
+ result = ""
107
+ clauses = []
108
+ filters.each { |f|
109
+ clauses.push "#{f[0]} #{f[1]} #{Azure::Storage::Table::EdmType.serialize_query_value(f[2])}"
110
+ }
111
+ return nil if clauses.length == 0
112
+
113
+ result << clauses.join(" and ")
114
+ result
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ #-------------------------------------------------------------------------
4
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
5
+ #
6
+ # The MIT License(MIT)
7
+
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files(the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions :
14
+
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+ #--------------------------------------------------------------------------
26
+ require "time"
27
+ require "date"
28
+ require "json"
29
+
30
+ module Azure
31
+ module Storage
32
+ module Table
33
+ # This is a class that serializes the request/response body for table
34
+ # service.
35
+ module Serialization
36
+ include Azure::Storage::Common::Service::Serialization
37
+
38
+ def self.hash_to_json(h)
39
+ newhash = {}
40
+ h.map do |key, val|
41
+ type = Table::EdmType.property_type(val)
42
+ type_key = key.is_a?(Symbol) ? key.to_s + TableConstants::ODATA_TYPE_SUFFIX : key + TableConstants::ODATA_TYPE_SUFFIX
43
+ newhash[key] = EdmType::serialize_value(type, val)
44
+ newhash[type_key] = type unless type.nil? || type.empty? || h.key?(type_key)
45
+ end
46
+ JSON newhash
47
+ end
48
+
49
+ def self.table_entries_from_json(json)
50
+ table_entries_from_hash(hash_from_json(json))
51
+ end
52
+
53
+ def self.hash_from_json(json)
54
+ JSON.parse(json)
55
+ end
56
+
57
+ def self.table_entries_from_hash(h)
58
+ values = []
59
+ if h["value"]
60
+ h["value"].each do |name|
61
+ values.push(name)
62
+ end
63
+ elsif h["TableName"]
64
+ values = h
65
+ end
66
+ values
67
+ end
68
+
69
+ def self.entity_from_json(json)
70
+ entity_from_hash(hash_from_json(json))
71
+ end
72
+
73
+ def self.entities_from_json(json)
74
+ entities_hash = hash_from_json(json)
75
+ entities_hash["value"].nil? ? [entity_from_hash(entities_hash)] : entities_from_hash(entities_hash)
76
+ end
77
+
78
+ def self.entities_from_hash(h)
79
+ entities = []
80
+ h["value"].each { |entity_hash|
81
+ entities.push(entity_from_hash(entity_hash))
82
+ }
83
+ entities
84
+ end
85
+
86
+ def self.entity_from_hash(h)
87
+ Entity.new do |entity|
88
+ entity.etag = h.delete(TableConstants::ODATA_ETAG)
89
+ properties = {}
90
+ h.each do |k, v|
91
+ next if k.end_with? TableConstants::ODATA_TYPE_SUFFIX
92
+ type = h[k + TableConstants::ODATA_TYPE_SUFFIX]
93
+ properties[k] = EdmType::deserialize_value(v, type.nil? ? EdmType::property_type(v) : type)
94
+ end
95
+ entity.properties = properties
96
+ end
97
+ end
98
+
99
+ def self.get_accept_string(accept_type = :min_meta)
100
+ case accept_type
101
+ when :no_meta
102
+ Azure::Storage::Common::HeaderConstants::ODATA_NO_META
103
+ when :min_meta
104
+ Azure::Storage::Common::HeaderConstants::ODATA_MIN_META
105
+ when :full_meta
106
+ Azure::Storage::Common::HeaderConstants::ODATA_FULL_META
107
+ when nil
108
+ Azure::Storage::Common::HeaderConstants::ODATA_MIN_META
109
+ else
110
+ accept_type
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,775 @@
1
+ # frozen_string_literal: true
2
+
3
+ #-------------------------------------------------------------------------
4
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
5
+ #
6
+ # The MIT License(MIT)
7
+
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files(the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions :
14
+
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+ #--------------------------------------------------------------------------
26
+ require "azure/storage/table/auth/shared_key"
27
+
28
+ module Azure::Storage
29
+ include Azure::Storage::Common::Service
30
+ StorageService = Azure::Storage::Common::Service::StorageService
31
+
32
+ module Table
33
+ class TableService < StorageService
34
+ class << self
35
+ # Public: Creates an instance of [Azure::Storage::Table::TableService]
36
+ #
37
+ # ==== Attributes
38
+ #
39
+ # * +options+ - Hash. Optional parameters.
40
+ #
41
+ # ==== Options
42
+ #
43
+ # Accepted key/value pairs in options parameter are:
44
+ #
45
+ # * +:use_development_storage+ - TrueClass|FalseClass. Whether to use storage emulator.
46
+ # * +:development_storage_proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
47
+ # * +:storage_account_name+ - String. The name of the storage account.
48
+ # * +:storage_access_key+ - Base64 String. The access key of the storage account.
49
+ # * +:storage_sas_token+ - String. The signed access signature for the storage account or one of its service.
50
+ # * +:storage_table_host+ - String. Specified Table service endpoint or hostname
51
+ # * +:storage_dns_suffix+ - String. The suffix of a regional Storage Service, to
52
+ # * +:default_endpoints_protocol+ - String. http or https
53
+ # * +:use_path_style_uri+ - String. Whether use path style URI for specified endpoints
54
+ # * +:ca_file+ - String. File path of the CA file if having issue with SSL
55
+ # * +:user_agent_prefix+ - String. The user agent prefix that can identify the application calls the library
56
+ #
57
+ # The valid set of options include:
58
+ # * Storage Emulator: +:use_development_storage+ required, +:development_storage_proxy_uri+ optionally
59
+ # * Storage account name and key: +:storage_account_name+ and +:storage_access_key+ required, set +:storage_dns_suffix+ necessarily
60
+ # * Storage account name and SAS token: +:storage_account_name+ and +:storage_sas_token+ required, set +:storage_dns_suffix+ necessarily
61
+ # * Specified hosts and SAS token: At least one of the service host and SAS token. It's up to user to ensure the SAS token is suitable for the serivce
62
+ # * Anonymous Table: only +:storage_table_host+, if it is to only access tables within a container
63
+ #
64
+ # Additional notes:
65
+ # * Specified hosts can be set when use account name with access key or sas token
66
+ # * +:default_endpoints_protocol+ can be set if the scheme is not specified in hosts
67
+ # * Storage emulator always use path style URI
68
+ # * +:ca_file+ is independent.
69
+ #
70
+ # When empty options are given, it will try to read settings from Environment Variables. Refer to [Azure::Storage::Common::ClientOptions.env_vars_mapping] for the mapping relationship
71
+ #
72
+ # @return [Azure::Storage::Table::TableService]
73
+ def create(options = {}, &block)
74
+ service_options = { client: Azure::Storage::Common::Client::create(options, &block), api_version: Azure::Storage::Table::Default::STG_VERSION }
75
+ service_options[:user_agent_prefix] = options[:user_agent_prefix] if options[:user_agent_prefix]
76
+ Azure::Storage::Table::TableService.new(service_options, &block)
77
+ end
78
+
79
+ # Public: Creates an instance of [Azure::Storage::Table::TableService] with Storage Emulator
80
+ #
81
+ # ==== Attributes
82
+ #
83
+ # * +proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
84
+ #
85
+ # @return [Azure::Storage::Table::TableService]
86
+ def create_development(proxy_uri = nil, &block)
87
+ service_options = { client: Azure::Storage::Common::Client::create_development(proxy_uri, &block), api_version: Azure::Storage::Table::Default::STG_VERSION }
88
+ Azure::Storage::Table::TableService.new(service_options, &block)
89
+ end
90
+
91
+ # Public: Creates an instance of [Azure::Storage::Table::TableService] from Environment Variables
92
+ #
93
+ # @return [Azure::Storage::Table::TableService]
94
+ def create_from_env(&block)
95
+ service_options = { client: Azure::Storage::Common::Client::create_from_env(&block), api_version: Azure::Storage::Table::Default::STG_VERSION }
96
+ Azure::Storage::Table::TableService.new(service_options, &block)
97
+ end
98
+
99
+ # Public: Creates an instance of [Azure::Storage::Table::TableService] from Environment Variables
100
+ #
101
+ # ==== Attributes
102
+ #
103
+ # * +connection_string+ - String. Please refer to https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/.
104
+ #
105
+ # @return [Azure::Storage::Table::TableService]
106
+ def create_from_connection_string(connection_string, &block)
107
+ service_options = { client: Azure::Storage::Common::Client::create_from_connection_string(connection_string, &block), api_version: Azure::Storage::Table::Default::STG_VERSION }
108
+ Azure::Storage::Table::TableService.new(service_options, &block)
109
+ end
110
+ end
111
+
112
+ # Public: Initializes an instance of [Azure::Storage::Table::TableService]
113
+ #
114
+ # ==== Attributes
115
+ #
116
+ # * +options+ - Hash. Optional parameters.
117
+ #
118
+ # ==== Options
119
+ #
120
+ # Accepted key/value pairs in options parameter are:
121
+ #
122
+ # * +:use_development_storage+ - TrueClass|FalseClass. Whether to use storage emulator.
123
+ # * +:development_storage_proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
124
+ # * +:storage_connection_string+ - String. The storage connection string.
125
+ # * +:storage_account_name+ - String. The name of the storage account.
126
+ # * +:storage_access_key+ - Base64 String. The access key of the storage account.
127
+ # * +:storage_sas_token+ - String. The signed access signature for the storage account or one of its service.
128
+ # * +:storage_table_host+ - String. Specified Table serivce endpoint or hostname
129
+ # * +:storage_dns_suffix+ - String. The suffix of a regional Storage Serivce, to
130
+ # * +:default_endpoints_protocol+ - String. http or https
131
+ # * +:use_path_style_uri+ - String. Whether use path style URI for specified endpoints
132
+ # * +:ca_file+ - String. File path of the CA file if having issue with SSL
133
+ # * +:user_agent_prefix+ - String. The user agent prefix that can identify the application calls the library
134
+ # * +:client+ - Azure::Storage::Common::Client. The common client used to initalize the service.
135
+ #
136
+ # The valid set of options include:
137
+ # * Storage Emulator: +:use_development_storage+ required, +:development_storage_proxy_uri+ optionally
138
+ # * Storage account name and key: +:storage_account_name+ and +:storage_access_key+ required, set +:storage_dns_suffix+ necessarily
139
+ # * Storage account name and SAS token: +:storage_account_name+ and +:storage_sas_token+ required, set +:storage_dns_suffix+ necessarily
140
+ # * Specified hosts and SAS token: At least one of the service host and SAS token. It's up to user to ensure the SAS token is suitable for the serivce
141
+ # * Azure::Storage::Common::Client: The common client used to initalize the service. This client can be initalized and used repeatedly.
142
+ # * Anonymous Table: only +:storage_table_host+, if it is to only access tables within a container
143
+ #
144
+ # Additional notes:
145
+ # * Specified hosts can be set when use account name with access key or sas token
146
+ # * +:default_endpoints_protocol+ can be set if the scheme is not specified in hosts
147
+ # * Storage emulator always use path style URI
148
+ # * +:ca_file+ is independent.
149
+ #
150
+ # When empty options are given, it will try to read settings from Environment Variables. Refer to [Azure::Storage::Common::ClientOptions.env_vars_mapping] for the mapping relationship
151
+ def initialize(options = {}, &block)
152
+ service_options = options.clone
153
+ client_config = service_options[:client] ||= Azure::Storage::Common::Client::create(service_options, &block)
154
+ @user_agent_prefix = service_options[:user_agent_prefix] if service_options[:user_agent_prefix]
155
+ @api_version = service_options[:api_version] || Azure::Storage::Table::Default::STG_VERSION
156
+ signer = service_options[:signer] || client_config.signer || Auth::SharedKey.new(client_config.storage_account_name, client_config.storage_access_key)
157
+ signer.api_ver = @api_version if signer.is_a? Azure::Storage::Common::Core::Auth::SharedAccessSignatureSigner
158
+ super(signer, client_config.storage_account_name, service_options, &block)
159
+ @storage_service_host[:primary] = client.storage_table_host
160
+ @storage_service_host[:secondary] = client.storage_table_host true
161
+ end
162
+
163
+ # Public: Creates new table in the storage account
164
+ #
165
+ # ==== Attributes
166
+ #
167
+ # * +table_name+ - String. The table name
168
+ # * +options+ - Hash. Optional parameters.
169
+ #
170
+ # ==== Options
171
+ #
172
+ # Accepted key/value pairs in options parameter are:
173
+ #
174
+ # * +:timeout+ - Integer. A timeout in seconds.
175
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
176
+ # in the analytics logs when storage analytics logging is enabled.
177
+ # * +:accept+ - String. Specifies the accepted content-type of the response payload. Possible values are:
178
+ # :no_meta
179
+ # :min_meta
180
+ # :full_meta
181
+ # * +:prefer+ - String. Specifies whether the response should include the inserted entity in the payload. Possible values are:
182
+ # Azure::Storage::Common::HeaderConstants::PREFER_CONTENT
183
+ # Azure::Storage::Common::HeaderConstants::PREFER_NO_CONTENT
184
+ #
185
+ # See http://msdn.microsoft.com/en-us/library/azure/dd135729
186
+ #
187
+ # @return [nil] on success
188
+ def create_table(table_name, options = {})
189
+ headers = {
190
+ Azure::Storage::Common::HeaderConstants::ACCEPT => Serialization.get_accept_string(options[:accept]),
191
+ }
192
+ headers[Azure::Storage::Common::HeaderConstants::PREFER] = options[:prefer] unless options[:prefer].nil?
193
+ body = Serialization.hash_to_json("TableName" => table_name)
194
+
195
+ call(:post, collection_uri(new_query(options)), body, headers, options)
196
+ nil
197
+ end
198
+
199
+ # Public: Deletes the specified table and any data it contains.
200
+ #
201
+ # ==== Attributes
202
+ #
203
+ # * +table_name+ - String. The table name
204
+ # * +options+ - Hash. Optional parameters.
205
+ #
206
+ # ==== Options
207
+ #
208
+ # Accepted key/value pairs in options parameter are:
209
+ # * +:timeout+ - Integer. A timeout in seconds.
210
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
211
+ # in the analytics logs when storage analytics logging is enabled.
212
+ #
213
+ # See http://msdn.microsoft.com/en-us/library/azure/dd179387
214
+ #
215
+ # Returns nil on success
216
+ def delete_table(table_name, options = {})
217
+ call(:delete, table_uri(table_name, new_query(options)), nil, {}, options)
218
+ nil
219
+ end
220
+
221
+ # Public: Gets the table.
222
+ #
223
+ # ==== Attributes
224
+ #
225
+ # * +table_name+ - String. The table name
226
+ # * +options+ - Hash. Optional parameters.
227
+ #
228
+ # ==== Options
229
+ #
230
+ # Accepted key/value pairs in options parameter are:
231
+ # * +:timeout+ - Integer. A timeout in seconds.
232
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
233
+ # in the analytics logs when storage analytics logging is enabled.
234
+ # * +:location_mode+ - LocationMode. Specifies the location mode used to decide
235
+ # which location the request should be sent to.
236
+ #
237
+ # Returns the last updated time for the table
238
+ def get_table(table_name, options = {})
239
+ headers = {
240
+ Azure::Storage::Common::HeaderConstants::ACCEPT => Serialization.get_accept_string(:full_meta),
241
+ }
242
+ options[:request_location_mode] = Azure::Storage::Common::RequestLocationMode::PRIMARY_OR_SECONDARY
243
+ response = call(:get, table_uri(table_name, new_query(options), options), nil, headers, options)
244
+ Serialization.table_entries_from_json(response.body)
245
+ rescue => e
246
+ raise_with_response(e, response)
247
+ end
248
+
249
+ # Public: Gets a list of all tables on the account.
250
+ #
251
+ # ==== Attributes
252
+ #
253
+ # * +options+ - Hash. Optional parameters.
254
+ #
255
+ # ==== Options
256
+ #
257
+ # Accepted key/value pairs in options parameter are:
258
+ # * +:next_table_token+ - String. A token used to enumerate the next page of results, when the list of tables is
259
+ # larger than a single operation can return at once. (optional)
260
+ # * +:timeout+ - Integer. A timeout in seconds.
261
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
262
+ # in the analytics logs when storage analytics logging is enabled.
263
+ # * +:location_mode+ - LocationMode. Specifies the location mode used to decide
264
+ # which location the request should be sent to.
265
+ # * +:accept+ - String. Specifies the accepted content-type of the response payload. Possible values are:
266
+ # :no_meta
267
+ # :min_meta
268
+ # :full_meta
269
+ #
270
+ # See http://msdn.microsoft.com/en-us/library/azure/dd179405
271
+ #
272
+ # Returns an array with an extra continuation_token property on success
273
+ def query_tables(options = {})
274
+ query = new_query(options)
275
+ query[TableConstants::NEXT_TABLE_NAME] = options[:next_table_token] if options[:next_table_token]
276
+
277
+ options[:request_location_mode] = Azure::Storage::Common::RequestLocationMode::PRIMARY_OR_SECONDARY
278
+ uri = collection_uri(query, options)
279
+
280
+ headers = {
281
+ Azure::Storage::Common::HeaderConstants::ACCEPT => Serialization.get_accept_string(options[:accept]),
282
+ }
283
+
284
+ response = call(:get, uri, nil, headers, options)
285
+ entries = Serialization.table_entries_from_json(response.body) || []
286
+ values = Azure::Storage::Common::Service::EnumerationResults.new(entries)
287
+ values.continuation_token = response.headers[TableConstants::CONTINUATION_NEXT_TABLE_NAME]
288
+ values
289
+ rescue => e
290
+ raise_with_response(e, response)
291
+ end
292
+
293
+ # Public: Gets the access control list (ACL) for the table.
294
+ #
295
+ # ==== Attributes
296
+ #
297
+ # * +table_name+ - String. The table name
298
+ # * +options+ - Hash. Optional parameters.
299
+ #
300
+ # ==== Options
301
+ #
302
+ # Accepted key/value pairs in options parameter are:
303
+ # * +:timeout+ - Integer. A timeout in seconds.
304
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
305
+ # in the analytics logs when storage analytics logging is enabled.
306
+ # * +:location_mode+ - LocationMode. Specifies the location mode used to decide
307
+ # which location the request should be sent to.
308
+ #
309
+ # See http://msdn.microsoft.com/en-us/library/azure/jj159100
310
+ #
311
+ # Returns a list of Azure::Storage::Entity::SignedIdentifier instances
312
+ def get_table_acl(table_name, options = {})
313
+ query = new_query(options)
314
+ query[Azure::Storage::Common::QueryStringConstants::COMP] = Azure::Storage::Common::QueryStringConstants::ACL
315
+
316
+ options[:request_location_mode] = Azure::Storage::Common::RequestLocationMode::PRIMARY_OR_SECONDARY
317
+ response = call(:get, generate_uri(table_name, query, options), nil, { "x-ms-version" => "2012-02-12" }, options)
318
+
319
+ signed_identifiers = []
320
+ signed_identifiers = Serialization.signed_identifiers_from_xml response.body unless response.body == nil || response.body.length < 1
321
+ signed_identifiers
322
+ rescue => e
323
+ raise_with_response(e, response)
324
+ end
325
+
326
+ # Public: Sets the access control list (ACL) for the table.
327
+ #
328
+ # ==== Attributes
329
+ #
330
+ # * +table_name+ - String. The table name
331
+ # * +options+ - Hash. Optional parameters.
332
+ #
333
+ # ==== Options
334
+ #
335
+ # Accepted key/value pairs in options parameter are:
336
+ # * +:signed_identifiers+ - Array. A list of Azure::Storage::Entity::SignedIdentifier instances
337
+ # * +:timeout+ - Integer. A timeout in seconds.
338
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
339
+ # in the analytics logs when storage analytics logging is enabled.
340
+ #
341
+ # See http://msdn.microsoft.com/en-us/library/azure/jj159102
342
+ #
343
+ # Returns nil on success
344
+ def set_table_acl(table_name, options = {})
345
+ query = new_query(options)
346
+ query[Azure::Storage::Common::QueryStringConstants::COMP] = Azure::Storage::Common::QueryStringConstants::ACL
347
+
348
+ uri = generate_uri(table_name, query)
349
+ body = nil
350
+ body = Serialization.signed_identifiers_to_xml options[:signed_identifiers] if options[:signed_identifiers] && options[:signed_identifiers].length > 0
351
+
352
+ call(:put, uri, body, { "x-ms-version" => "2012-02-12" }, options)
353
+ nil
354
+ end
355
+
356
+ # Public: Inserts new entity to the table.
357
+ #
358
+ #
359
+ # ==== Attributes
360
+ #
361
+ # * +table_name+ - String. The table name
362
+ # * +entity_values+ - Hash. A hash of the name/value pairs for the entity.
363
+ # * +options+ - Hash. Optional parameters.
364
+ #
365
+ # ==== Options
366
+ #
367
+ # Accepted key/value pairs in options parameter are:
368
+ # * +:timeout+ - Integer. A timeout in seconds.
369
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
370
+ # in the analytics logs when storage analytics logging is enabled.
371
+ # * +:accept+ - String. Specifies the accepted content-type of the response payload. Possible values are:
372
+ # :no_meta
373
+ # :min_meta
374
+ # :full_meta
375
+ #
376
+ # See http://msdn.microsoft.com/en-us/library/azure/dd179433
377
+ #
378
+ # Returns a Azure::Storage::Entity::Table::Entity
379
+ def insert_entity(table_name, entity_values, options = {})
380
+ body = Serialization.hash_to_json(entity_values)
381
+ #time = EdmType::to_edm_time(Time.now)
382
+ headers = {
383
+ Azure::Storage::Common::HeaderConstants::ACCEPT => Serialization.get_accept_string(options[:accept])
384
+ }
385
+ response = call(:post, entities_uri(table_name, nil, nil, new_query(options)), body, headers, options)
386
+ result = Serialization.entity_from_json(response.body)
387
+ result.etag = response.headers[Azure::Storage::Common::HeaderConstants::ETAG] if result.etag.nil?
388
+ result
389
+ rescue => e
390
+ raise_with_response(e, response)
391
+ end
392
+
393
+ # Public: Queries entities for the given table name
394
+ #
395
+ # ==== Attributes
396
+ #
397
+ # * +table_name+ - String. The table name
398
+ # * +options+ - Hash. Optional parameters.
399
+ #
400
+ # ==== Options
401
+ #
402
+ # Accepted key/value pairs in options parameter are:
403
+ # * +:partition_key+ - String. The partition key (optional)
404
+ # * +:row_key+ - String. The row key (optional)
405
+ # * +:select+ - Array. An array of property names to return (optional)
406
+ # * +:filter+ - String. A filter expression (optional)
407
+ # * +:top+ - Integer. A limit for the number of results returned (optional)
408
+ # * +:continuation_token+ - Hash. The continuation token.
409
+ # * +:timeout+ - Integer. A timeout in seconds.
410
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
411
+ # in the analytics logs when storage analytics logging is enabled.
412
+ # * +:location_mode+ - LocationMode. Specifies the location mode used to decide
413
+ # which location the request should be sent to.
414
+ # * +:accept+ - String. Specifies the accepted content-type of the response payload. Possible values are:
415
+ # :no_meta
416
+ # :min_meta
417
+ # :full_meta
418
+ #
419
+ # See http://msdn.microsoft.com/en-us/library/azure/dd179421
420
+ #
421
+ # Returns an array with an extra continuation_token property on success
422
+ def query_entities(table_name, options = {})
423
+ query = new_query(options)
424
+ query[Azure::Storage::Common::QueryStringConstants::SELECT] = options[:select].join "," if options[:select]
425
+ query[Azure::Storage::Common::QueryStringConstants::FILTER] = options[:filter] if options[:filter]
426
+ query[Azure::Storage::Common::QueryStringConstants::TOP] = options[:top].to_s if options[:top] unless options[:partition_key] && options[:row_key]
427
+ query[Azure::Storage::Common::QueryStringConstants::NEXT_PARTITION_KEY] = options[:continuation_token][:next_partition_key] if options[:continuation_token] && options[:continuation_token][:next_partition_key]
428
+ query[Azure::Storage::Common::QueryStringConstants::NEXT_ROW_KEY] = options[:continuation_token][:next_row_key] if options[:continuation_token] && options[:continuation_token][:next_row_key]
429
+
430
+ options[:request_location_mode] = Azure::Storage::Common::RequestLocationMode::PRIMARY_OR_SECONDARY
431
+ uri = entities_uri(table_name, options[:partition_key], options[:row_key], query, options)
432
+
433
+ headers = {
434
+ Azure::Storage::Common::HeaderConstants::ACCEPT => Serialization.get_accept_string(options[:accept])
435
+ }
436
+
437
+ response = call(:get, uri, nil, headers, options)
438
+
439
+ entities = Azure::Storage::Common::Service::EnumerationResults.new.push(*Serialization.entities_from_json(response.body))
440
+
441
+ entities.continuation_token = nil
442
+ entities.continuation_token = {
443
+ next_partition_key: response.headers[TableConstants::CONTINUATION_NEXT_PARTITION_KEY],
444
+ next_row_key: response.headers[TableConstants::CONTINUATION_NEXT_ROW_KEY]
445
+ } if response.headers[TableConstants::CONTINUATION_NEXT_PARTITION_KEY]
446
+
447
+ entities
448
+ rescue => e
449
+ raise_with_response(e, response)
450
+ end
451
+
452
+ # Public: Updates an existing entity in a table. The Update Entity operation replaces
453
+ # the entire entity and can be used to remove properties.
454
+ #
455
+ # ==== Attributes
456
+ #
457
+ # * +table_name+ - String. The table name
458
+ # * +entity_values+ - Hash. A hash of the name/value pairs for the entity.
459
+ # * +options+ - Hash. Optional parameters.
460
+ #
461
+ # ==== Options
462
+ #
463
+ # Accepted key/value pairs in options parameter are:
464
+ # * +:if_match+ - String. A matching condition which is required for update (optional, Default="*")
465
+ # * +:create_if_not_exists+ - Boolean. If true, and partition_key and row_key do not reference and existing entity,
466
+ # that entity will be inserted. If false, the operation will fail. (optional, Default=false)
467
+ # * +:timeout+ - Integer. A timeout in seconds.
468
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
469
+ # in the analytics logs when storage analytics logging is enabled.
470
+ #
471
+ # See http://msdn.microsoft.com/en-us/library/azure/dd179427
472
+ #
473
+ # Returns the ETag for the entity on success
474
+ def update_entity(table_name, entity_values, options = {})
475
+ if_match = "*"
476
+ if_match = options[:if_match] if options[:if_match]
477
+
478
+ uri = entities_uri(table_name,
479
+ entity_values[:PartitionKey] || entity_values["PartitionKey"],
480
+ entity_values[:RowKey] || entity_values["RowKey"], new_query(options))
481
+
482
+ headers = {}
483
+ headers["If-Match"] = if_match || "*" unless options[:create_if_not_exists]
484
+
485
+ body = Serialization.hash_to_json(entity_values)
486
+
487
+ response = call(:put, uri, body, headers, options)
488
+ response.headers["etag"]
489
+ rescue => e
490
+ raise_with_response(e, response)
491
+ end
492
+
493
+ # Public: Updates an existing entity by updating the entity's properties. This operation
494
+ # does not replace the existing entity, as the update_entity operation does.
495
+ #
496
+ # ==== Attributes
497
+ #
498
+ # * +table_name+ - String. The table name
499
+ # * +entity_values+ - Hash. A hash of the name/value pairs for the entity.
500
+ # * +options+ - Hash. Optional parameters.
501
+ #
502
+ # ==== Options
503
+ #
504
+ # Accepted key/value pairs in options parameter are:
505
+ # * +:if_match+ - String. A matching condition which is required for update (optional, Default="*")
506
+ # * +:create_if_not_exists+ - Boolean. If true, and partition_key and row_key do not reference and existing entity,
507
+ # that entity will be inserted. If false, the operation will fail. (optional, Default=false)
508
+ # * +:timeout+ - Integer. A timeout in seconds.
509
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
510
+ # in the analytics logs when storage analytics logging is enabled.
511
+ #
512
+ # See http://msdn.microsoft.com/en-us/library/azure/dd179392
513
+ #
514
+ # Returns the ETag for the entity on success
515
+ def merge_entity(table_name, entity_values, options = {})
516
+ if_match = "*"
517
+ if_match = options[:if_match] if options[:if_match]
518
+
519
+ uri = entities_uri(table_name,
520
+ entity_values[:PartitionKey] || entity_values["PartitionKey"],
521
+ entity_values[:RowKey] || entity_values["RowKey"], new_query(options))
522
+
523
+ headers = { "X-HTTP-Method" => "MERGE" }
524
+ headers["If-Match"] = if_match || "*" unless options[:create_if_not_exists]
525
+
526
+ body = Serialization.hash_to_json(entity_values)
527
+
528
+ response = call(:post, uri, body, headers, options)
529
+ response.headers["etag"]
530
+ rescue => e
531
+ raise_with_response(e, response)
532
+ end
533
+
534
+ # Public: Inserts or updates an existing entity within a table by merging new property values into the entity.
535
+ #
536
+ # ==== Attributes
537
+ #
538
+ # * +table_name+ - String. The table name
539
+ # * +entity_values+ - Hash. A hash of the name/value pairs for the entity.
540
+ # * +options+ - Hash. Optional parameters.
541
+ #
542
+ # ==== Options
543
+ #
544
+ # Accepted key/value pairs in options parameter are:
545
+ # * +:timeout+ - Integer. A timeout in seconds.
546
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
547
+ # in the analytics logs when storage analytics logging is enabled.
548
+ #
549
+ # See http://msdn.microsoft.com/en-us/library/azure/hh452241
550
+ #
551
+ # Returns the ETag for the entity on success
552
+ def insert_or_merge_entity(table_name, entity_values, options = {})
553
+ options[:create_if_not_exists] = true
554
+ merge_entity(table_name, entity_values, options)
555
+ end
556
+
557
+ # Public: Inserts or updates a new entity into a table.
558
+ #
559
+ # ==== Attributes
560
+ #
561
+ # * +table_name+ - String. The table name
562
+ # * +entity_values+ - Hash. A hash of the name/value pairs for the entity.
563
+ # * +options+ - Hash. Optional parameters.
564
+ #
565
+ # ==== Options
566
+ #
567
+ # Accepted key/value pairs in options parameter are:
568
+ # * +:timeout+ - Integer. A timeout in seconds.
569
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
570
+ # in the analytics logs when storage analytics logging is enabled.
571
+ #
572
+ # See http://msdn.microsoft.com/en-us/library/azure/hh452242
573
+ #
574
+ # Returns the ETag for the entity on success
575
+ def insert_or_replace_entity(table_name, entity_values, options = {})
576
+ options[:create_if_not_exists] = true
577
+ update_entity(table_name, entity_values, options)
578
+ end
579
+
580
+ # Public: Deletes an existing entity in the table.
581
+ #
582
+ # ==== Attributes
583
+ #
584
+ # * +table_name+ - String. The table name
585
+ # * +partition_key+ - String. The partition key
586
+ # * +row_key+ - String. The row key
587
+ # * +options+ - Hash. Optional parameters.
588
+ #
589
+ # ==== Options
590
+ #
591
+ # Accepted key/value pairs in options parameter are:
592
+ # * +:if_match+ - String. A matching condition which is required for update (optional, Default="*")
593
+ # * +:timeout+ - Integer. A timeout in seconds.
594
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
595
+ # in the analytics logs when storage analytics logging is enabled.
596
+ #
597
+ # See http://msdn.microsoft.com/en-us/library/azure/dd135727
598
+ #
599
+ # Returns nil on success
600
+ def delete_entity(table_name, partition_key, row_key, options = {})
601
+ if_match = "*"
602
+ if_match = options[:if_match] if options[:if_match]
603
+
604
+ call(:delete, entities_uri(table_name, partition_key, row_key, new_query(options)), nil, { "If-Match" => if_match }, options)
605
+ nil
606
+ end
607
+
608
+ # Public: Executes a batch of operations.
609
+ #
610
+ # ==== Attributes
611
+ #
612
+ # * +batch+ - The Azure::Storage::Table::Batch instance to execute.
613
+ # * +options+ - Hash. Optional parameters.
614
+ #
615
+ # ==== Options
616
+ #
617
+ # Accepted key/value pairs in options parameter are:
618
+ # * +:timeout+ - Integer. A timeout in seconds.
619
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
620
+ # in the analytics logs when storage analytics logging is enabled.
621
+ # * +:location_mode+ - LocationMode. Specifies the location mode used to decide
622
+ # which location the request should be sent to.
623
+ #
624
+ # See http://msdn.microsoft.com/en-us/library/azure/dd894038
625
+ #
626
+ # Returns an array of results, one for each operation in the batch
627
+ def execute_batch(batch, options = {})
628
+ headers = {
629
+ Azure::Storage::Common::HeaderConstants::CONTENT_TYPE => "multipart/mixed; boundary=#{batch.batch_id}",
630
+ Azure::Storage::Common::HeaderConstants::ACCEPT => Serialization.get_accept_string(options[:accept]),
631
+ "Accept-Charset" => "UTF-8"
632
+ }
633
+
634
+ body = batch.to_body(self)
635
+ options[:request_location_mode] = Azure::Storage::Common::RequestLocationMode::PRIMARY_OR_SECONDARY
636
+ response = call(:post, generate_uri("/$batch", new_query(options), options), body, headers, options, true)
637
+ batch.parse_response(response)
638
+ rescue => e
639
+ raise_with_response(e, response)
640
+ end
641
+
642
+ # Public: Gets an existing entity in the table.
643
+ #
644
+ # ==== Attributes
645
+ #
646
+ # * +table_name+ - String. The table name
647
+ # * +partition_key+ - String. The partition key
648
+ # * +row_key+ - String. The row key
649
+ # * +options+ - Hash. Optional parameters.
650
+ #
651
+ # ==== Options
652
+ #
653
+ # Accepted key/value pairs in options parameter are:
654
+ # * +:timeout+ - Integer. A timeout in seconds.
655
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
656
+ # in the analytics logs when storage analytics logging is enabled.
657
+ # * +:location_mode+ - LocationMode. Specifies the location mode used to decide
658
+ # which location the request should be sent to.
659
+ #
660
+ # Returns an Azure::Storage::Table::Entity instance on success
661
+ def get_entity(table_name, partition_key, row_key, options = {})
662
+ options[:partition_key] = partition_key
663
+ options[:row_key] = row_key
664
+ results = query_entities(table_name, options)
665
+ results.length > 0 ? results[0] : nil
666
+ end
667
+
668
+ # Protected: Generate the URI for the collection of tables.
669
+ #
670
+ # Returns a URI
671
+ protected
672
+ def collection_uri(query = {}, options = {})
673
+ generate_uri("Tables", query, options)
674
+ end
675
+
676
+ # Public: Generate the URI for a specific table.
677
+ #
678
+ # ==== Attributes
679
+ #
680
+ # * +name+ - The table name. If this is a URI, we just return this
681
+ #
682
+ # Returns a URI
683
+ public
684
+ def table_uri(name, query = {}, options = {})
685
+ return name if name.kind_of? ::URI
686
+ generate_uri("Tables('#{name}')", query, options)
687
+ end
688
+
689
+ # Public: Generate the URI for an entity or group of entities in a table.
690
+ # If both the 'partition_key' and 'row_key' are specified, then the URI
691
+ # will match the entity under those specific keys.
692
+ #
693
+ # ==== Attributes
694
+ #
695
+ # * +table_name+ - The table name
696
+ # * +partition_key+ - The desired partition key (optional)
697
+ # * +row_key+ - The desired row key (optional)
698
+ #
699
+ # Returns a URI
700
+ public
701
+ def entities_uri(table_name, partition_key = nil, row_key = nil, query = {}, options = {})
702
+ return table_name if table_name.kind_of? ::URI
703
+
704
+ path = if partition_key && row_key
705
+ "%s(PartitionKey='%s',RowKey='%s')" % [
706
+ table_name.encode("UTF-8"), encodeODataUriValue(partition_key.encode("UTF-8")), encodeODataUriValue(row_key.encode("UTF-8"))
707
+ ]
708
+ else
709
+ "%s()" % table_name.encode("UTF-8")
710
+ end
711
+
712
+ uri = generate_uri(path, query, options)
713
+ qs = []
714
+ if query
715
+ query.each do | key, val |
716
+ key = key.encode("UTF-8")
717
+ val = val.encode("UTF-8")
718
+
719
+ if key[0] == "$"
720
+ qs.push "#{key}#{::URI.encode_www_form("" => val)}"
721
+ else
722
+ qs.push ::URI.encode_www_form(key => val)
723
+ end
724
+ end
725
+ end
726
+ uri.query = qs.join "&" if qs.length > 0
727
+ uri
728
+ end
729
+
730
+ protected
731
+ def encodeODataUriValues(values)
732
+ new_values = []
733
+ values.each do |value|
734
+ new_values.push encodeODataUriValue(value)
735
+ end
736
+ new_values
737
+ end
738
+
739
+ protected
740
+ def encodeODataUriValue(value)
741
+ # Replace each single quote (') with double single quotes ('') not double
742
+ # quotes (")
743
+ value = value.gsub("'", "''")
744
+
745
+ # Encode the special URL characters
746
+ value = URI.escape(value)
747
+
748
+ value
749
+ end
750
+
751
+ protected
752
+ def raise_with_response(e, response)
753
+ raise e if response.nil?
754
+ raise "Response header: #{response.headers.inspect}\nResponse body: #{response.body.inspect}\n#{e.inspect}\n#{e.backtrace.join("\n")}"
755
+ end
756
+
757
+ protected
758
+ def call(method, uri, body = nil, headers = {}, options = {}, is_batch = false)
759
+ headers["x-ms-version"] = @api_version ? @api_version : Default::STG_VERSION unless headers["x-ms-version"]
760
+ headers["User-Agent"] = @user_agent_prefix ? "#{@user_agent_prefix}; #{Default::USER_AGENT}" : Default::USER_AGENT
761
+ # Add JSON Content-Type header if is_batch is false because default is Atom.
762
+ headers[Azure::Storage::Common::HeaderConstants::CONTENT_TYPE] = Azure::Storage::Common::HeaderConstants::JSON_CONTENT_TYPE_VALUE unless is_batch
763
+ headers[Azure::Storage::Common::HeaderConstants::DATA_SERVICE_VERSION] = TableConstants::DEFAULT_DATA_SERVICE_VERSION
764
+ super(method, uri, body, headers, options)
765
+ end
766
+
767
+ protected
768
+ def new_query(options = {})
769
+ options[:timeout].nil? ? {} : { Azure::Storage::Common::QueryStringConstants::TIMEOUT => options[:timeout].to_s }
770
+ end
771
+ end
772
+ end
773
+ end
774
+
775
+ Azure::Storage::TableService = Azure::Storage::Table::TableService