Complex data types¶
Complex data types can be indexed, searched and sorted. Supported complex data types:
The best way to show you how it works is with some examples.
You can find samples source code at Hawkore's Apache Ignite extensions sample project.
Let's create a sample Profile table indexed with Advanced Lucene Index using Java Annotations Based Configuration.
public class Profile { @QuerySqlField(index=true) private String login; @QuerySqlField private String firstName; @QuerySqlField private String lastName ; /** * Simple types can be indexed inside collections */ @QueryTextField(stringMappers=@StringMapper(case_sensitive=false)) @QuerySqlField private ArrayList<String> cities; /** * Simple types can be indexed inside maps */ @QueryTextField( stringMappers={ // below mapper is equivalent to @StringMapper(name="textAddresses", case_sensitive=true) // as if name is not provided will takes annotated property name @StringMapper(case_sensitive=true), @StringMapper(name="textAddresses._key", case_sensitive=true), @StringMapper(name="textAddresses._value", case_sensitive=false) } ) @QuerySqlField private HashMap<String, String> textAddresses; /** * Complex java type's components can be indexed * * <p> * NOTE that if components within Address were not annotated with {@code @QueryTextField} you need to * define index here as follows: * * <pre> * {@code @QueryTextField}( * stringMappers={ * {@code @StringMapper}(name="address.city", case_sensitive=false) * }, * integerMappers = {@code @IntegerMapper}(name="address.zip") * ) * </pre> */ @QueryTextField @QuerySqlField (hidden=true) private Address address; /** * Complex java type's components can be indexed even while being inside maps * * <p> * NOTE that components within Address do not require to be annotated with {@code @QueryTextField} * as index MUST be defined here */ @QueryTextField( stringMappers={ @StringMapper(name="addresses._key", case_sensitive=true), @StringMapper(name="addresses.city", case_sensitive=false) }, integerMappers = @IntegerMapper(name="addresses.zip") ) @QuerySqlField private HashMap<String, Address> addresses; /** * Complex java type's components can be indexed even while being inside collections * * <p> * NOTE that components within Address do not require to be annotated with {@code @QueryTextField} * as index MUST be defined here * */ @QueryTextField( stringMappers={ @StringMapper(name="addressList.city", case_sensitive=false) }, integerMappers = @IntegerMapper(name="addressList.zip") ) @QuerySqlField private ArrayList<Address> addressList; [...] }
/** * Address * * <p> * * NOTE that below {@code @QueryTextField} annotations will be used for complex * java type indexing of Address as a direct member of Profile (out of map and collections). * * <p> * * They are not required if you define index within Profile. * * @author Manuel Núñez ([email protected]) * */ public class Address { @QuerySqlField private String street; @QueryTextField(stringMappers = @StringMapper(case_sensitive = false)) @QuerySqlField private String city; @QueryTextField(integerMappers = @IntegerMapper) @QuerySqlField private int zip; [...] }
<bean id="profilesCache" class="org.apache.ignite.configuration.CacheConfiguration"> <property name="name" value="profiles" /> <property name="rebalanceMode" value="ASYNC"/> <property name="cacheMode" value="PARTITIONED" /> <property name="indexedTypes"> <array> <value>java.lang.String</value> <value>com.hawkore.ignite.examples.entities.profiles.Profile</value> </array> </property> </bean>
Complex Java Types¶
The components of Complex Types can be indexed, searched and sorted this way:
SELECT LOGIN, FIRSTNAME, LASTNAME, ADDRESS_CITY, ADDRESS_ZIP, ADDRESS_STREET FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "address.city", value: "Chicago" } }';
SELECT LOGIN, FIRSTNAME, LASTNAME, ADDRESS_CITY, ADDRESS_ZIP, ADDRESS_STREET FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "address.zip", value: 60601 } }';
SELECT LOGIN, FIRSTNAME, LASTNAME, ADDRESS_CITY, ADDRESS_ZIP, ADDRESS_STREET FROM "profiles".profile WHERE lucene = '{ refresh: true, sort: { field: "address.zip", reverse: true } }';
Collections¶
Any java.util.Collection
non-abstract sub type can be indexed.
Lists and sets are indexed in the same way as regular columns, using their base type:
Searches are also done in the same way as with regular columns:
SELECT LOGIN, FIRSTNAME, LASTNAME, CITIES FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "cities", value: "London" } }';
SELECT LOGIN, FIRSTNAME, LASTNAME, CITIES FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "cities", value: "OtherCity_20" } }';
Complex types can be searched even while being inside collections:
SELECT LOGIN, FIRSTNAME, LASTNAME, addressList FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "addressList.city", value: "Chicago" } }';
SELECT LOGIN, FIRSTNAME, LASTNAME, addressList FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "addressList.zip", value: 60601 } }';
Maps¶
Any java.util.Map
non-abstract sub type can be indexed.
Map keys and values can also be indexed by adding the suffixes ._key
or ._value
to the mapped column name.
Please don't use map keys containing the separator chars, which are .
and $
.
Maps values are indexed by default using their keys as field name suffixes. For searching map values under a certain key you should use $
as field-key separator (fieldName$keyValue
):
SELECT LOGIN, FIRSTNAME, LASTNAME, textAddresses FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "textAddresses$London", value: "Camden Road #12" } }';
SELECT LOGIN, FIRSTNAME, LASTNAME, textAddresses FROM "profiles".profile WHERE lucene = '{ filter: { type: "match", field: "textAddresses._key", value: "OtherCity_17" } }';
SELECT LOGIN, FIRSTNAME, LASTNAME, textAddresses FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "textAddresses._value", value: "Paseo de la Castellana #50" } }';
Complex types can be searched even while being inside maps:
SELECT LOGIN, FIRSTNAME, LASTNAME, addresses FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "addresses.city$Illinois", value: "Chicago" } }';
SELECT LOGIN, FIRSTNAME, LASTNAME, addresses FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "addresses.zip$Illinois", value: 60601 } }';
SELECT LOGIN, FIRSTNAME, LASTNAME, addresses FROM "profiles".profile WHERE lucene = '{ refresh: true, filter: { type: "match", field: "addresses._key", value: "OtherState_17" } }';
Ignite Cache Entry¶
If Ignite Cache Entry is composed by simple types, you can index and search _KEY
and _VAL
using Advanced Lucene index, where _KEY
is the Cache Entry's key and _VAL
is the Cache Entry's value.
<!-- QueryEntity Based Configuration for String valueType. As you know this could be do it programmatically :) --> <bean id="stringCache" class="org.apache.ignite.configuration.CacheConfiguration"> <property name="name" value="str"/> <!-- Configure query entities --> <property name="queryEntities"> <list> <bean class="org.apache.ignite.cache.QueryEntity"> <property name="tableName" value="TABLE"/> <!-- Setting indexed type's key SIMPLE class --> <property name="keyType" value="java.lang.Integer"/> <!-- Setting indexed type's value SIMPLE class --> <property name="valueType" value="java.lang.String"/> <!-- Defining indexed fields.--> <property name="indexes"> <list> <!-- Defining lucene index on _key and _val (key and value cache entry).--> <bean class="org.apache.ignite.cache.QueryIndex"> <constructor-arg> <list> <value>LUCENE</value> </list> </constructor-arg> <constructor-arg value="FULLTEXT"/> <!-- Defining advanced lucene index configuration.--> <property name="luceneIndexOptions"> <value><![CDATA[{ 'version':'0', 'refresh_seconds':'60', 'directory_path':'', 'ram_buffer_mb':'10', 'max_cached_mb':'-1', 'optimizer_enabled':'true', 'optimizer_schedule':'0 1 * * *', 'schema':'{ "fields":{ "_KEY":{"type":"integer","validated":false}, "_VAL":{"type":"text","validated":false,"analyzer":"standard"} } }' }]]></value> </property> </bean> </list> </property> </bean> </list> </property> </bean>
-- Lucene index search on cache entry with simple types insert into "str".TABLE (_key,_val) values (1, 'a very large text one'); insert into "str".TABLE (_key,_val) values (2, 'a very large text two'); insert into "str".TABLE (_key,_val) values (3, 'a very large text three'); insert into "str".TABLE (_key,_val) values (4, 'a very large text four'); -- Refresh index SELECT * FROM "str".TABLE WHERE lucene = '{ refresh : true}'; -- Phrase query on _VAL cache entry SELECT * FROM "str".TABLE WHERE lucene = '{refresh : true, query : { type : "phrase", field : "_VAL", value : "three" }}'; -- Match query on _KEY cache entry SELECT * FROM "str".TABLE WHERE lucene = '{refresh : true, query : { type : "match", field : "_KEY", value : 2 }}';
Summary¶
Using Advanced Lucene indexing on collections and maps may produce surprising results.
Let's create a sample Blog table indexed with Advanced Lucene Index using Java Annotations Based Configuration.
public class BlogEntry { @QuerySqlField @QueryTextField(textMappers=@TextMapper(analyzer="english")) private String comment; @QuerySqlField @QueryTextField(stringMappers=@StringMapper(case_sensitive=false)) private ArrayList<String> tags; [...] }
public class BlogEntryKey { @QueryTextField(longMappers = @LongMapper) @QuerySqlField(index = true) private long id; @QueryTextField(stringMappers = @StringMapper) @QuerySqlField(index = true) @AffinityKeyMapped private String author; [...] }
<bean id="blogCache" class="org.apache.ignite.configuration.CacheConfiguration"> <property name="name" value="blog" /> <property name="rebalanceMode" value="ASYNC"/> <property name="cacheMode" value="PARTITIONED" /> <property name="indexedTypes"> <array> <value>com.hawkore.ignite.examples.entities.blog.BlogEntryKey</value> <value>com.hawkore.ignite.examples.entities.blog.BlogEntry</value> </array> </property> </bean>
Let's insert some data:
-- insert blog some entries INSERT INTO "blog".blogEntry(id, author, comment, tags) VALUES(1, 'Jose', 'Apache Ignite is awesome', public.hkjson_to_list('["Apache Ignite"]')); INSERT INTO "blog".blogEntry(id, author, comment, tags) VALUES(2, 'Manuel', 'Apache Ignite''s web console app is great', public.hkjson_to_list('["Apache Ignite", "management"]')); INSERT INTO "blog".blogEntry(id, author, comment, tags) VALUES(3, 'Jorge', 'New Apache Ignite 2.7.0 has been released', public.hkjson_to_list('["Apache Ignite", "development"]')); INSERT INTO "blog".blogEntry(id, author, comment, tags) VALUES(4, 'Arturo', 'Is there any embedded Apache Ignite service for tests?', public.hkjson_to_list('["Apache Ignite", "testing"]')); INSERT INTO "blog".blogEntry(id, author, comment, tags) VALUES(5, 'Manuel', 'Apache Ignite''s SQL distributed database is a surprising feature.', public.hkjson_to_list('["Apache Ignite", "management", "development", "testing"]'));
Search for blog entries that contains "Apache Ignite" on tags:
SELECT * FROM "blog".blogEntry WHERE lucene = '{ refresh: true, filter: { type: "boolean", must: [ {type: "match", field: "tags", value: "Apache Ignite"} ] } }';
+----+--------+-------------------------------------------------------------------+---------------------------------------------------+ | ID | AUTHOR | COMMENT | TAGS | +====+========+===================================================================+===================================================+ | 1 | Jose | Apache Ignite is awesome | [Apache Ignite] | +----+--------+-------------------------------------------------------------------+---------------------------------------------------+ | 4 | Jose | Is there any embedded Apache Ignite service for tests? | [Apache Ignite, testing] | +----+--------+-------------------------------------------------------------------+---------------------------------------------------+ | 3 | Jorge | New Apache Ignite 2.7.0 has been released | [Apache Ignite, development] | +----+--------+-------------------------------------------------------------------+---------------------------------------------------+ | 2 | Manuel | Apache Ignite's web console app is great | [Apache Ignite, management] | +----+--------+-------------------------------------------------------------------+---------------------------------------------------+ | 5 | Manuel | Apache Ignite's SQL distributed database is a surprising feature. | [Apache Ignite, management, development, testing] | +----+--------+-------------------------------------------------------------------+---------------------------------------------------+
Search for blog entries that contains "Apache Ignite" and "management" on tags:
SELECT * FROM "blog".blogEntry WHERE lucene = '{ refresh: true, filter: { type: "boolean", must: [ {type: "match", field: "tags", value: "Apache Ignite"}, {type: "match", field: "tags", value: "management"} ] } }';
+----+--------+-------------------------------------------------------------------+---------------------------------------------------+ | ID | AUTHOR | COMMENT | TAGS | +====+========+===================================================================+===================================================+ | 2 | Manuel | Apache Ignite's web console app is great | [Apache Ignite, management] | +----+--------+-------------------------------------------------------------------+---------------------------------------------------+ | 5 | Manuel | Apache Ignite's SQL distributed database is a surprising feature. | [Apache Ignite, management, development, testing] | +----+--------+-------------------------------------------------------------------+---------------------------------------------------+