SQL
- SQL
The SQL service provided by Hazelcast .NET client allows you to query data stored in IHMap declaratively.
Example: How to Query an IHMap using SQL
This SQL query returns map entries whose key are more than 2:
await using var map = await client.GetMapAsync<int, string>("MyMap");
await client.Sql.ExecuteCommandAsync($"CREATE OR REPLACE MAPPING {map.Name} TYPE IMap OPTIONS ('keyFormat'='int', 'valueFormat'='varchar')");
await map.SetAllAsync(Enumerable.Range(1, 5).ToDictionary(v => v, v => $"val-{v}"));
await using var result = await client.Sql.ExecuteQueryAsync($"SELECT __key, this FROM {map.Name} WHERE __key > 2");
await foreach (var row in result)
Console.WriteLine($"{row.GetKey<int>()}: {row.GetValue<string>()}");
NOTE: The column value is deserialized but NOT cached. Each column is deserialized on demand. Avoid getting the value of a column multiple times. Cache it in a local variable.
Querying IHMap
The following subsections describe how you can access Hazelcast map objects and perform queries on them.
Before querying data in a map, you need to create a mapping to one. See Hazelcast docs
Map Names
The SQL service exposes IHMap objects as tables in the predefined partitioned schema using exact names.
This schema is in the SQL service search path so that you can access the IHMap objects with or without the schema name.s
Schema and table names are case-sensitive; you can access the employee map, for example, as employee or partitioned.employee,
but not as Employee:
SELECT * FROM employee
SELECT * FROM partitioned.employee
Field Names
The SQL service resolves fields accessible from the SQL automatically. The service reads the first local entry pair of the IHMap to construct the list of fields. If the IHMap does not have local entries on the member where the query is started, then the list of fields cannot be resolved, and an exception is thrown.
Field names are case-sensitive.
Key and Value Objects
An IHMap entry consists of a key and a value. These are accessible through the __key and this aliases. The following
query returns the keys and values of all entries in a map:
SELECT __key, this FROM employee
Key and Value Fields
You may also access the nested fields of a key or value. The list of exposed fields depends on the serialization format, as described below:
- For IdentifiedDataSerializable objects, you can use public field name or getter names. See Hazelcast docs for more information.
- For Portable objects, the fields written with
IPortableWritermethods are exposed using their exact names. - For
Jsonobjects, the fields can be accessed withJSON_QUERY(..)orJSON_VALUE(..).
For example, consider this portable class:
public class Employee : IPortable
{
int IPortable.ClassId => 123;
int IPortable.FactoryId => 345;
public int Age { get; set; }
public string Name { get; set; }
public void ReadPortable(IPortableReader reader)
{
Age = reader.ReadInt(nameof(Age));
Name = reader.ReadString(nameof(Name));
}
public void WritePortable(IPortableWriter writer)
{
writer.WriteInt(nameof(Age), Age);
writer.WriteString(nameof(Name), Name);
}
}
The SQL service can access the following fields:
| Name | SQL Type |
|---|---|
| name | VARCHAR |
| age | INTEGER |
Together with the key and value objects, you may query the following fields from IHMap<int, Employee>:
SELECT __key, this, Name, Age FROM employee
If both the key and value have fields with the same name, then the field of the value is exposed.
"SELECT *" Queries
You may use the SELECT * FROM <table> syntax to get all the table fields.
The __key and this fields are returned by the SELECT * queries if they do not have nested fields. For IHMap<number, Employee>, the following query does not return the this field, because the value has nested fields Name and Age:
-- Returns __key, Name, Age
SELECT * FROM employee
Special characters in names
If map or field name contains non-alphanumeric characters or starts with a number, you will need to enclose it in double quotes:
SELECT * FROM "my-map"
SELECT * FROM "2map"
Enumerating query result
ISqlService.ExecuteQuery returns Hazelcast.Sql.ISqlQueryResult which provides methods to manage current query:
await using var result = await client.Sql.ExecuteQueryAsync("SELECT Name, Age FROM employee");
It implements IAsyncEnumerable<SqlRow> as one-off stream of rows and can be enumerated via regular foreach cycle:
await foreach (var row in result)
Console.WriteLine(row.GetColumn<string>("Name"));
Using LINQ over IAsyncEnumerable<T> is also possible but requires installing System.Linq.Async package. See SqlLinqEnumerationExample as an example.
NOTE: Obtained result is not reusable as
IAsyncEnumerable<SqlRow>. It will never restart enumeration but continue where previous one finished.
Warning
Give attention that filtering and projection is done in local of client in the example. To use LINQ which runs all query on server, please see LINQ Provider.
Disposing query result
ISqlQueryResult implements IAsyncDisposable. It's DisposeAsync implementation will make sure to cancel the query and free used server resources.
Because of this, it is recommended to wrap operations with query into await using statement. This will ensure to send Cancel request in case if query is cancelled client-side or exception is thrown before it is completed or all rows are exhausted:
await using (var result = await client.Sql.ExecuteQueryAsync("SELECT * FROM MyMap"))
{
//...
}
Cancelling query enumeration
You can cancel enumeration of ISqlQueryResult, via WithCancellation extension method, see SqlCancellationExample.
If you're using System.Linq.Async package, you can also pass CancellationToken to ToListAsync, ToArrayAsync and related methods.
NOTE: At the moment cancellation doesn't work during server query itself. Cancellation will stop the enumeration before fetching next page or switching to the next row of the current page, but won't stop executing request. This will be fixed in the later versions.
Data Types
The SQL service supports a set of SQL data types represented by Hazelcast.Sql.SqlColumnType enum. The table below shows SQL datatype, and corresponding .NET types:
| Column Type | .NET |
|---|---|
| VARCHAR | string |
| BOOLEAN | bool |
| TINYINT | byte |
| SMALLINT | short |
| INTEGER | int |
| BIGINT | long |
| DECIMAL | Hazelcast.Sql.HBigDecimal |
| REAL | float |
| DOUBLE | double |
| DATE | Hazelcast.Sql.HLocalDate |
| TIME | Hazelcast.Sql.HLocalTime |
| TIMESTAMP | Hazelcast.Sql.HLocalDateTime |
| TIMESTAMP_WITH_TIME_ZONE | Hazelcast.Sql.HOffsetDateTime |
| OBJECT | Any class |
| JSON | Hazelcast.Core.HazelcastJsonValue |
| NULL | null |
All Hazelcast.Sql.* types has conversion to and from their closest built-in counterparts. Table below lists possible conversions:
| Hazelcast type | To .NET Type | From .NET Type |
----------------------------------|-------------------|------------------------|
| Hazelcast.Sql.HBigDecimal | decimal** | decimal |
| Hazelcast.Sql.HLocalDate | DateTime* | DateTime |
| Hazelcast.Sql.HLocalTime | TimeSpan | TimeSpan, DateTime |
| Hazelcast.Sql.HLocalDateTime | DateTime* | DateTime |
| Hazelcast.Sql.HOffsetDateTime | DateTimeOffset* | DateTimeOffset |
* - Possible ArgumentOutOfRangeException
** - Possible OverflowException
|
Decimal String Format
SQL DECIMAL type uses dot as separator.
Examples: 12345, 123456.789.
Date String Format
SQL DATE type uses yyyy-mm-dd format.
Examples: 2021-07-01, 1990-12-31.
Time String Format
SQL TIME uses HH:mm:ss.SSS where HH is in 24-hour format and, SSS represents nanoseconds and can be at most 9 digits long.
Examples: 10:20:30, 23:59:59.999999999
Timestamp String Format
SQL TIMESTAMP type uses yyyy-mm-dd(T|t)HH:mm:ss.SSS which is the combination of
DATE and TIME strings. There must be a T or t letter in between.
Examples: 2021-07-01T10:20:30, 1990-12-31t23:59:59.999999999
Timestamp with Timezone String Format
SQL TIMESTAMP WITH TIMEZONE uses yyyy-mm-dd(T|t)HH:mm:ss.SSS{timezoneString} which is the combination of TIMESTAMP and timezone strings. The timezone string can be one of Z, +hh:mm or -hh:mm where hh represents hour-in-day, and mm represents minutes-in-hour.
The timezone must be in the range [-18:00, +18:00].
2021-07-01T10:20:30Z, 1990-12-31t23:59:59.999999999+11:30
Casting
You may need to use casting when sending parameters for certain types. In general, you should try to send a parameter that has the same data type as the related column.
How to Cast
Casting syntax: CAST(? AS TYPE)
Example casting:
SELECT * FROM someMap WHERE this = CAST(? AS INTEGER)
Casting Between Types
When comparing a column with a parameter, your parameter must be of a compatible type. You can cast string to every SQL type.
An Example of Implicit Cast
In the example below, Age column is of type INTEGER. We pass parameters as shorts (TINYINT) and they are automatically casted to INTEGER for comparison.
await using var result = await client.Sql.ExecuteQueryAsync(
$"SELECT Name FROM {map.Name} WHERE Age > ? AND Age < ?",
(short)20, (short)30
);
An Example of Explicit Cast
In the example below, Age column is of type INTEGER. We pass parameters as strings (VARCHAR) and cast them to INTEGER for comparison.
await using var result = await client.Sql.ExecuteQueryAsync(
$"SELECT Name FROM {map.Name} WHERE Age > CAST(? AS INTEGER) AND Age < CAST(? AS INTEGER)",
"20", "30"
);
Important Notes About Comparison and Casting
In case of comparison operators (=, <, <>, ...), if one side is
?, it's assumed to be exactly the other side's type, except thatTINYINT,SMALLINT,INTEGERare all converted toBIGINT. Note, that reverse is not valid as it may lead to value loss.String parameters can be cast to any type. The cast operation may fail though.
To send a
DECIMALtype, useHazelcast.Sql.HBigDecimalor an explicitCASTfrom string or other number type.To send date and time related types, use corresponding
Hazelcast.Sql.H*type or a string with an explicitCAST.
SELECT
Synopsis
SELECT [ * | expression [ [ AS ] expression_alias ] [, ...] ]
FROM table_name [ [ AS ] table_alias ]
[WHERE condition]
Description
The SELECT query retrieves rows from a table. A row is a sequence of expressions defined after the SELECT keyword. Expressions may have optional aliases.
table_name refers to a single IHMap data structure. A table may have an optional alias.
An optional WHERE clause defines a condition, that is any expression that evaluates to a result of type boolean. Any row that doesn’t satisfy the condition is eliminated from the result.
Sorting
You can use the standard SQL clauses ORDER BY, LIMIT, and OFFSET to sort and limit the result set. In order to do so, you need server configuration. See Hazelcast docs for more.
Unsupported Features
The following features are not supported and are planned for future releases:
- set operators (
UNION,INTERSECT,MINUS) - subqueries (
SELECT … FROM table WHERE x = (SELECT …))
Expressions
Hazelcast SQL supports logical predicates, IS predicates, comparison operators, mathematical functions and operators, string functions, and special functions.
Refer to Hazelcast docs for all possible operations.
Lite Members
You cannot start SQL queries on lite members. This limitation will be removed in future releases.
More Information
Please refer to Hazelcast SQL docs for more information.
For basic usage of SQL, see SqlBasicQueryExample and SqlJsonExample in Hazelcast.Net.Examples project.