diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java index af8b9e303bdf..f0d1edc986af 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java @@ -85,6 +85,7 @@ public interface Constants { String CUSTOM_FILTERS = "hbase.rest.custom.filters"; String ROW_KEYS_PARAM_NAME = "row"; + String KEY_ENCODING_QUERY_PARAM_NAME = "e"; /** * If this query parameter is present when processing row or scanner resources, it disables server * side block caching diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/MultiRowResource.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/MultiRowResource.java index cc5fb22265c8..82900135dc40 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/MultiRowResource.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/MultiRowResource.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import org.apache.hbase.thirdparty.javax.ws.rs.GET; +import org.apache.hbase.thirdparty.javax.ws.rs.HeaderParam; import org.apache.hbase.thirdparty.javax.ws.rs.Produces; import org.apache.hbase.thirdparty.javax.ws.rs.core.Context; import org.apache.hbase.thirdparty.javax.ws.rs.core.MultivaluedMap; @@ -63,14 +64,18 @@ public MultiRowResource(TableResource tableResource, String versions, String col @GET @Produces({ MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, MIMETYPE_PROTOBUF_IETF }) - public Response get(final @Context UriInfo uriInfo) { + public Response get(final @Context UriInfo uriInfo, + final @HeaderParam("Encoding") String keyEncodingHeader) { MultivaluedMap params = uriInfo.getQueryParameters(); + String keyEncoding = (keyEncodingHeader != null) + ? keyEncodingHeader + : params.getFirst(KEY_ENCODING_QUERY_PARAM_NAME); servlet.getMetrics().incrementRequests(1); try { CellSetModel model = new CellSetModel(); for (String rk : params.get(ROW_KEYS_PARAM_NAME)) { - RowSpec rowSpec = new RowSpec(rk); + RowSpec rowSpec = new RowSpec(rk, keyEncoding); if (this.versions != null) { rowSpec.setMaxVersions(this.versions); diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RowResource.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RowResource.java index b599b0b19491..1f0c75ae4814 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RowResource.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RowResource.java @@ -72,10 +72,10 @@ public class RowResource extends ResourceBase { * Constructor */ public RowResource(TableResource tableResource, String rowspec, String versions, String check, - String returnResult) throws IOException { + String returnResult, String keyEncoding) throws IOException { super(); this.tableResource = tableResource; - this.rowspec = new RowSpec(rowspec); + this.rowspec = new RowSpec(rowspec, keyEncoding); if (versions != null) { this.rowspec.setMaxVersions(Integer.parseInt(versions)); } diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RowSpec.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RowSpec.java index c9993336fa14..e1559dd67233 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RowSpec.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RowSpec.java @@ -20,6 +20,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; +import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -47,6 +48,10 @@ public class RowSpec { private int maxValues = Integer.MAX_VALUE; public RowSpec(String path) throws IllegalArgumentException { + this(path, null); + } + + public RowSpec(String path, String keyEncoding) throws IllegalArgumentException { int i = 0; while (path.charAt(i) == '/') { i++; @@ -55,6 +60,34 @@ public RowSpec(String path) throws IllegalArgumentException { i = parseColumns(path, i); i = parseTimestamp(path, i); i = parseQueryParams(path, i); + + if (keyEncoding != null) { + // See https://en.wikipedia.org/wiki/Base64#Variants_summary_table + Base64.Decoder decoder; + switch (keyEncoding) { + case "b64": + case "base64": + case "b64url": + case "base64url": + decoder = Base64.getUrlDecoder(); + break; + case "b64basic": + case "base64basic": + decoder = Base64.getDecoder(); + break; + default: + throw new IllegalArgumentException("unknown key encoding '" + keyEncoding + "'"); + } + this.row = decoder.decode(this.row); + if (this.endRow != null) { + this.endRow = decoder.decode(this.endRow); + } + TreeSet decodedColumns = new TreeSet<>(Bytes.BYTES_COMPARATOR); + for (byte[] encodedColumn : this.columns) { + decodedColumns.add(decoder.decode(encodedColumn)); + } + this.columns = decodedColumns; + } } private int parseRowKeys(final String path, int i) throws IllegalArgumentException { diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/TableResource.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/TableResource.java index dbac4686520f..3a89d79a3986 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/TableResource.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/TableResource.java @@ -35,6 +35,7 @@ import org.apache.hbase.thirdparty.javax.ws.rs.DefaultValue; import org.apache.hbase.thirdparty.javax.ws.rs.Encoded; +import org.apache.hbase.thirdparty.javax.ws.rs.HeaderParam; import org.apache.hbase.thirdparty.javax.ws.rs.Path; import org.apache.hbase.thirdparty.javax.ws.rs.PathParam; import org.apache.hbase.thirdparty.javax.ws.rs.QueryParam; @@ -94,9 +95,12 @@ public RowResource getRowResource( // We need the @Encoded decorator so Jersey won't urldecode before // the RowSpec constructor has a chance to parse final @PathParam("rowspec") @Encoded String rowspec, final @QueryParam("v") String versions, - final @QueryParam("check") String check, final @QueryParam("rr") String returnResult) + final @QueryParam("check") String check, final @QueryParam("rr") String returnResult, + final @HeaderParam("Encoding") String keyEncodingHeader, + final @QueryParam(Constants.KEY_ENCODING_QUERY_PARAM_NAME) String keyEncodingQuery) throws IOException { - return new RowResource(this, rowspec, versions, check, returnResult); + String keyEncoding = (keyEncodingHeader != null) ? keyEncodingHeader : keyEncodingQuery; + return new RowResource(this, rowspec, versions, check, returnResult, keyEncoding); } @Path("{suffixglobbingspec: .*\\*/.+}") @@ -105,8 +109,12 @@ public RowResource getRowResourceWithSuffixGlobbing( // the RowSpec constructor has a chance to parse final @PathParam("suffixglobbingspec") @Encoded String suffixglobbingspec, final @QueryParam("v") String versions, final @QueryParam("check") String check, - final @QueryParam("rr") String returnResult) throws IOException { - return new RowResource(this, suffixglobbingspec, versions, check, returnResult); + final @QueryParam("rr") String returnResult, + final @HeaderParam("Encoding") String keyEncodingHeader, + final @QueryParam(Constants.KEY_ENCODING_QUERY_PARAM_NAME) String keyEncodingQuery) + throws IOException { + String keyEncoding = (keyEncodingHeader != null) ? keyEncodingHeader : keyEncodingQuery; + return new RowResource(this, suffixglobbingspec, versions, check, returnResult, keyEncoding); } @Path("{scanspec: .*[*]$}") diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/RowResourceBase.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/RowResourceBase.java index 27de4c5803c4..6373c8515e01 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/RowResourceBase.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/RowResourceBase.java @@ -23,6 +23,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; +import java.util.Base64; import java.util.HashMap; import java.util.Map; import javax.xml.bind.JAXBContext; @@ -43,6 +44,8 @@ import org.apache.hadoop.hbase.rest.model.CellSetModel; import org.apache.hadoop.hbase.rest.model.RowModel; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.http.Header; +import org.apache.http.message.BasicHeader; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -464,6 +467,15 @@ protected static Response getValueXML(String url) throws IOException { return response; } + protected static Response getValueXML(String url, Header[] headers) throws IOException { + Header[] fullHeaders = new Header[headers.length + 1]; + for (int i = 0; i < headers.length; i++) + fullHeaders[i] = headers[i]; + fullHeaders[headers.length] = new BasicHeader("Accept", Constants.MIMETYPE_XML); + Response response = client.get(url, fullHeaders); + return response; + } + protected static Response getValueJson(String url) throws IOException { Response response = client.get(url, Constants.MIMETYPE_JSON); return response; @@ -483,6 +495,28 @@ protected static Response deleteValue(String table, String row, String column) return response; } + protected static Response deleteValueB64(String table, String row, String column, + boolean useQueryString) throws IOException { + StringBuilder path = new StringBuilder(); + Base64.Encoder encoder = Base64.getUrlEncoder(); + path.append('/'); + path.append(table); + path.append('/'); + path.append(encoder.encodeToString(row.getBytes("UTF-8"))); + path.append('/'); + path.append(encoder.encodeToString(column.getBytes("UTF-8"))); + + Response response; + if (useQueryString) { + path.append("?e=b64"); + response = client.delete(path.toString()); + } else { + response = client.delete(path.toString(), new BasicHeader("Encoding", "b64")); + } + Thread.yield(); + return response; + } + protected static Response getValueXML(String table, String row, String column) throws IOException { StringBuilder path = new StringBuilder(); @@ -506,6 +540,26 @@ protected static Response deleteRow(String table, String row) throws IOException return response; } + protected static Response deleteRowB64(String table, String row, boolean useQueryString) + throws IOException { + StringBuilder path = new StringBuilder(); + Base64.Encoder encoder = Base64.getUrlEncoder(); + path.append('/'); + path.append(table); + path.append('/'); + path.append(encoder.encodeToString(row.getBytes("UTF-8"))); + + Response response; + if (useQueryString) { + path.append("?e=b64"); + response = client.delete(path.toString()); + } else { + response = client.delete(path.toString(), new BasicHeader("Encoding", "b64")); + } + Thread.yield(); + return response; + } + protected static Response getValueJson(String table, String row, String column) throws IOException { StringBuilder path = new StringBuilder(); diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestDeleteRow.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestDeleteRow.java index 9d9d2f337699..9893a9ef67d2 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestDeleteRow.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestDeleteRow.java @@ -100,4 +100,38 @@ public void testDeleteXML() throws IOException, JAXBException { assertEquals(404, response.getCode()); } + private void testDeleteB64XML(boolean useQueryString) throws IOException, JAXBException { + Response response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1); + assertEquals(200, response.getCode()); + response = putValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2); + assertEquals(200, response.getCode()); + checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1); + checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2); + + response = deleteValueB64(TABLE, ROW_1, COLUMN_1, useQueryString); + assertEquals(200, response.getCode()); + response = getValueXML(TABLE, ROW_1, COLUMN_1); + assertEquals(404, response.getCode()); + checkValueXML(TABLE, ROW_1, COLUMN_2, VALUE_2); + + response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1); + assertEquals(200, response.getCode()); + response = checkAndDeletePB(TABLE, ROW_1, COLUMN_1, VALUE_1); + assertEquals(200, response.getCode()); + response = getValueXML(TABLE, ROW_1, COLUMN_1); + assertEquals(404, response.getCode()); + + response = deleteRowB64(TABLE, ROW_1, useQueryString); + assertEquals(200, response.getCode()); + response = getValueXML(TABLE, ROW_1, COLUMN_1); + assertEquals(404, response.getCode()); + response = getValueXML(TABLE, ROW_1, COLUMN_2); + assertEquals(404, response.getCode()); + } + + @Test + public void testDeleteB64XML() throws IOException, JAXBException { + testDeleteB64XML(/* useQueryString: */false); + testDeleteB64XML(/* useQueryString: */true); + } } diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGetAndPutResource.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGetAndPutResource.java index b2c45e8cbd78..d14c45e0532b 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGetAndPutResource.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGetAndPutResource.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.StringWriter; import java.net.URLEncoder; +import java.util.Base64; import java.util.HashMap; import java.util.List; import javax.xml.bind.JAXBException; @@ -40,6 +41,7 @@ import org.apache.hadoop.hbase.testclassification.RestTests; import org.apache.hadoop.hbase.util.Bytes; import org.apache.http.Header; +import org.apache.http.message.BasicHeader; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -333,6 +335,72 @@ public void testURLEncodedKey() throws IOException, JAXBException { checkValueXML(path.toString(), TABLE, urlKey, COLUMN_1, VALUE_1); } + private void setupValue1() throws IOException, JAXBException { + StringBuilder path = new StringBuilder(); + path.append('/'); + path.append(TABLE); + path.append('/'); + path.append(ROW_1); + path.append('/'); + path.append(COLUMN_1); + Response response = putValueXML(path.toString(), TABLE, ROW_1, COLUMN_1, VALUE_1); + assertEquals(200, response.getCode()); + } + + private void checkValue1(Response getResponse) throws JAXBException { + assertEquals(Constants.MIMETYPE_XML, getResponse.getHeader("content-type")); + + CellSetModel cellSet = + (CellSetModel) xmlUnmarshaller.unmarshal(new ByteArrayInputStream(getResponse.getBody())); + assertEquals(1, cellSet.getRows().size()); + RowModel rowModel = cellSet.getRows().get(0); + assertEquals(ROW_1, new String(rowModel.getKey())); + assertEquals(1, rowModel.getCells().size()); + CellModel cell = rowModel.getCells().get(0); + assertEquals(COLUMN_1, new String(cell.getColumn())); + assertEquals(VALUE_1, new String(cell.getValue())); + } + + // See https://issues.apache.org/jira/browse/HBASE-28174 + @Test + public void testUrlB64EncodedKeyQueryParam() throws IOException, JAXBException { + setupValue1(); + + StringBuilder path = new StringBuilder(); + Base64.Encoder encoder = Base64.getUrlEncoder(); + path.append('/'); + path.append(TABLE); + path.append('/'); + path.append(encoder.encodeToString(ROW_1.getBytes("UTF-8"))); + path.append('/'); + path.append(encoder.encodeToString(COLUMN_1.getBytes("UTF-8"))); + path.append("?e=b64"); + Response response = getValueXML(path.toString()); + assertEquals(200, response.getCode()); + + checkValue1(response); + } + + // See https://issues.apache.org/jira/browse/HBASE-28174 + @Test + public void testUrlB64EncodedKeyHeader() throws IOException, JAXBException { + setupValue1(); + + StringBuilder path = new StringBuilder(); + Base64.Encoder encoder = Base64.getUrlEncoder(); + path.append('/'); + path.append(TABLE); + path.append('/'); + path.append(encoder.encodeToString(ROW_1.getBytes("UTF-8"))); + path.append('/'); + path.append(encoder.encodeToString(COLUMN_1.getBytes("UTF-8"))); + Response response = + getValueXML(path.toString(), new Header[] { new BasicHeader("Encoding", "b64") }); + assertEquals(200, response.getCode()); + + checkValue1(response); + } + @Test public void testNoSuchCF() throws IOException { final String goodPath = "/" + TABLE + "/" + ROW_1 + "/" + CFA + ":"; diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestMultiRowResource.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestMultiRowResource.java index 215d4f9c346c..3e7b4e5cf20c 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestMultiRowResource.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestMultiRowResource.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; +import java.util.Base64; import java.util.Collection; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; @@ -156,6 +157,76 @@ public void testMultiCellGetJSON() throws IOException { client.delete(row_6_url, extraHdr); } + private void checkMultiCellGetJSON(Response response) throws IOException { + assertEquals(200, response.getCode()); + assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); + + ObjectMapper mapper = new JacksonJaxbJsonProvider().locateMapper(CellSetModel.class, + MediaType.APPLICATION_JSON_TYPE); + CellSetModel cellSet = mapper.readValue(response.getBody(), CellSetModel.class); + + RowModel rowModel = cellSet.getRows().get(0); + assertEquals(ROW_1, new String(rowModel.getKey())); + assertEquals(1, rowModel.getCells().size()); + CellModel cell = rowModel.getCells().get(0); + assertEquals(COLUMN_1, new String(cell.getColumn())); + assertEquals(VALUE_1, new String(cell.getValue())); + + rowModel = cellSet.getRows().get(1); + assertEquals(ROW_2, new String(rowModel.getKey())); + assertEquals(1, rowModel.getCells().size()); + cell = rowModel.getCells().get(0); + assertEquals(COLUMN_2, new String(cell.getColumn())); + assertEquals(VALUE_2, new String(cell.getValue())); + } + + // See https://issues.apache.org/jira/browse/HBASE-28174 + @Test + public void testMultiCellGetJSONB64() throws IOException { + String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; + String row_6_url = "/" + TABLE + "/" + ROW_2 + "/" + COLUMN_2; + + if (csrfEnabled) { + Response response = client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1)); + assertEquals(400, response.getCode()); + } + + client.post(row_5_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_1), extraHdr); + client.post(row_6_url, Constants.MIMETYPE_BINARY, Bytes.toBytes(VALUE_2), extraHdr); + + StringBuilder path = new StringBuilder(); + Base64.Encoder encoder = Base64.getUrlEncoder(); + path.append("/"); + path.append(TABLE); + path.append("/multiget/?row="); + path.append(encoder.encodeToString(ROW_1.getBytes("UTF-8"))); + path.append("&row="); + path.append(encoder.encodeToString(ROW_2.getBytes("UTF-8"))); + path.append("&e=b64"); // Specify encoding via query string + + Response response = client.get(path.toString(), Constants.MIMETYPE_JSON); + + checkMultiCellGetJSON(response); + + path = new StringBuilder(); + path.append("/"); + path.append(TABLE); + path.append("/multiget/?row="); + path.append(encoder.encodeToString(ROW_1.getBytes("UTF-8"))); + path.append("&row="); + path.append(encoder.encodeToString(ROW_2.getBytes("UTF-8"))); + + Header[] headers = new Header[] { new BasicHeader("Accept", Constants.MIMETYPE_JSON), + new BasicHeader("Encoding", "b64") // Specify encoding via header + }; + response = client.get(path.toString(), headers); + + checkMultiCellGetJSON(response); + + client.delete(row_5_url, extraHdr); + client.delete(row_6_url, extraHdr); + } + @Test public void testMultiCellGetXML() throws IOException { String row_5_url = "/" + TABLE + "/" + ROW_1 + "/" + COLUMN_1; diff --git a/src/main/asciidoc/_chapters/external_apis.adoc b/src/main/asciidoc/_chapters/external_apis.adoc index 129094e1d1cc..2ea4113981ba 100644 --- a/src/main/asciidoc/_chapters/external_apis.adoc +++ b/src/main/asciidoc/_chapters/external_apis.adoc @@ -250,6 +250,32 @@ curl -vi -X GET \ -H "Accept: text/xml" \ "http://example.com:8000/users/row1/cf:a/" +|/_table_/_row_/_column:qualifier_?e=b64 +|GET +|Get the value of a single column using a binary rowkey and column name, encoded in https://datatracker.ietf.org/doc/html/rfc4648#section-5[URL-safe base64]. Returned values are Base-64 encoded. +|curl -vi -X GET \ + -H "Accept: text/xml" \ + "http://example.com:8000/users/cm93MQ/Y2Y6YQ?e=b64" + +curl -vi -X GET \ + -H "Accept: text/xml" \ + -H "Encoding: base64" \ + "http://example.com:8000/users/cm93MQ/Y2Y6YQ/" + +|/_table_/multiget?row=_row_&row=_row_/_column:qualifier_&row=... +|GET +|Multi-Get a combination of rows/columns. Values are Base-64 encoded. +|curl -vi -X GET \ + -H "Accept: text/xml" \ + "http://example.com:8000/users/multiget?row=row1&row=row2/cf:a" + +|/_table_/multiget?e=b64&row=_row_&row=_row_/_column:qualifier_&row=... +|GET +|Multi-Get a combination of rows/columns using binary rowkeys and column names, encoded in https://datatracker.ietf.org/doc/html/rfc4648#section-5[URL-safe base64]. Returned values are Base-64 encoded. +|curl -vi -X GET \ + -H "Accept: text/xml" \ + "http://example.com:8000/users/multiget?e=b64&row=cm93MQ&row=cm93Mg%2FY2Y6YQ" + |/_table_/_row_/_column:qualifier_/?v=_number_of_versions_ |GET |Multi-Get a specified number of versions of a given cell. Values are Base-64 encoded. @@ -259,6 +285,55 @@ curl -vi -X GET \ |=== + +.Endpoints for `Delete` Operations +[options="header", cols="2m,m,3d,6l"] +|=== +|Endpoint +|HTTP Verb +|Description +|Example + +|/_table_/_row_ +|DELETE +|Delete all columns of a single row. +|curl -vi -X DELETE \ + "http://example.com:8000/users/row1" + +|/_table_/_row_/_column_family_: +|DELETE +|Delete all columns of a single row and column family. +|curl -vi -X DELETE \ + "http://example.com:8000/users/row1/cf" + +|/_table_/_row_/_column:qualifier_/_timestamp_ +|DELETE +|Delete a single column. +|curl -vi -X DELETE \ + "http://example.com:8000/users/row1/cf:a/1458586888395" + +|/_table_/_row_/_column:qualifier_ +|DELETE +|Delete a single column. +|curl -vi -X DELETE \ + "http://example.com:8000/users/row1/cf:a" + +curl -vi -X DELETE \ + -H "Accept: text/xml" \ + "http://example.com:8000/users/row1/cf:a/" + +|/_table_/_row_/_column:qualifier_?e=b64 +|DELETE +|Delete a single column using a binary rowkey and column name, encoded in https://datatracker.ietf.org/doc/html/rfc4648#section-5[URL-safe base64]. +|curl -vi -X DELETE \ + "http://example.com:8000/users/cm93MQ/Y2Y6YQ?e=b64" + +curl -vi -X DELETE \ + -H "Encoding: base64" \ + "http://example.com:8000/users/cm93MQ/Y2Y6YQ/" + +|=== + .Endpoints for `Scan` Operations [options="header", cols="2m,m,3d,6l"] |===