LindormTable lets you run SQL queries directly on HBase tables created with HBase Shell or ApsaraDB for HBase API for Java — no data migration required. Column mapping bridges the schema-free HBase storage model and Lindorm SQL, so you can filter, index, and query your existing data using standard SQL syntax.
Prerequisites
Before you begin, make sure that you have:
-
LindormTable version 2.6.4 or later. See Release notes of LindormTable and Upgrade the minor engine version of a Lindorm instance
How it works
HBase tables are schema-free: columns are stored as raw byte arrays (VARBINARY). To query them with SQL, you define column mappings that tell LindormTable how to decode each column's bytes back to a typed value.
Mapping a column means declaring its Lindorm SQL type — not writing data. When you run a SELECT, the system applies the type's corresponding Bytes.toBytes() method in reverse. Choosing the wrong type returns incorrect data, because the bytes are decoded using the wrong deserialization function. For example, if a column stores an INT written via Bytes.toBytes(int) but you map it as HSTRING, the decoded value will be garbage.
The supported mapping types are:
| Mapping type | Java type | Write function |
|---|---|---|
| HLONG | java.lang.Long | Bytes.toBytes(long) |
| HINTEGER | java.lang.Integer | Bytes.toBytes(int) |
| HSHORT | java.lang.Short | Bytes.toBytes(short) |
| HFLOAT | java.lang.Float | Bytes.toBytes(float) |
| HDOUBLE | java.lang.Double | Bytes.toBytes(double) |
| HSTRING | java.lang.String | Bytes.toBytes(String) |
| HBOOLEAN | java.lang.Boolean | Bytes.toBytes(boolean) |
Bytes.toBytes(String)uses UTF-8 encoding. If you write string data from a language other than Java, use thetoBytesmethod fromorg.apache.hadoop.hbase.util.Bytes, which also applies UTF-8 encoding.
Column mapping syntax
Use ALTER TABLE to add or remove column mappings:
-- Add a mapping
ALTER TABLE table_name MAP DYNAMIC COLUMN [family_name:]qualifier_name hbase_type;
-- Remove one or more mappings
ALTER TABLE table_name UNMAP DYNAMIC COLUMN [family_name:]qualifier_name [, ...];
Where hbase_type is one of: HLONG | HINTEGER | HSHORT | HFLOAT | HDOUBLE | HSTRING | HBOOLEAN
LindormTable 2.5.1 and later support rowkey mappings. Use backticks around the rowkey identifier: `ROW`.
For more information about dynamic columns, see Dynamic columns.
Access an HBase table with SQL
This procedure uses a sample HBase table named dt with column family f1. The table contains columns of multiple data types written via ApsaraDB for HBase API for Java.
Step 1: Prepare sample data
The following Java code creates the dt table and writes one row with columns of each supported type. Skip this step if your HBase table already exists.
For more information about connecting with ApsaraDB for HBase API for Java, see Use ApsaraDB for HBase API for Java to develop applications. To create the table with HBase Shell instead, see Use Lindorm Shell to connect to LindormTable.
// Create the table with column family f1
try (Admin admin = connection.getAdmin()) {
HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("dt"));
htd.addFamily(new HColumnDescriptor(Bytes.toBytes("f1")));
admin.createTable(htd);
}
// Write one row with columns of each supported type
try (Table table = connection.getTable(TableName.valueOf("dt"))) {
byte[] rowkey = Bytes.toBytes("row1");
byte[] family = Bytes.toBytes("f1");
Put put = new Put(rowkey);
String name = "Some one";
put.addColumn(family, Bytes.toBytes("name"), Bytes.toBytes(name)); // HSTRING
int age = 25;
put.addColumn(family, Bytes.toBytes("age"), Bytes.toBytes(age)); // HINTEGER
long timestamp = 1656675491000L;
put.addColumn(family, Bytes.toBytes("time"), Bytes.toBytes(timestamp)); // HLONG
short buycode = 123;
put.addColumn(family, Bytes.toBytes("buycode"), Bytes.toBytes(buycode)); // HSHORT
float price = 12.3f;
put.addColumn(family, Bytes.toBytes("price"), Bytes.toBytes(price)); // HFLOAT
double price2 = 12.33333;
put.addColumn(family, Bytes.toBytes("price2"), Bytes.toBytes(price2)); // HDOUBLE
boolean isMale = true;
put.addColumn(family, Bytes.toBytes("isMale"), Bytes.toBytes(isMale)); // HBOOLEAN
table.put(put);
}
Step 2: Connect to LindormTable
Connect using Lindorm-cli. For connection instructions, see Use Lindorm-cli to connect to and use LindormTable.
For ApsaraDB for HBase Performance-enhanced Edition, change the JDBC URL format tojdbc:lindorm:table:url=http://<API URL for Java>and change the port from 30020 to 30060. For example, changeld-bp1ietqp4fby3**-proxy-hbaseue.hbaseue.rds.aliyuncs.com:30020` to `jdbc:lindorm:table:url=http://ld-bp1ietqp4fby3**-proxy-hbaseue.hbaseue.rds.aliyuncs.com:30060.
Step 3: Add column mappings
Map each column to its corresponding Lindorm SQL type. The mapping type must match the Java type used when writing the data — an incorrect mapping causes the system to return wrong values when decoding the bytes.
ALTER TABLE dt MAP DYNAMIC COLUMN `ROW` HSTRING, f1:name HSTRING, f1:age HINTEGER, f1:time HLONG, f1:buycode HSHORT, f1:price HFLOAT, f1:price2 HDOUBLE, f1:isMale HBOOLEAN;
To verify the current schema, run:
DESCRIBE dt;
For DESCRIBE syntax details, see DESCRIBE/SHOW/USE.
Step 4: Query data
-- Return one row
SELECT * FROM dt LIMIT 1;
-- Filter by Boolean column
SELECT * FROM dt WHERE f1:isMale=true LIMIT 1;
-- Filter by string column
SELECT * FROM dt WHERE f1:name='Some one' LIMIT 1;
-- Filter by range on a LONG column
SELECT * FROM dt WHERE f1:time>1656675490000 AND f1:time<1656675492000 LIMIT 1;
Step 5: (Optional) Create a secondary index
Secondary indexes are used to reduce data query time at the cost of storage space. Secondary indexes allow you to query data based on non-primary key columns. This improves query efficiency but requires more storage space. For more information about syntax limits on secondary indexes, see CREATE INDEX and Secondary indexes.
-
Set the mutability attribute on the primary table:
ALTER TABLE dt SET 'MUTABILITY' = 'MUTABLE_LATEST';If you use a custom timestamp when writing data, set the attribute to
MUTABLE_ALLinstead. -
Create the secondary index:
CREATE INDEX idx ON dt(f1:age) WITH (INDEX_COVERED_TYPE ='COVERED_DYNAMIC_COLUMNS'); -
(Optional) If you specified the
asyncparameter when creating the index and your LindormTable version is earlier than 2.6.3, build the historical data into the index:BUILD INDEX idx ON dt; -
Verify the index status:
SHOW INDEX FROM dt;Expected output:
+---------------+-------------+-------------+--------------+------------------+---------------+-----------------+----------------+-------------+ | TABLE_SCHEMA | DATA_TABLE | INDEX_NAME | INDEX_STATE | INDEX_PROGRESS | INDEX_TYPE | INDEX_COVERED | INDEX_COLUMN | INDEX_TTL | +---------------+-------------+-------------+--------------+------------------+---------------+-----------------+----------------+-------------+ | default | dt | idx | ACTIVE | 100% | SECONDARY | TRUE | f1:age,ROW | | +---------------+-------------+-------------+--------------+------------------+---------------+-----------------+----------------+-------------+INDEX_STATE: ACTIVEmeans the index is ready.INDEX_PROGRESSshows the build progress. -
(Optional) Check the execution plan to confirm the index is being used:
EXPLAIN SELECT * FROM dt WHERE f1:age=23 LIMIT 1;
Step 6: (Optional) Create a search index
Search indexes support more flexible queries across multiple columns.
-
Create the search index:
CREATE INDEX search_idx USING SEARCH ON dt(f1:age,f1:name);Before creating a search index on an HBase table, note these constraints:
-
All columns in the search index must already be defined in the column mappings.
-
Column data types must match those specified in the mappings.
-
Column mappings for indexed columns cannot be removed after the search index is created. Removing them causes incorrect query results.
-
If you write data with a custom timestamp and need a search index, set the
MUTABILITYattribute toMUTABLE_ALL.
-
-
Verify that both indexes are created:
SHOW INDEX FROM dt;Expected output:
+--------------+------------+------------+-------------+----------------+------------+---------------+----------------+-----------+-------------------+ | TABLE_SCHEMA | DATA_TABLE | INDEX_NAME | INDEX_STATE | INDEX_PROGRESS | INDEX_TYPE | INDEX_COVERED | INDEX_COLUMN | INDEX_TTL | INDEX_DESCRIPTION | +--------------+------------+------------+-------------+----------------+------------+---------------+----------------+-----------+-------------------+ | default | dt | idx | ACTIVE | DONE | SECONDARY | DYNAMIC | f1:age,ROW | | | | default | dt | search_idx | BUILDING | N/A | SEARCH | NA | f1:age,f1:name | 0 | | +--------------+------------+------------+-------------+----------------+------------+---------------+----------------+-----------+-------------------+
Step 7: (Optional) Remove column mappings
Remove a single column mapping:
ALTER TABLE dt UNMAP DYNAMIC COLUMN f1:isMale;
Remove multiple column mappings:
ALTER TABLE dt UNMAP DYNAMIC COLUMN f1:price2, f1:price2;
Do not remove mappings for columns included in a search index. Doing so causes incorrect query results.