All Products
Search
Document Center

Lindorm:Use SQL to access an HBase table

Last Updated:Mar 28, 2026

LindormTable lets you query existing HBase tables with SQL — without changing how you write data. Map column qualifiers to typed SQL columns, then run SELECT queries and create secondary or search indexes.

Prerequisites

Before you begin, ensure that you have:

How column mapping works

HBase tables are schema-free. Columns store raw bytes (VARBINARY), and column names are dynamic. LindormTable's column mapping feature bridges this gap: declare the Java serialization type used when writing each qualifier, and Lindorm SQL can decode the bytes and expose the columns as typed SQL columns — enabling WHERE filters, secondary indexes, and search indexes on HBase data.

For more information about dynamic columns, see Dynamic columns.

Column mapping syntax

Use ALTER TABLE to add or remove column mappings:

-- Add a mapping
ALTER TABLE <table_name> MAP DYNAMIC COLUMN [<family>:]<qualifier> <hbase_type>;

-- Remove one or more mappings
ALTER TABLE <table_name> UNMAP DYNAMIC COLUMN [<family>:]<qualifier> [, ...];

Supported hbase_type values

TypeJava typeWrite functionWhen to use
HLONGjava.lang.LongBytes.toBytes(long)Data written as a Java long
HINTEGERjava.lang.IntegerBytes.toBytes(int)Data written as a Java int
HSHORTjava.lang.ShortBytes.toBytes(short)Data written as a Java short
HFLOATjava.lang.FloatBytes.toBytes(float)Data written as a Java float
HDOUBLEjava.lang.DoubleBytes.toBytes(double)Data written as a Java double
HSTRINGjava.lang.StringBytes.toBytes(String) — UTF-8Data written as a Java String
HBOOLEANjava.lang.BooleanBytes.toBytes(boolean)Data written as a Java boolean
Important

The mapping type must exactly match the Java serialization method used at write time. A mismatch causes Lindorm SQL to read the wrong bytes and return incorrect results. See Common mistakes below.

Usage notes

  • Adding a mapping declares the type for a column — it does not require data to already exist in that column.

  • Rowkey mappings are supported on LindormTable 2.5.1 and later. Enclose the rowkey identifier in backticks: ` ROW `.

  • In languages other than Java, use the toBytes method from org.apache.hadoop.hbase.util.Bytes. Strings are always encoded as UTF-8.

Common mistakes

Mapping type does not match the write path

This is the most critical correctness risk. If column f:age was written with Bytes.toBytes(int), map it as HINTEGER. If you map it as HSTRING instead, Lindorm SQL calls Bytes.toString() on 4 raw integer bytes and returns garbage.

Example of a correct mapping:

int age = 25;
put.addColumn(Bytes.toBytes("f"), Bytes.toBytes("age"), Bytes.toBytes(age));
// Map as HINTEGER

String age2 = "25";
put.addColumn(Bytes.toBytes("f"), Bytes.toBytes("age2"), Bytes.toBytes(age2));
// Map as HSTRING

Mixing these up is the most common source of incorrect query results.

Prepare sample data

The steps below use the following HBase table and data. If you already have an HBase table, skip to Map column qualifiers.

The sample creates a table named dt with column family f1 and writes one row using ApsaraDB for HBase API for Java. To use HBase Shell instead, see Use Lindorm Shell to connect to LindormTable.

// Create table dt 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
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);
}

Map column qualifiers

  1. Connect to LindormTable using Lindorm-cli. For instructions, see Use Lindorm-cli to connect to and use LindormTable.

    If you're accessing an HBase table in ApsaraDB for HBase Performance-enhanced Edition, change the Java API URL format to jdbc:lindorm:table:url=http://<API URL for Java> and change the port from 30020 to 30060. For example, ld-bp1ietqp4fby3****-proxy-hbaseue.hbaseue.rds.aliyuncs.com:30020 becomes jdbc:lindorm:table:url=http://ld-bp1ietqp4fby3****-proxy-hbaseue.hbaseue.rds.aliyuncs.com:30060.
  2. Map the rowkey and all qualifiers to their corresponding Lindorm SQL types:

    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;
  3. Verify the mapping by running DESCRIBE:

    DESCRIBE dt;

    For more information about the DESCRIBE syntax, see DESCRIBE/SHOW/USE.

  4. Query the table:

    SELECT * FROM dt LIMIT 1;
    SELECT * FROM dt WHERE f1:isMale=true LIMIT 1;
    SELECT * FROM dt WHERE f1:name='Some one' LIMIT 1;
    SELECT * FROM dt WHERE f1:time>1656675490000 AND f1:time<1656675492000 LIMIT 1;

Create indexes (optional)

Adding indexes improves query performance but requires additional storage. Choose the index type based on your query pattern:

Index typeBest forTrade-off
Secondary indexQueries on non-primary-key columns with standard write volumeFaster reads, more storage, slight write overhead
Search indexFull-text search or complex multi-condition queriesFlexible queries, higher storage cost

Create a secondary index

  1. Set the table's MUTABILITY attribute:

    If you write data with custom timestamps, use MUTABLE_ALL instead of MUTABLE_LATEST.
    ALTER TABLE dt SET 'MUTABILITY' = 'MUTABLE_LATEST';
  2. Create the index:

    CREATE INDEX idx ON dt(f1:age) WITH (INDEX_COVERED_TYPE ='COVERED_DYNAMIC_COLUMNS');
  3. If you specified the `async` parameter and your LindormTable version is earlier than 2.6.3, build historical data into the index table:

    BUILD INDEX idx ON dt;

    Skip this step if you did not use async.

  4. Verify the index:

    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    |             |
    +---------------+-------------+-------------+--------------+------------------+---------------+-----------------+----------------+-------------+

    The index is ready when INDEX_STATE is ACTIVE. INDEX_PROGRESS shows the build progress.

  5. (Optional) Check whether the secondary index is used in a query:

    EXPLAIN SELECT * FROM dt WHERE f1:age=23 LIMIT 1;

    For more information, see CREATE INDEX and Secondary indexes.

Create a search index

  1. 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 the following:

    • All columns in the search index must already have column mappings defined.

    • Column data types must match the supported mapping types listed in Column mapping syntax.

    • Do not remove the column mappings after creating the search index — this causes incorrect query results.

    • If you write data with custom timestamps, set MUTABILITY to MUTABLE_ALL before creating the index.

  2. Check 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 | 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         |                   |
    +--------------+------------+------------+-------------+----------------+------------+---------------+----------------+-----------+-------------------+

Remove column mappings (optional)

Remove a single mapping:

ALTER TABLE dt UNMAP DYNAMIC COLUMN f1:isMale;

Remove multiple mappings in one statement:

ALTER TABLE dt UNMAP DYNAMIC COLUMN f1:price, f1:price2;

What's next