How to execute ranged query in cassandra with astyanax and composite column - cassandra

I am developing a blog using cassandra and astyanax. It is only an exercise of course.
I have modelled the CF_POST_INFO column family in this way:
private static class PostAttribute {
#Component(ordinal = 0)
UUID postId;
#Component(ordinal = 1)
String category;
#Component
String name;
public PostAttribute() {}
private PostAttribute(UUID postId, String category, String name) {
this.postId = postId;
this.category = category;
this.name = name;
}
public static PostAttribute of(UUID postId, String category, String name) {
return new PostAttribute(postId, category, name);
}
}
private static AnnotatedCompositeSerializer<PostAttribute> postSerializer = new AnnotatedCompositeSerializer<>(PostAttribute.class);
private static final ColumnFamily<String, PostAttribute> CF_POST_INFO =
ColumnFamily.newColumnFamily("post_info", StringSerializer.get(), postSerializer);
And a post is saved in this way:
MutationBatch m = keyspace().prepareMutationBatch();
ColumnListMutation<PostAttribute> clm = m.withRow(CF_POST_INFO, "posts")
.putColumn(PostAttribute.of(post.getId(), "author", "id"), post.getAuthor().getId().get())
.putColumn(PostAttribute.of(post.getId(), "author", "name"), post.getAuthor().getName())
.putColumn(PostAttribute.of(post.getId(), "meta", "title"), post.getTitle())
.putColumn(PostAttribute.of(post.getId(), "meta", "pubDate"), post.getPublishingDate().toDate());
for(String tag : post.getTags()) {
clm.putColumn(PostAttribute.of(post.getId(), "tags", tag), (String) null);
}
for(String category : post.getCategories()) {
clm.putColumn(PostAttribute.of(post.getId(), "categories", category), (String)null);
}
the idea is to have some row like bucket of some time (one row per month or year for example).
Now if I want to get the last 5 posts for example, how can I do a rage query for that? I can execute a rage query based on the post id (UUID) but I don't know the available post ids without doing another query to get them. What are the cassandra best practice here?
Any suggestion about the data model is welcome of course, I'm very newbie to cassandra.

If your use case works the way I think it works you could modify your PostAttribute so that the first component is a TimeUUID that way you can store it as time series data and you'd easily be able to pull the oldest 5 or newest 5 using the standard techniques. Anyway...here's a sample of what it would look like to me since you don't really need to make multiple columns if you're already using composites.
public class PostInfo {
#Component(ordinal = 0)
protected UUID timeUuid;
#Component(ordinal = 1)
protected UUID postId;
#Component(ordinal = 2)
protected String category;
#Component(ordinal = 3)
protected String name;
#Component(ordinal = 4)
protected UUID authorId;
#Component(ordinal = 5)
protected String authorName;
#Component(ordinal = 6)
protected String title;
#Component(ordinal = 7)
protected Date published;
public PostInfo() {}
private PostInfo(final UUID postId, final String category, final String name, final UUID authorId, final String authorName, final String title, final Date published) {
this.timeUuid = TimeUUIDUtils.getUniqueTimeUUIDinMillis();
this.postId = postId;
this.category = category;
this.name = name;
this.authorId = authorId;
this.authorName = authorName;
this.title = title;
this.published = published;
}
public static PostInfo of(final UUID postId, final String category, final String name, final UUID authorId, final String authorName, final String title, final Date published) {
return new PostInfo(postId, category, name, authorId, authorName, title, published);
}
}
private static AnnotatedCompositeSerializer<PostInfo> postInfoSerializer = new AnnotatedCompositeSerializer<>(PostInfo.class);
private static final ColumnFamily<String, PostInfo> CF_POSTS_TIMELINE =
ColumnFamily.newColumnFamily("post_info", StringSerializer.get(), postInfoSerializer);
You should save it like this:
MutationBatch m = keyspace().prepareMutationBatch();
ColumnListMutation<PostInfo> clm = m.withRow(CF_POSTS_TIMELINE, "all" /* or whatever makes sense for you such as year or month or whatever */)
.putColumn(PostInfo.of(post.getId(), post.getCategory(), post.getName(), post.getAuthor().getId(), post.getAuthor().getName(), post.getTitle(), post.getPublishedOn()), /* maybe just null bytes as column value */)
m.execute();
Then you could query like this:
OperationResult<ColumnList<PostInfo>> result = getKeyspace()
.prepareQuery(CF_POSTS_TIMELINE)
.getKey("all" /* or whatever makes sense like month, year, etc */)
.withColumnRange(new RangeBuilder()
.setLimit(5)
.setReversed(true)
.build())
.execute();
ColumnList<PostInfo> columns = result.getResult();
for (Column<PostInfo> column : columns) {
// do what you need here
}

Related

How to use fieldset in lightning Component

I want to create a custom lightning component to create new Case records and need to use fieldset to include fields in component. Need to use this only for one object. I never used fieldsets so dont have any idea on it. It would be really great if you can share some sample code or any link for the same.
You can use this utility class
This is the wrapper class to hold the meta info about the fields
public with sharing class DataTableColumns {
#AuraEnabled
public String label {get;set;}
#AuraEnabled
public String fieldName {get;set;}
#AuraEnabled
public String type {get;set;}
public DataTableColumns(String label, String fieldName, String type){
this.label = label;
this.fieldName = fieldName;
this.type = type;
}
}
Class FieldSetHelper has a method getColumns () this will return the list of DataTableColumns wrapper containing the information about the filedset columns
public with sharing class FieldSetHelper {
/*
#param String strObjectName : required. Object name to get the required filed set
#param String strFieldSetName : required. FieldSet name
#return List<DataTableColumns> list of columns in the specified fieldSet
*/
public static List<DataTableColumns> getColumns (String strObjectName, String strFieldSetName) {
Schema.SObjectType SObjectTypeObj = Schema.getGlobalDescribe().get(strObjectName);
Schema.DescribeSObjectResult DescribeSObjectResultObj = SObjectTypeObj.getDescribe();
Schema.FieldSet fieldSetObj = DescribeSObjectResultObj.FieldSets.getMap().get(strFieldSetName);
List<DataTableColumns> lstDataColumns = new List<DataTableColumns>();
for( Schema.FieldSetMember eachFieldSetMember : fieldSetObj.getFields() ){
String dataType =
String.valueOf(eachFieldSetMember.getType()).toLowerCase();
DataTableColumns datacolumns = new DataTableColumns(
String.valueOf(eachFieldSetMember.getLabel()) ,
String.valueOf(eachFieldSetMember.getFieldPath()),
String.valueOf(eachFieldSetMember.getType()).toLowerCase() );
lstDataColumns.add(datacolumns);
}
return lstDataColumns;
}
}
After you getting all those field set information the create lightning component dynamically

Get column name and their value in cassandra trigger

I have this cassandra trigger
public class AuditTrigger implements ITrigger
{
private Properties properties = loadProperties();
public Collection<Mutation> augment(Partition update)
{
String auditKeyspace = properties.getProperty("keyspace");
String auditTable = properties.getProperty("table");
RowUpdateBuilder audit = new RowUpdateBuilder(Schema.instance.getCFMetaData(auditKeyspace, auditTable),
FBUtilities.timestampMicros(),
UUIDGen.getTimeUUID());
audit.add("keyspace_name", update.metadata().ksName);
audit.add("table_name", update.metadata().cfName);
audit.add("primary_key", update.metadata().getKeyValidator().getString(update.partitionKey().getKey()));
return Collections.singletonList(audit.build());
}
}
ho can I get the inserted value and their column name ?

Lucene query with numeric field does not find anything

I try to understand how the lucene query syntax works so I wrote this small program.
When using a NumericRangeQuery I can find the documents I want but when trying to parse a search condition, it can't find any hits, although I'm using the same conditions.
i understand the difference can be explained by the analyzer but the StandardAnalyzer is used which does not remove numeric values.
Can someone tell me what I'm doing wrong ?
Thanks.
package org.burre.lucene.matching;
import java.io.IOException;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.store.*;
import org.apache.lucene.util.Version;
public class SmallestEngine {
private static final Version VERSION=Version.LUCENE_48;
private StandardAnalyzer analyzer = new StandardAnalyzer(VERSION);
private Directory index = new RAMDirectory();
private Document buildDoc(String name, int beds) {
Document doc = new Document();
doc.add(new StringField("name", name, Field.Store.YES));
doc.add(new IntField("beds", beds, Field.Store.YES));
return doc;
}
public void buildSearchEngine() throws IOException {
IndexWriterConfig config = new IndexWriterConfig(VERSION,
analyzer);
IndexWriter w = new IndexWriter(index, config);
// Generate 10 houses with 0 to 3 beds
for (int i=0;i<10;i++)
w.addDocument(buildDoc("house"+(100+i),i % 4));
w.close();
}
/**
* Execute the query and show the result
*/
public void search(Query q) throws IOException {
System.out.println("executing query\""+q+"\"");
IndexReader reader = DirectoryReader.open(index);
try {
IndexSearcher searcher = new IndexSearcher(reader);
ScoreDoc[] hits = searcher.search(q, 10).scoreDocs;
System.out.println("Found " + hits.length + " hits.");
for (int i = 0; i < hits.length; ++i) {
int docId = hits[i].doc;
Document d = searcher.doc(docId);
System.out.println(""+(i+1)+". " + d.get("name") + ", beds:"
+ d.get("beds"));
}
} finally {
if (reader != null)
reader.close();
}
}
public static void main(String[] args) throws IOException, ParseException {
SmallestEngine me = new SmallestEngine();
me.buildSearchEngine();
System.out.println("SearchByRange");
me.search(NumericRangeQuery.newIntRange("beds", 3, 3,true,true));
System.out.println("-----------------");
System.out.println("SearchName");
me.search(new QueryParser(VERSION,"name",me.analyzer).parse("house107"));
System.out.println("-----------------");
System.out.println("Search3Beds");
me.search(new QueryParser(VERSION,"beds",me.analyzer).parse("3"));
System.out.println("-----------------");
System.out.println("Search3BedsInRange");
me.search(new QueryParser(VERSION,"name",me.analyzer).parse("beds:[3 TO 3]"));
}
}
The output of this program is:
SearchByRange
executing query"beds:[3 TO 3]"
Found 2 hits.
1. house103, beds:3
2. house107, beds:3
-----------------
SearchName
executing query"name:house107"
Found 1 hits.
1. house107, beds:3
-----------------
Search3Beds
executing query"beds:3"
Found 0 hits.
-----------------
Search3BedsInRange
executing query"beds:[3 TO 3]"
Found 0 hits.
You need to use NumericRangeQuery to perform a search on the numeric field.
The answer here could give you some insight.
Also the answer here says
for numeric values (longs, dates, floats, etc.) you need to have NumericRangeQuery. Otherwise Lucene has no idea how do you want to define similarity.
What you need to do is to write your own QueryParser:
public class CustomQueryParser extends QueryParser {
// ctor omitted
#Override
public Query newTermQuery(Term term) {
if (term.field().equals("beds")) {
// manually construct and return non-range query for numeric value
} else {
return super.newTermQuery(term);
}
}
#Override
public Query newRangeQuery(String field, String part1, String part2, boolean startInclusive, boolean endInclusive) {
if (field.equals("beds")) {
// manually construct and return range query for numeric value
} else {
return super.newRangeQuery(field, part1, part2, startInclusive, endInclusive);
}
}
}
It seems like you always have to use the NumericRangeQuery for numeric conditions. (thanks to Mindas) so as he suggested I created My own more intelligent QueryParser.
Using the Apache commons-lang function StringUtils.isNumeric() I can create a more generic QueryParser:
public class IntelligentQueryParser extends QueryParser {
// take over super constructors
#Override
protected org.apache.lucene.search.Query newRangeQuery(String field,
String part1, String part2, boolean part1Inclusive, boolean part2Inclusive) {
if(StringUtils.isNumeric(part1))
{
return NumericRangeQuery.newIntRange(field, Integer.parseInt(part1),Integer.parseInt(part2),part1Inclusive,part2Inclusive);
}
return super.newRangeQuery(field, part1, part2, part1Inclusive, part2Inclusive);
}
#Override
protected org.apache.lucene.search.Query newTermQuery(
org.apache.lucene.index.Term term) {
if(StringUtils.isNumeric(term.text()))
{
return NumericRangeQuery.newIntRange(term.field(), Integer.parseInt(term.text()),Integer.parseInt(term.text()),true,true);
}
return super.newTermQuery(term);
}
}
Just wanted to share this.

JPQL: The state field path cannot be resolved to a valid type

I can't make this query work:
Query query = eManager.createQuery("select c FROM News c WHERE c.NEWSID = :id",News.class);
return (News)query.setParameter("id", newsId).getSingleResult();
and I got this exception:
Exception Description: Problem compiling [select c FROM News c WHERE c.NEWSID = :id].
[27, 35] The state field path 'c.NEWSID' cannot be resolved to a valid type.] with root cause
Local Exception Stack:
Exception [EclipseLink-0] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.JPQLException
Exception Description: Problem compiling [select c FROM News c WHERE c.NEWSID = :id].
Why does it happen?
:id and named parameter are identical
EDIT:
my entity class
#Entity
#Table(name="NEWS")
public class News implements Serializable{
#Id
#SequenceGenerator(name = "news_seq_gen", sequenceName = "news_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "news_seq_gen")
private int newsId;
private String newsTitle;
private String newsBrief;
private String newsContent;
private Date newsDate;
#Transient
private boolean selected=false;
//constructor and getters and setters
That happens because News entity does not have persistent attribute named NEWSID. Names of the persistent attributes are case sensitive in JPQL queries and those should be written with exactly same case as they appear in entity.
Because entity have persistent attribute named newsId, that should also be used in query instead of NEWSID:
select c FROM News c WHERE c.newsId = :id
entity have persistent attribute named newsId.but in query you have used NEWSID . try with this
select c FROM News c WHERE c.newsId = :id
My entity is:
#Entity
#Table(name = "TBL_PERSON_INFO")
public class Person implements Serializable {
#Id
#Column(name = "ID", nullable = false)
private Integer id;
#Column(name = "USER_ID", nullable = false)
private Integer user_id;
.
.
.
}
my query is (JPQL):
String queryName = "from Person p where p.user_id = :user_id";
so I use it like this:
javax.persistence.Query query = em.createQuery(queryName);
query.setParameter("user_id", userId);
try {
obj = query.getSingleResult();
}
catch (javax.persistence.NoResultException nre) {
logger.error("javax.persistence.NoResultException: " + nre.getMessage());
}
catch (javax.persistence.NonUniqueResultException nure) {
logger.error("javax.persistence.NonUniqueResultException: " + nure.getMessage());
}
catch (Exception e) {
e.printStackTrace();
}
if (obj == null) {
System.out.println("obj is null!");
return null;
}
Person person = (Person) obj;
It's work ;-)

JPQL Query - get child item in list from a parent

How would I create a JPQL query to get a Record if it exists in a Folder when I have the Folder :id and the Record :refId (named parameters). I would like to have the Record returned back from the query as there is other data that is part of the Record needed.
Here are the entities:
#Entity
public class Folder {
#Id
#Column(name = "id")
private Long id;
#OneToMany(targetEntity = Record.class, cascade = { CascadeType.ALL })
#JoinColumn(name = "RECORD_FOLDER_0")
#OrderBy("id ASC")
private List<Record> records;
...
}
#Entity
public class Record {
#Id
#Column(name = "id")
private Long id;
#Basic
#Column(name = "refId")
private Long refId;
#Basic
#Column(name = "data", length = 255)
private String data;
...
}
The regular SQL would look something like:
.createNativeQuery(String.format("SELECT * FROM record r WHERE r.refId=%d AND r.RECORD_FOLDER_0=%d", refId, folderId));
I am struggling on how to use a JPQL query to do this.
Since you don't have a reference to the parent Folder from Record, you'll have to start with the Folder and join the child Records. Something like this should do the trick:
TypedQuery<Record> q = em.createQuery(
"SELECT r FROM Folder f JOIN f.records r " +
"WHERE f.id = :folderId AND r.refId = :refId", Record.class);
q.setParameter("folderId", folderId);
q.setParameter("refId", refId);

Resources