You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lens.apache.org by pr...@apache.org on 2015/04/13 15:24:09 UTC
incubator-lens git commit: LENS-455: Allow partition columns to be
queried as field in where/select clause.
Repository: incubator-lens
Updated Branches:
refs/heads/master 2391a8080 -> 7586a83c8
LENS-455: Allow partition columns to be queried as field in where/select clause.
Project: http://git-wip-us.apache.org/repos/asf/incubator-lens/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-lens/commit/7586a83c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-lens/tree/7586a83c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-lens/diff/7586a83c
Branch: refs/heads/master
Commit: 7586a83c8aead80c0b8e21300623ea50eb61ac34
Parents: 2391a80
Author: Rajat Khandelwal <ra...@gmail.com>
Authored: Mon Apr 13 18:53:57 2015 +0530
Committer: Rajat Khandelwal <ra...@gmail.com>
Committed: Mon Apr 13 18:53:57 2015 +0530
----------------------------------------------------------------------
lens-api/src/main/resources/cube-0.1.xsd | 6 ++-
.../lens/cube/metadata/CubeDimensionTable.java | 34 ++++++++++----
.../lens/cube/metadata/MetastoreConstants.java | 1 +
.../lens/cube/metadata/MetastoreUtil.java | 4 ++
.../apache/lens/cube/parse/CandidateDim.java | 3 +-
.../cube/parse/CandidateTablePruneCause.java | 2 +-
.../cube/metadata/TestCubeMetastoreClient.java | 8 ----
.../apache/lens/cube/parse/CubeTestSetup.java | 30 +++++++++++++
.../lens/cube/parse/TestCubeRewriter.java | 47 ++++++++++++++++++--
.../src/main/resources/product_table.xml | 3 +-
10 files changed, 113 insertions(+), 25 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-api/src/main/resources/cube-0.1.xsd
----------------------------------------------------------------------
diff --git a/lens-api/src/main/resources/cube-0.1.xsd b/lens-api/src/main/resources/cube-0.1.xsd
index 1de4258..24d0d64 100644
--- a/lens-api/src/main/resources/cube-0.1.xsd
+++ b/lens-api/src/main/resources/cube-0.1.xsd
@@ -528,7 +528,11 @@
<xs:element type="x_properties" name="properties" maxOccurs="1" minOccurs="0">
<xs:annotation>
<xs:documentation>
- Dimension table properties
+ Dimension table properties. The properties that should be set are:
+ 1. dimtable.{dim_table_name}.part.cols = comma separated list of partition columns of this dimtable.
+ This would basically be union of all partition columns of all storage tables of the dimtable.
+ Setting this makes that partition column queryable.
+ Time part columns can be skipped as they will generally not be queried.
</xs:documentation>
</xs:annotation>
</xs:element>
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-cube/src/main/java/org/apache/lens/cube/metadata/CubeDimensionTable.java
----------------------------------------------------------------------
diff --git a/lens-cube/src/main/java/org/apache/lens/cube/metadata/CubeDimensionTable.java b/lens-cube/src/main/java/org/apache/lens/cube/metadata/CubeDimensionTable.java
index 33e0482..47a30dd 100644
--- a/lens-cube/src/main/java/org/apache/lens/cube/metadata/CubeDimensionTable.java
+++ b/lens-cube/src/main/java/org/apache/lens/cube/metadata/CubeDimensionTable.java
@@ -25,6 +25,8 @@ import org.apache.hadoop.hive.metastore.api.FieldSchema;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.metadata.Table;
+import com.google.common.collect.Sets;
+
public final class CubeDimensionTable extends AbstractCubeTable {
private String dimName; // dimension name the dimtabe belongs to
private final Map<String, UpdatePeriod> snapshotDumpPeriods = new HashMap<String, UpdatePeriod>();
@@ -54,6 +56,7 @@ public final class CubeDimensionTable extends AbstractCubeTable {
addProperties();
}
+
private static Map<String, UpdatePeriod> getSnapshotDumpPeriods(Set<String> storages) {
Map<String, UpdatePeriod> snapshotDumpPeriods = new HashMap<String, UpdatePeriod>();
for (String storage : storages) {
@@ -71,6 +74,19 @@ public final class CubeDimensionTable extends AbstractCubeTable {
}
}
+ public Set<String> getPartCols() {
+ Set<String> partCols = Sets.newHashSet();
+ String partColsStr = getProperties().get(MetastoreUtil.getDimTablePartsKey(getName()));
+ if (partColsStr != null) {
+ for (String s : StringUtils.split(partColsStr, ",")) {
+ if (!StringUtils.isBlank(s)) {
+ partCols.add(s);
+ }
+ }
+ }
+ return partCols;
+ }
+
@Override
public CubeTableType getTableType() {
return CubeTableType.DIM_TABLE;
@@ -79,10 +95,11 @@ public final class CubeDimensionTable extends AbstractCubeTable {
@Override
protected void addProperties() {
super.addProperties();
- setDimName(getProperties(), getName(), dimName);
- setSnapshotPeriods(getName(), getProperties(), snapshotDumpPeriods);
+ setDimName(getName(), getProperties(), dimName);
+ setSnapshotDumpPeriods(getName(), getProperties(), snapshotDumpPeriods);
}
+
public Map<String, UpdatePeriod> getSnapshotDumpPeriods() {
return snapshotDumpPeriods;
}
@@ -91,7 +108,7 @@ public final class CubeDimensionTable extends AbstractCubeTable {
return dimName;
}
- private static void setSnapshotPeriods(String name, Map<String, String> props,
+ private static void setSnapshotDumpPeriods(String name, Map<String, String> props,
Map<String, UpdatePeriod> snapshotDumpPeriods) {
if (snapshotDumpPeriods != null) {
props.put(MetastoreUtil.getDimensionStorageListKey(name), MetastoreUtil.getStr(snapshotDumpPeriods.keySet()));
@@ -103,7 +120,7 @@ public final class CubeDimensionTable extends AbstractCubeTable {
}
}
- private static void setDimName(Map<String, String> props, String dimTblName, String dimName) {
+ private static void setDimName(String dimTblName, Map<String, String> props, String dimName) {
props.put(MetastoreUtil.getDimNameKey(dimTblName), dimName);
}
@@ -115,8 +132,7 @@ public final class CubeDimensionTable extends AbstractCubeTable {
String storagesStr = params.get(MetastoreUtil.getDimensionStorageListKey(name));
if (!StringUtils.isBlank(storagesStr)) {
Map<String, UpdatePeriod> dumpPeriods = new HashMap<String, UpdatePeriod>();
- String[] storages = storagesStr.split(",");
- for (String storage : storages) {
+ for (String storage : StringUtils.split(storagesStr, ",")) {
String dumpPeriod = params.get(MetastoreUtil.getDimensionDumpPeriodKey(name, storage));
if (dumpPeriod != null) {
dumpPeriods.put(storage, UpdatePeriod.valueOf(dumpPeriod));
@@ -178,7 +194,7 @@ public final class CubeDimensionTable extends AbstractCubeTable {
*/
public void alterUberDim(String newDimName) {
this.dimName = newDimName;
- setDimName(getProperties(), getName(), this.dimName);
+ setDimName(getName(), getProperties(), this.dimName);
}
/**
@@ -198,7 +214,7 @@ public final class CubeDimensionTable extends AbstractCubeTable {
}
snapshotDumpPeriods.put(storage, period);
- setSnapshotPeriods(getName(), getProperties(), snapshotDumpPeriods);
+ setSnapshotDumpPeriods(getName(), getProperties(), snapshotDumpPeriods);
}
@Override
@@ -213,6 +229,6 @@ public final class CubeDimensionTable extends AbstractCubeTable {
void dropStorage(String storage) {
snapshotDumpPeriods.remove(storage);
- setSnapshotPeriods(getName(), getProperties(), snapshotDumpPeriods);
+ setSnapshotDumpPeriods(getName(), getProperties(), snapshotDumpPeriods);
}
}
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreConstants.java
----------------------------------------------------------------------
diff --git a/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreConstants.java b/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreConstants.java
index e25fc81..e7f10ac 100644
--- a/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreConstants.java
+++ b/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreConstants.java
@@ -42,6 +42,7 @@ public final class MetastoreConstants {
// Uber dimension constants
public static final String DIMENSION_PFX = "dimension.";
public static final String ATTRIBUTES_LIST_SFX = ".attributes.list";
+ public static final String PARTCOLS_SFX = ".part.cols";
public static final String TIMED_DIMENSION_SFX = ".timed.dimension";
// fact constants
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreUtil.java
----------------------------------------------------------------------
diff --git a/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreUtil.java b/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreUtil.java
index 30253d3..bdec4e3 100644
--- a/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreUtil.java
+++ b/lens-cube/src/main/java/org/apache/lens/cube/metadata/MetastoreUtil.java
@@ -62,6 +62,10 @@ public class MetastoreUtil {
return getDimPrefix(dimName) + ATTRIBUTES_LIST_SFX;
}
+ public static final String getDimTablePartsKey(String dimtableName) {
+ return getDimensionTablePrefix(dimtableName) + PARTCOLS_SFX;
+ }
+
public static final String getDimTimedDimensionKey(String dimName) {
return getDimPrefix(dimName) + TIMED_DIMENSION_SFX;
}
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateDim.java
----------------------------------------------------------------------
diff --git a/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateDim.java b/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateDim.java
index 90d0b6d..64dff16 100644
--- a/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateDim.java
+++ b/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateDim.java
@@ -29,6 +29,7 @@ import org.apache.lens.cube.metadata.StorageConstants;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hive.ql.session.SessionState;
+import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.Setter;
@@ -117,7 +118,7 @@ public class CandidateDim implements CandidateTable {
@Override
public Collection<String> getColumns() {
- return dimtable.getAllFieldNames();
+ return Sets.union(dimtable.getAllFieldNames(), dimtable.getPartCols());
}
@Override
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTablePruneCause.java
----------------------------------------------------------------------
diff --git a/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTablePruneCause.java b/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTablePruneCause.java
index bc9ef93..2c191fc 100644
--- a/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTablePruneCause.java
+++ b/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTablePruneCause.java
@@ -240,7 +240,7 @@ public class CandidateTablePruneCause {
for (Map.Entry<String, SkipStorageCause> entry : storageCauses.entrySet()) {
String key = entry.getKey();
key = key.substring(0, (key.indexOf("_") + key.length() + 1) % (key.length() + 1)); // extract the storage part
- cause.getStorageCauses().put(key, entry.getValue());
+ cause.getStorageCauses().put(key.toLowerCase(), entry.getValue());
}
return cause;
}
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-cube/src/test/java/org/apache/lens/cube/metadata/TestCubeMetastoreClient.java
----------------------------------------------------------------------
diff --git a/lens-cube/src/test/java/org/apache/lens/cube/metadata/TestCubeMetastoreClient.java b/lens-cube/src/test/java/org/apache/lens/cube/metadata/TestCubeMetastoreClient.java
index fe29d25..9ceea48 100644
--- a/lens-cube/src/test/java/org/apache/lens/cube/metadata/TestCubeMetastoreClient.java
+++ b/lens-cube/src/test/java/org/apache/lens/cube/metadata/TestCubeMetastoreClient.java
@@ -1490,10 +1490,6 @@ public class TestCubeMetastoreClient {
String storageTableName = MetastoreUtil.getFactStorageTableName(cubeFact.getName(), c1);
List<Partition> parts = client.getPartitionsByFilter(storageTableName, "dt='latest'");
Assert.assertEquals(parts.size(), 0);
-// Assert
-// .assertEquals(TextInputFormat.class.getCanonicalName(), parts.get(0).getInputFormatClass().getCanonicalName());
-// Assert.assertEquals(parts.get(0).getParameters().get(MetastoreUtil.getLatestPartTimestampKey("dt")),
-// UpdatePeriod.HOURLY.format().format(now));
Assert.assertEquals(client.getAllParts(storageTableName).size(), 1);
client.dropPartition(cubeFact.getName(), c1, timeParts, null, UpdatePeriod.HOURLY);
@@ -1577,10 +1573,6 @@ public class TestCubeMetastoreClient {
Assert.assertEquals(client.getAllParts(storageTableName).size(), 1);
List<Partition> parts = client.getPartitionsByFilter(storageTableName, "dt='latest'");
Assert.assertEquals(parts.size(), 0);
-// Assert
-// .assertEquals(TextInputFormat.class.getCanonicalName(), parts.get(0).getInputFormatClass().getCanonicalName());
-// Assert.assertEquals(parts.get(0).getParameters().get(MetastoreUtil.getLatestPartTimestampKey("dt")),
-// UpdatePeriod.HOURLY.format().format(now));
client.dropPartition(cubeFactWithParts.getName(), c1, timeParts, partSpec, UpdatePeriod.HOURLY);
Assert.assertFalse(client.factPartitionExists(cubeFactWithParts.getName(), c1, UpdatePeriod.HOURLY, timeParts,
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-cube/src/test/java/org/apache/lens/cube/parse/CubeTestSetup.java
----------------------------------------------------------------------
diff --git a/lens-cube/src/test/java/org/apache/lens/cube/parse/CubeTestSetup.java b/lens-cube/src/test/java/org/apache/lens/cube/parse/CubeTestSetup.java
index 62b2c95..517fe7b 100644
--- a/lens-cube/src/test/java/org/apache/lens/cube/parse/CubeTestSetup.java
+++ b/lens-cube/src/test/java/org/apache/lens/cube/parse/CubeTestSetup.java
@@ -1776,6 +1776,20 @@ public class CubeTestSetup {
storageTables.put(c1, s1);
client.createCubeDimensionTable(dimName, dimTblName, dimColumns, 0L, dumpPeriods, dimProps, storageTables);
+ dimTblName = "countrytable_partitioned";
+
+ StorageTableDesc s2 = new StorageTableDesc();
+ s2.setInputFormat(TextInputFormat.class.getCanonicalName());
+ s2.setOutputFormat(HiveIgnoreKeyTextOutputFormat.class.getCanonicalName());
+ ArrayList<FieldSchema> partCols = Lists.newArrayList();
+ partCols.add(dimColumns.remove(dimColumns.size() - 2));
+ s2.setPartCols(partCols);
+ dumpPeriods.clear();
+ dumpPeriods.put(c3, UpdatePeriod.HOURLY);
+ storageTables.clear();
+ storageTables.put(c3, s2);
+ dimProps.put(MetastoreUtil.getDimTablePartsKey(dimTblName), partCols.get(0).getName());
+ client.createCubeDimensionTable(dimName, dimTblName, dimColumns, 0L, dumpPeriods, dimProps, storageTables);
}
private void createStateTable(CubeMetastoreClient client) throws Exception {
@@ -1814,6 +1828,22 @@ public class CubeTestSetup {
storageTables.put(c1, s1);
client.createCubeDimensionTable(dimName, dimTblName, dimColumns, 0L, dumpPeriods, dimProps, storageTables);
+
+ // In this, country id will be a partition
+ dimTblName = "statetable_partitioned";
+
+ StorageTableDesc s2 = new StorageTableDesc();
+ s2.setInputFormat(TextInputFormat.class.getCanonicalName());
+ s2.setOutputFormat(HiveIgnoreKeyTextOutputFormat.class.getCanonicalName());
+ partCols.add(dimColumns.remove(dimColumns.size() - 1));
+ s2.setPartCols(partCols);
+ s2.setTimePartCols(timePartCols);
+ dumpPeriods.clear();
+ dumpPeriods.put(c3, UpdatePeriod.HOURLY);
+ storageTables.clear();
+ storageTables.put(c3, s2);
+ dimProps.put(MetastoreUtil.getDimTablePartsKey(dimTblName), partCols.get(1).getName());
+ client.createCubeDimensionTable(dimName, dimTblName, dimColumns, 0L, dumpPeriods, dimProps, storageTables);
}
public void createSources(HiveConf conf, String dbName) throws Exception {
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-cube/src/test/java/org/apache/lens/cube/parse/TestCubeRewriter.java
----------------------------------------------------------------------
diff --git a/lens-cube/src/test/java/org/apache/lens/cube/parse/TestCubeRewriter.java b/lens-cube/src/test/java/org/apache/lens/cube/parse/TestCubeRewriter.java
index 6c65953..b85f60e 100644
--- a/lens-cube/src/test/java/org/apache/lens/cube/parse/TestCubeRewriter.java
+++ b/lens-cube/src/test/java/org/apache/lens/cube/parse/TestCubeRewriter.java
@@ -408,6 +408,38 @@ public class TestCubeRewriter extends TestQueryRewrite {
}
@Test
+ public void testPartColAsQueryColumn() throws Exception {
+ Configuration conf = getConf();
+ conf.setBoolean(CubeQueryConfUtil.FAIL_QUERY_ON_PARTIAL_DATA, false);
+ conf.set(CubeQueryConfUtil.DRIVER_SUPPORTED_STORAGES, "C3");
+ conf.setBoolean(CubeQueryConfUtil.DISABLE_AUTO_JOINS, false);
+ String hql, expected;
+ hql = rewrite(
+ "select countrydim.name, msr2 from" + " testCube" + " where countrydim.region = 'asia' and "
+ + TWO_DAYS_RANGE, conf);
+ expected =
+ getExpectedQuery(cubeName, "select countrydim.name, sum(testcube.msr2)" + " FROM ", " JOIN " + getDbName()
+ + "c3_statetable_partitioned statedim ON" + " testCube.stateid = statedim.id and statedim.dt = 'latest' JOIN "
+ + getDbName()
+ + "c3_countrytable_partitioned countrydim on statedim.countryid=countrydim.id and countrydim.dt='latest'",
+ "countrydim.region='asia'",
+ " group by countrydim.name ", null,
+ getWhereForDailyAndHourly2days(cubeName, "C3_testfact"));
+ compareQueries(hql, expected);
+ hql = rewrite(
+ "select statedim.name, statedim.countryid, msr2 from" + " testCube" + " where statedim.countryid = 5 and "
+ + TWO_DAYS_RANGE, conf);
+ expected =
+ getExpectedQuery(cubeName, "select statedim.name, statedim.countryid, sum(testcube.msr2)" + " FROM ",
+ " JOIN " + getDbName()
+ + "c3_statetable_partitioned statedim ON" + " testCube.stateid = statedim.id and statedim.dt = 'latest'",
+ "statedim.countryid=5",
+ " group by statedim.name, statedim.countryid", null,
+ getWhereForDailyAndHourly2days(cubeName, "C3_testfact"));
+ compareQueries(hql, expected);
+ }
+
+ @Test
public void testCubeJoinQuery() throws Exception {
// q1
String hqlQuery =
@@ -833,19 +865,19 @@ public class TestCubeRewriter extends TestQueryRewrite {
Assert.assertEquals(
pruneCauses.getBrief().substring(0, CandidateTablePruneCode.MISSING_PARTITIONS.errorFormat.length() - 3),
- CandidateTablePruneCode.MISSING_PARTITIONS.errorFormat.substring(0,
- CandidateTablePruneCode.MISSING_PARTITIONS.errorFormat.length() - 3));
+ CandidateTablePruneCode.MISSING_PARTITIONS.errorFormat.substring(0,
+ CandidateTablePruneCode.MISSING_PARTITIONS.errorFormat.length() - 3));
Assert.assertEquals(pruneCauses.getDetails().get("testfact").iterator().next().getCause(),
CandidateTablePruneCode.MISSING_PARTITIONS);
Assert.assertEquals(pruneCauses.getDetails().get("testfactmonthly").iterator().next().getCause(),
CandidateTablePruneCode.NO_FACT_UPDATE_PERIODS_FOR_GIVEN_RANGE);
Assert.assertEquals(pruneCauses.getDetails().get("testfact2").iterator().next().getCause(),
- CandidateTablePruneCode.MISSING_PARTITIONS);
+ CandidateTablePruneCode.MISSING_PARTITIONS);
Assert.assertEquals(pruneCauses.getDetails().get("testfact2_raw").iterator().next().getCause(),
CandidateTablePruneCode.MISSING_PARTITIONS);
Assert.assertEquals(pruneCauses.getDetails().get("cheapfact").iterator().next().getCause(),
- CandidateTablePruneCode.NO_CANDIDATE_STORAGES);
+ CandidateTablePruneCode.NO_CANDIDATE_STORAGES);
Assert.assertEquals(pruneCauses.getDetails().get("summary1,summary2,summary3").iterator().next().getCause(),
CandidateTablePruneCode.MISSING_PARTITIONS);
Assert.assertEquals(pruneCauses.getDetails().get("summary4").iterator().next()
@@ -896,6 +928,13 @@ public class TestCubeRewriter extends TestQueryRewrite {
}
}))
);
+ put("statetable_partitioned", Arrays.asList(CandidateTablePruneCause.noCandidateStorages(
+ new HashMap<String, SkipStorageCause>() {
+ {
+ put("C3_statetable_partitioned", new SkipStorageCause(SkipStorageCode.UNSUPPORTED));
+ }
+ }))
+ );
}
}
));
http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/7586a83c/lens-examples/src/main/resources/product_table.xml
----------------------------------------------------------------------
diff --git a/lens-examples/src/main/resources/product_table.xml b/lens-examples/src/main/resources/product_table.xml
index 4babca6..f7e804f 100644
--- a/lens-examples/src/main/resources/product_table.xml
+++ b/lens-examples/src/main/resources/product_table.xml
@@ -26,12 +26,12 @@
<column comment="SKU_number" name="SKU_number" type="INT"/>
<column comment="" name="description" type="STRING"/>
<column comment="" name="color" type="STRING"/>
- <column comment="" name="category" type="STRING"/>
<column comment="" name="weight" type="FLOAT"/>
<column comment="" name="manufacturer" type="STRING"/>
</columns>
<properties>
<property name="dim4.prop" value="d1"/>
+ <property name="dimtable.product_table.part.cols" value="category"/>
</properties>
<storage_tables>
<storage_table>
@@ -42,6 +42,7 @@
<table_desc external="true" field_delimiter="," table_location="/tmp/examples/product">
<part_cols>
<column comment="Time column" name="dt" type="STRING"/>
+ <column comment="Category" name="category" type="STRING"/>
</part_cols>
<time_part_cols>dt</time_part_cols>
</table_desc>