1package storage 2 3// Copyright 2017 Microsoft Corporation 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17import ( 18 "encoding/json" 19 "fmt" 20 "io/ioutil" 21 "net/http" 22 "net/url" 23 "strconv" 24) 25 26const ( 27 headerAccept = "Accept" 28 headerEtag = "Etag" 29 headerPrefer = "Prefer" 30 headerXmsContinuation = "x-ms-Continuation-NextTableName" 31) 32 33// TableServiceClient contains operations for Microsoft Azure Table Storage 34// Service. 35type TableServiceClient struct { 36 client Client 37 auth authentication 38} 39 40// TableOptions includes options for some table operations 41type TableOptions struct { 42 RequestID string 43} 44 45func (options *TableOptions) addToHeaders(h map[string]string) map[string]string { 46 if options != nil { 47 h = addToHeaders(h, "x-ms-client-request-id", options.RequestID) 48 } 49 return h 50} 51 52// QueryNextLink includes information for getting the next page of 53// results in query operations 54type QueryNextLink struct { 55 NextLink *string 56 ml MetadataLevel 57} 58 59// GetServiceProperties gets the properties of your storage account's table service. 60// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-table-service-properties 61func (t *TableServiceClient) GetServiceProperties() (*ServiceProperties, error) { 62 return t.client.getServiceProperties(tableServiceName, t.auth) 63} 64 65// SetServiceProperties sets the properties of your storage account's table service. 66// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-table-service-properties 67func (t *TableServiceClient) SetServiceProperties(props ServiceProperties) error { 68 return t.client.setServiceProperties(props, tableServiceName, t.auth) 69} 70 71// GetTableReference returns a Table object for the specified table name. 72func (t *TableServiceClient) GetTableReference(name string) *Table { 73 return &Table{ 74 tsc: t, 75 Name: name, 76 } 77} 78 79// QueryTablesOptions includes options for some table operations 80type QueryTablesOptions struct { 81 Top uint 82 Filter string 83 RequestID string 84} 85 86func (options *QueryTablesOptions) getParameters() (url.Values, map[string]string) { 87 query := url.Values{} 88 headers := map[string]string{} 89 if options != nil { 90 if options.Top > 0 { 91 query.Add(OdataTop, strconv.FormatUint(uint64(options.Top), 10)) 92 } 93 if options.Filter != "" { 94 query.Add(OdataFilter, options.Filter) 95 } 96 headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID) 97 } 98 return query, headers 99} 100 101// QueryTables returns the tables in the storage account. 102// You can use query options defined by the OData Protocol specification. 103// 104// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-tables 105func (t *TableServiceClient) QueryTables(ml MetadataLevel, options *QueryTablesOptions) (*TableQueryResult, error) { 106 query, headers := options.getParameters() 107 uri := t.client.getEndpoint(tableServiceName, tablesURIPath, query) 108 return t.queryTables(uri, headers, ml) 109} 110 111// NextResults returns the next page of results 112// from a QueryTables or a NextResults operation. 113// 114// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-tables 115// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-timeout-and-pagination 116func (tqr *TableQueryResult) NextResults(options *TableOptions) (*TableQueryResult, error) { 117 if tqr == nil { 118 return nil, errNilPreviousResult 119 } 120 if tqr.NextLink == nil { 121 return nil, errNilNextLink 122 } 123 headers := options.addToHeaders(map[string]string{}) 124 125 return tqr.tsc.queryTables(*tqr.NextLink, headers, tqr.ml) 126} 127 128// TableQueryResult contains the response from 129// QueryTables and QueryTablesNextResults functions. 130type TableQueryResult struct { 131 OdataMetadata string `json:"odata.metadata"` 132 Tables []Table `json:"value"` 133 QueryNextLink 134 tsc *TableServiceClient 135} 136 137func (t *TableServiceClient) queryTables(uri string, headers map[string]string, ml MetadataLevel) (*TableQueryResult, error) { 138 if ml == EmptyPayload { 139 return nil, errEmptyPayload 140 } 141 headers = mergeHeaders(headers, t.client.getStandardHeaders()) 142 headers[headerAccept] = string(ml) 143 144 resp, err := t.client.exec(http.MethodGet, uri, headers, nil, t.auth) 145 if err != nil { 146 return nil, err 147 } 148 defer resp.Body.Close() 149 150 if err := checkRespCode(resp, []int{http.StatusOK}); err != nil { 151 return nil, err 152 } 153 154 respBody, err := ioutil.ReadAll(resp.Body) 155 if err != nil { 156 return nil, err 157 } 158 var out TableQueryResult 159 err = json.Unmarshal(respBody, &out) 160 if err != nil { 161 return nil, err 162 } 163 164 for i := range out.Tables { 165 out.Tables[i].tsc = t 166 } 167 out.tsc = t 168 169 nextLink := resp.Header.Get(http.CanonicalHeaderKey(headerXmsContinuation)) 170 if nextLink == "" { 171 out.NextLink = nil 172 } else { 173 originalURI, err := url.Parse(uri) 174 if err != nil { 175 return nil, err 176 } 177 v := originalURI.Query() 178 v.Set(nextTableQueryParameter, nextLink) 179 newURI := t.client.getEndpoint(tableServiceName, tablesURIPath, v) 180 out.NextLink = &newURI 181 out.ml = ml 182 } 183 184 return &out, nil 185} 186 187func addBodyRelatedHeaders(h map[string]string, length int) map[string]string { 188 h[headerContentType] = "application/json" 189 h[headerContentLength] = fmt.Sprintf("%v", length) 190 h[headerAcceptCharset] = "UTF-8" 191 return h 192} 193 194func addReturnContentHeaders(h map[string]string, ml MetadataLevel) map[string]string { 195 if ml != EmptyPayload { 196 h[headerPrefer] = "return-content" 197 h[headerAccept] = string(ml) 198 } else { 199 h[headerPrefer] = "return-no-content" 200 // From API version 2015-12-11 onwards, Accept header is required 201 h[headerAccept] = string(NoMetadata) 202 } 203 return h 204} 205