You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ak...@apache.org on 2016/09/19 14:30:07 UTC

ignite git commit: IGNITE-3799. 1) "alias" fields - POJO fields sharing the same Cassandra table column, but just having different names in POJO object. 2) calculated fields - read only POJO fields which values calculated based on other field values. S

Repository: ignite
Updated Branches:
  refs/heads/master f2f82f09b -> 5281a0fe6


IGNITE-3799.
 1) "alias" fields - POJO fields sharing the same Cassandra table column, but just having different names in POJO object.
 2) calculated fields - read only POJO fields which values calculated based on other field values. Such fields should be only written to Cassandra, but it doesn't make sense to read them back on load operation.
 3) unit tests modified according to new POJO fields aliases concept.
 Fixes #1080.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/5281a0fe
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/5281a0fe
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/5281a0fe

Branch: refs/heads/master
Commit: 5281a0fe6d363d35cadaf6de874854d07b2f678b
Parents: f2f82f0
Author: Igor Rudyak <ir...@gmail.com>
Authored: Mon Sep 19 21:29:56 2016 +0700
Committer: AKuznetsov <ak...@gridgain.com>
Committed: Mon Sep 19 21:29:56 2016 +0700

----------------------------------------------------------------------
 .../store/cassandra/CassandraCacheStore.java    |   2 +-
 .../cassandra/CassandraCacheStoreFactory.java   |   2 +-
 .../store/cassandra/common/CassandraHelper.java |  18 +
 .../cassandra/common/PropertyMappingHelper.java |   3 +-
 .../persistence/KeyPersistenceSettings.java     |  55 ++-
 .../KeyValuePersistenceSettings.java            |  82 +++-
 .../persistence/PersistenceController.java      | 417 ++++++++++---------
 .../persistence/PersistenceSettings.java        | 157 ++++++-
 .../store/cassandra/persistence/PojoField.java  |  47 ++-
 .../persistence/ValuePersistenceSettings.java   |  21 +-
 .../ignite/tests/IgnitePersistentStoreTest.java |  14 +-
 .../ignite/tests/load/PersonGenerator.java      |   2 +-
 .../org/apache/ignite/tests/pojos/Person.java   |  35 +-
 .../apache/ignite/tests/utils/TestsHelper.java  |  22 +-
 .../persistence/pojo/persistence-settings-3.xml |   2 +
 15 files changed, 602 insertions(+), 277 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStore.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStore.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStore.java
index f7e7917..9ba3a75 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStore.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStore.java
@@ -72,7 +72,7 @@ public class CassandraCacheStore<K, V> implements CacheStore<K, V> {
     private int maxPoolSize = Runtime.getRuntime().availableProcessors();
 
     /** Controller component responsible for serialization logic. */
-    private PersistenceController controller;
+    private final PersistenceController controller;
 
     /**
      * Store constructor.

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStoreFactory.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStoreFactory.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStoreFactory.java
index 7584dfb..71bb737 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStoreFactory.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/CassandraCacheStoreFactory.java
@@ -46,7 +46,7 @@ public class CassandraCacheStoreFactory<K, V> implements Factory<CassandraCacheS
     private String persistenceSettingsBean;
 
     /** Data source. */
-    private transient DataSource dataSrc;
+    private DataSource dataSrc;
 
     /** Persistence settings. */
     private KeyValuePersistenceSettings persistenceSettings;

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/CassandraHelper.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/CassandraHelper.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/CassandraHelper.java
index d3bff7f..9066112 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/CassandraHelper.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/CassandraHelper.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.cache.store.cassandra.common;
 
 import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.DataType;
 import com.datastax.driver.core.Session;
 import com.datastax.driver.core.exceptions.InvalidQueryException;
 import com.datastax.driver.core.exceptions.NoHostAvailableException;
@@ -129,5 +130,22 @@ public class CassandraHelper {
 
         return false;
     }
+
+    /**
+     * Checks if two Java classes are Cassandra compatible - mapped to the same Cassandra type.
+     *
+     * @param type1 First type.
+     * @param type2 Second type.
+     * @return {@code true} if classes are compatible and {@code false} if not.
+     */
+    public static boolean isCassandraCompatibleTypes(Class type1, Class type2) {
+        if (type1 == null || type2 == null)
+            return false;
+
+        DataType.Name t1 = PropertyMappingHelper.getCassandraType(type1);
+        DataType.Name t2 = PropertyMappingHelper.getCassandraType(type2);
+
+        return t1 != null && t2 != null && t1.equals(t2);
+    }
 }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/PropertyMappingHelper.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/PropertyMappingHelper.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/PropertyMappingHelper.java
index 9053a93..64b784b 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/PropertyMappingHelper.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/common/PropertyMappingHelper.java
@@ -130,8 +130,7 @@ public class PropertyMappingHelper {
             return list;
 
         for (PropertyDescriptor descriptor : descriptors) {
-            if (descriptor.getReadMethod() == null || descriptor.getWriteMethod() == null ||
-                (primitive && !isPrimitivePropertyDescriptor(descriptor)))
+            if (descriptor.getReadMethod() == null || (primitive && !isPrimitivePropertyDescriptor(descriptor)))
                 continue;
 
             if (annotation == null || descriptor.getReadMethod().getAnnotation(annotation) != null)

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyPersistenceSettings.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyPersistenceSettings.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyPersistenceSettings.java
index 5732b4e..c614abf 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyPersistenceSettings.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyPersistenceSettings.java
@@ -20,7 +20,10 @@ package org.apache.ignite.cache.store.cassandra.persistence;
 import java.beans.PropertyDescriptor;
 import java.util.LinkedList;
 import java.util.List;
+
+import org.apache.ignite.IgniteException;
 import org.apache.ignite.cache.affinity.AffinityKeyMapped;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
 import org.apache.ignite.cache.store.cassandra.common.PropertyMappingHelper;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
@@ -55,8 +58,11 @@ public class KeyPersistenceSettings extends PersistenceSettings {
     public KeyPersistenceSettings(Element el) {
         super(el);
 
-        if (!PersistenceStrategy.POJO.equals(getStrategy()))
+        if (PersistenceStrategy.POJO != getStrategy()) {
+            init();
+
             return;
+        }
 
         NodeList keyElem = el.getElementsByTagName(PARTITION_KEY_ELEMENT);
 
@@ -84,6 +90,8 @@ public class KeyPersistenceSettings extends PersistenceSettings {
         fields.addAll(clusterKeyFields);
 
         checkDuplicates(fields);
+
+        init();
     }
 
     /** {@inheritDoc} */
@@ -120,8 +128,8 @@ public class KeyPersistenceSettings extends PersistenceSettings {
         }
 
         return clusterKey.length() == 0 ?
-            "  primary key ((" + partKey.toString() + "))" :
-            "  primary key ((" + partKey.toString() + "), " + clusterKey.toString() + ")";
+            "  primary key ((" + partKey + "))" :
+            "  primary key ((" + partKey + "), " + clusterKey + ")";
     }
 
     /**
@@ -141,12 +149,12 @@ public class KeyPersistenceSettings extends PersistenceSettings {
             if (builder.length() != 0)
                 builder.append(", ");
 
-            boolean asc = PojoKeyField.SortOrder.ASC.equals(sortOrder);
+            boolean asc = PojoKeyField.SortOrder.ASC == sortOrder;
 
             builder.append("\"").append(field.getColumn()).append("\" ").append(asc ? "asc" : "desc");
         }
 
-        return builder.length() == 0 ? null : "clustering order by (" + builder.toString() + ")";
+        return builder.length() == 0 ? null : "clustering order by (" + builder + ")";
     }
 
     /** {@inheritDoc} */
@@ -162,7 +170,7 @@ public class KeyPersistenceSettings extends PersistenceSettings {
     private List<String> getPartitionKeyColumns() {
         List<String> cols = new LinkedList<>();
 
-        if (PersistenceStrategy.BLOB.equals(getStrategy()) || PersistenceStrategy.PRIMITIVE.equals(getStrategy())) {
+        if (PersistenceStrategy.BLOB == getStrategy() || PersistenceStrategy.PRIMITIVE == getStrategy()) {
             cols.add(getColumn());
             return cols;
         }
@@ -205,8 +213,15 @@ public class KeyPersistenceSettings extends PersistenceSettings {
             return list;
 
         if (el == null) {
-            for (PropertyDescriptor descriptor : descriptors)
-                list.add(new PojoKeyField(descriptor));
+            for (PropertyDescriptor desc : descriptors) {
+                boolean valid = desc.getWriteMethod() != null ||
+                        desc.getReadMethod().getAnnotation(QuerySqlField.class) != null ||
+                        desc.getReadMethod().getAnnotation(AffinityKeyMapped.class) != null;
+
+                // Skip POJO field if it's read-only and is not annotated with @QuerySqlField or @AffinityKeyMapped.
+                if (valid)
+                    list.add(new PojoKeyField(desc));
+            }
 
             return list;
         }
@@ -217,7 +232,7 @@ public class KeyPersistenceSettings extends PersistenceSettings {
 
         if (cnt == 0) {
             throw new IllegalArgumentException("Incorrect configuration of Cassandra key persistence settings, " +
-                "no cluster key fields specified inside '" + PARTITION_KEY_ELEMENT + "/" +
+                "no key fields specified inside '" + PARTITION_KEY_ELEMENT + "/" +
                 CLUSTER_KEY_ELEMENT + "' element");
         }
 
@@ -244,9 +259,25 @@ public class KeyPersistenceSettings extends PersistenceSettings {
         List<PropertyDescriptor> primitivePropDescriptors = PropertyMappingHelper.getPojoPropertyDescriptors(getJavaClass(),
             AffinityKeyMapped.class, true);
 
-        return primitivePropDescriptors != null && !primitivePropDescriptors.isEmpty() ?
-            primitivePropDescriptors :
-            PropertyMappingHelper.getPojoPropertyDescriptors(getJavaClass(), true);
+        primitivePropDescriptors = primitivePropDescriptors != null && !primitivePropDescriptors.isEmpty() ?
+            primitivePropDescriptors : PropertyMappingHelper.getPojoPropertyDescriptors(getJavaClass(), true);
+
+        boolean valid = false;
+
+        for (PropertyDescriptor desc : primitivePropDescriptors) {
+            if (desc.getWriteMethod() != null) {
+                valid = true;
+
+                break;
+            }
+        }
+
+        if (!valid) {
+            throw new IgniteException("Partition key can't have only calculated read-only fields, there should be " +
+                    "some fields with setter method");
+        }
+
+        return primitivePropDescriptors;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyValuePersistenceSettings.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyValuePersistenceSettings.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyValuePersistenceSettings.java
index f4de066..ce8214a 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyValuePersistenceSettings.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/KeyValuePersistenceSettings.java
@@ -25,11 +25,15 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Serializable;
 import java.io.StringReader;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
+import java.util.Collections;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import org.apache.ignite.IgniteException;
+import org.apache.ignite.cache.store.cassandra.common.CassandraHelper;
 import org.apache.ignite.cache.store.cassandra.common.SystemHelper;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.springframework.core.io.Resource;
@@ -101,6 +105,9 @@ public class KeyValuePersistenceSettings implements Serializable {
     /** Persistence settings for Ignite cache values. */
     private ValuePersistenceSettings valPersistenceSettings;
 
+    /** List of Cassandra table columns */
+    private List<String> tableColumns;
+
     /**
      * Constructs Ignite cache key/value persistence settings.
      *
@@ -264,12 +271,27 @@ public class KeyValuePersistenceSettings implements Serializable {
     }
 
     /**
+     * Returns column names for Cassandra table.
+     *
+     * @return Column names.
+     */
+    public List<String> getTableColumns() {
+        return tableColumns;
+    }
+
+    /**
      * Returns DDL statement to create Cassandra table.
      *
      * @return Table DDL statement.
      */
     public String getTableDDLStatement() {
-        String colsDDL = keyPersistenceSettings.getTableColumnsDDL() + ",\n" + valPersistenceSettings.getTableColumnsDDL();
+        String keyColumnsDDL = keyPersistenceSettings.getTableColumnsDDL();
+        String valColumnsDDL = valPersistenceSettings.getTableColumnsDDL(new HashSet<>(keyPersistenceSettings.getTableColumns()));
+
+        String colsDDL = keyColumnsDDL;
+
+        if (valColumnsDDL != null && !valColumnsDDL.trim().isEmpty())
+            colsDDL += ",\n" + valColumnsDDL;
 
         String primaryKeyDDL = keyPersistenceSettings.getPrimaryKeyDDL();
 
@@ -304,10 +326,11 @@ public class KeyValuePersistenceSettings implements Serializable {
     public List<String> getIndexDDLStatements() {
         List<String> idxDDLs = new LinkedList<>();
 
+        Set<String> keyColumns = new HashSet<>(keyPersistenceSettings.getTableColumns());
         List<PojoField> fields = valPersistenceSettings.getFields();
 
         for (PojoField field : fields) {
-            if (((PojoValueField)field).isIndexed())
+            if (!keyColumns.contains(field.getColumn()) && ((PojoValueField)field).isIndexed())
                 idxDDLs.add(((PojoValueField)field).getIndexDDL(keyspace, tbl));
         }
 
@@ -360,7 +383,7 @@ public class KeyValuePersistenceSettings implements Serializable {
         try {
             return Integer.parseInt(val);
         }
-        catch (NumberFormatException e) {
+        catch (NumberFormatException ignored) {
             throw new IllegalArgumentException("Incorrect value '" + val + "' specified for '" + attr + "' attribute");
         }
     }
@@ -451,28 +474,63 @@ public class KeyValuePersistenceSettings implements Serializable {
         List<PojoField> keyFields = keyPersistenceSettings.getFields();
         List<PojoField> valFields = valPersistenceSettings.getFields();
 
-        if (PersistenceStrategy.POJO.equals(keyPersistenceSettings.getStrategy()) &&
+        if (PersistenceStrategy.POJO == keyPersistenceSettings.getStrategy() &&
             (keyFields == null || keyFields.isEmpty())) {
             throw new IllegalArgumentException("Incorrect Cassandra persistence settings specification, " +
                 "there are no key fields found");
         }
 
-        if (PersistenceStrategy.POJO.equals(valPersistenceSettings.getStrategy()) &&
+        if (PersistenceStrategy.POJO == valPersistenceSettings.getStrategy() &&
             (valFields == null || valFields.isEmpty())) {
             throw new IllegalArgumentException("Incorrect Cassandra persistence settings specification, " +
                 "there are no value fields found");
         }
 
-        if (keyFields == null || keyFields.isEmpty() || valFields == null || valFields.isEmpty())
-            return;
+        // Validating aliases compatibility - fields having different names, but mapped to the same Cassandra table column.
+        if (valFields != null && !valFields.isEmpty()) {
+            String keyColumn = keyPersistenceSettings.getColumn();
+            Class keyClass = keyPersistenceSettings.getJavaClass();
+
+            if (keyColumn != null && !keyColumn.isEmpty()) {
+                for (PojoField valField : valFields) {
+                    if (keyColumn.equals(valField.getColumn()) &&
+                            !CassandraHelper.isCassandraCompatibleTypes(keyClass, valField.getJavaClass())) {
+                        throw new IllegalArgumentException("Value field '" + valField.getName() + "' shares the same " +
+                                "Cassandra table column '" + keyColumn + "' with key, but their Java classes are " +
+                                "different. Fields sharing the same column should have the same Java class as their " +
+                                "type or should be mapped to the same Cassandra primitive type.");
+                    }
+                }
+            }
 
-        for (PojoField keyField : keyFields) {
-            for (PojoField valField : valFields) {
-                if (keyField.getColumn().equals(valField.getColumn())) {
-                    throw new IllegalArgumentException("Incorrect Cassandra persistence settings specification, " +
-                        "key column '" + keyField.getColumn() + "' also specified as a value column");
+            if (keyFields != null && !keyFields.isEmpty()) {
+                for (PojoField keyField : keyFields) {
+                    for (PojoField valField : valFields) {
+                        if (keyField.getColumn().equals(valField.getColumn()) &&
+                                !CassandraHelper.isCassandraCompatibleTypes(keyField.getJavaClass(), valField.getJavaClass())) {
+                            throw new IllegalArgumentException("Value field '" + valField.getName() + "' shares the same " +
+                                    "Cassandra table column '" + keyColumn + "' with key field '" + keyField.getName() + "', " +
+                                    "but their Java classes are different. Fields sharing the same column should have " +
+                                    "the same Java class as their type or should be mapped to the same Cassandra " +
+                                    "primitive type.");
+                        }
+                    }
                 }
             }
         }
+
+        tableColumns = new LinkedList<>();
+
+        for (String column : keyPersistenceSettings.getTableColumns()) {
+            if (!tableColumns.contains(column))
+                tableColumns.add(column);
+        }
+
+        for (String column : valPersistenceSettings.getTableColumns()) {
+            if (!tableColumns.contains(column))
+                tableColumns.add(column);
+        }
+
+        tableColumns = Collections.unmodifiableList(tableColumns);
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceController.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceController.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceController.java
index 9a1699b..fb3278e 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceController.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceController.java
@@ -21,8 +21,11 @@ import com.datastax.driver.core.BoundStatement;
 import com.datastax.driver.core.PreparedStatement;
 import com.datastax.driver.core.Row;
 import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.cache.store.cassandra.common.PropertyMappingHelper;
 import org.apache.ignite.cache.store.cassandra.serializer.Serializer;
@@ -34,19 +37,25 @@ import org.apache.ignite.cache.store.cassandra.serializer.Serializer;
  */
 public class PersistenceController {
     /** Ignite cache key/value persistence settings. */
-    private KeyValuePersistenceSettings persistenceSettings;
+    private final KeyValuePersistenceSettings persistenceSettings;
+
+    /** List of key unique POJO fields (skipping aliases pointing to the same Cassandra table column). */
+    private final List<PojoField> keyUniquePojoFields;
+
+    /** List of value unique POJO fields (skipping aliases pointing to the same Cassandra table column). */
+    private final List<PojoField> valUniquePojoFields;
 
     /** CQL statement to insert row into Cassandra table. */
-    private String writeStatement;
+    private final String writeStatement;
 
     /** CQL statement to delete row from Cassandra table. */
-    private String delStatement;
+    private final String delStatement;
 
     /** CQL statement to select value fields from Cassandra table. */
-    private String loadStatement;
+    private final String loadStatement;
 
     /** CQL statement to select key/value fields from Cassandra table. */
-    private String loadStatementWithKeyFields;
+    private final String loadStatementWithKeyFields;
 
     /**
      * Constructs persistence controller from Ignite cache persistence settings.
@@ -57,7 +66,46 @@ public class PersistenceController {
         if (settings == null)
             throw new IllegalArgumentException("Persistent settings can't be null");
 
-        this.persistenceSettings = settings;
+        persistenceSettings = settings;
+
+        String[] loadStatements = prepareLoadStatements();
+
+        loadStatementWithKeyFields = loadStatements[0];
+        loadStatement = loadStatements[1];
+        writeStatement = prepareWriteStatement();
+        delStatement = prepareDeleteStatement();
+
+        keyUniquePojoFields = settings.getKeyPersistenceSettings().cassandraUniqueFields();
+
+        List<PojoField> _valUniquePojoFields = settings.getValuePersistenceSettings().cassandraUniqueFields();
+
+        if (_valUniquePojoFields == null || _valUniquePojoFields.isEmpty()) {
+            valUniquePojoFields = _valUniquePojoFields;
+
+            return;
+        }
+
+        List<String> keyColumns = new LinkedList<>();
+
+        if (keyUniquePojoFields == null)
+            keyColumns.add(settings.getKeyPersistenceSettings().getColumn());
+        else {
+            for (PojoField field : keyUniquePojoFields)
+                keyColumns.add(field.getColumn());
+        }
+
+        List<PojoField> fields = new LinkedList<>(_valUniquePojoFields);
+
+        for (String column : keyColumns) {
+            for (int i = 0; i < fields.size(); i++) {
+                if (column.equals(fields.get(i).getColumn())) {
+                    fields.remove(i);
+                    break;
+                }
+            }
+        }
+
+        valUniquePojoFields = fields.isEmpty() ? null : Collections.unmodifiableList(fields);
     }
 
     /**
@@ -93,32 +141,6 @@ public class PersistenceController {
      * @return CQL statement.
      */
     public String getWriteStatement() {
-        if (writeStatement != null)
-            return writeStatement;
-
-        List<String> cols = getKeyValueColumns();
-
-        StringBuilder colsList = new StringBuilder();
-        StringBuilder questionsList = new StringBuilder();
-
-        for (String column : cols) {
-            if (colsList.length() != 0) {
-                colsList.append(", ");
-                questionsList.append(",");
-            }
-
-            colsList.append("\"").append(column).append("\"");
-            questionsList.append("?");
-        }
-
-        writeStatement = "insert into \"" + persistenceSettings.getKeyspace() + "\".\"" + persistenceSettings.getTable() + "\" (" +
-            colsList.toString() + ") values (" + questionsList.toString() + ")";
-
-        if (persistenceSettings.getTTL() != null)
-            writeStatement += " using ttl " + persistenceSettings.getTTL();
-
-        writeStatement += ";";
-
         return writeStatement;
     }
 
@@ -128,27 +150,6 @@ public class PersistenceController {
      * @return CQL statement.
      */
     public String getDeleteStatement() {
-        if (delStatement != null)
-            return delStatement;
-
-        List<String> cols = getKeyColumns();
-
-        StringBuilder statement = new StringBuilder();
-
-        for (String column : cols) {
-            if (statement.length() != 0)
-                statement.append(" and ");
-
-            statement.append("\"").append(column).append("\"=?");
-        }
-
-        statement.append(";");
-
-        delStatement = "delete from \"" +
-            persistenceSettings.getKeyspace() + "\".\"" +
-            persistenceSettings.getTable() + "\" where " +
-            statement.toString();
-
         return delStatement;
     }
 
@@ -160,53 +161,6 @@ public class PersistenceController {
      * @return CQL statement.
      */
     public String getLoadStatement(boolean includeKeyFields) {
-        if (loadStatement != null && loadStatementWithKeyFields != null)
-            return includeKeyFields ? loadStatementWithKeyFields : loadStatement;
-
-        List<String> valCols = getValueColumns();
-
-        List<String> keyCols = getKeyColumns();
-
-        StringBuilder hdrWithKeyFields = new StringBuilder("select ");
-
-        for (int i = 0; i < keyCols.size(); i++) {
-            if (i > 0)
-                hdrWithKeyFields.append(", ");
-
-            hdrWithKeyFields.append("\"").append(keyCols.get(i)).append("\"");
-        }
-
-        StringBuilder hdr = new StringBuilder("select ");
-
-        for (int i = 0; i < valCols.size(); i++) {
-            if (i > 0)
-                hdr.append(", ");
-
-            hdrWithKeyFields.append(",");
-
-            hdr.append("\"").append(valCols.get(i)).append("\"");
-            hdrWithKeyFields.append("\"").append(valCols.get(i)).append("\"");
-        }
-
-        StringBuilder statement = new StringBuilder();
-
-        statement.append(" from \"");
-        statement.append(persistenceSettings.getKeyspace());
-        statement.append("\".\"").append(persistenceSettings.getTable());
-        statement.append("\" where ");
-
-        for (int i = 0; i < keyCols.size(); i++) {
-            if (i > 0)
-                statement.append(" and ");
-
-            statement.append("\"").append(keyCols.get(i)).append("\"=?");
-        }
-
-        statement.append(";");
-
-        loadStatement = hdr.toString() + statement.toString();
-        loadStatementWithKeyFields = hdrWithKeyFields.toString() + statement.toString();
-
         return includeKeyFields ? loadStatementWithKeyFields : loadStatement;
     }
 
@@ -219,10 +173,12 @@ public class PersistenceController {
      * @return statement with bounded key.
      */
     public BoundStatement bindKey(PreparedStatement statement, Object key) {
-        KeyPersistenceSettings settings = persistenceSettings.getKeyPersistenceSettings();
+        PersistenceSettings settings = persistenceSettings.getKeyPersistenceSettings();
+
+        Object[] values = PersistenceStrategy.POJO != settings.getStrategy() ?
+            new Object[1] : new Object[keyUniquePojoFields.size()];
 
-        Object[] values = getBindingValues(settings.getStrategy(),
-            settings.getSerializer(), settings.getFields(), key);
+        bindValues(settings.getStrategy(), settings.getSerializer(), keyUniquePojoFields, key, values, 0);
 
         return statement.bind(values);
     }
@@ -237,27 +193,13 @@ public class PersistenceController {
      * @return statement with bounded key and value.
      */
     public BoundStatement bindKeyValue(PreparedStatement statement, Object key, Object val) {
-        KeyPersistenceSettings keySettings = persistenceSettings.getKeyPersistenceSettings();
-        Object[] keyValues = getBindingValues(keySettings.getStrategy(),
-            keySettings.getSerializer(), keySettings.getFields(), key);
-
-        ValuePersistenceSettings valSettings = persistenceSettings.getValuePersistenceSettings();
-        Object[] valValues = getBindingValues(valSettings.getStrategy(),
-            valSettings.getSerializer(), valSettings.getFields(), val);
+        Object[] values = new Object[persistenceSettings.getTableColumns().size()];
 
-        Object[] values = new Object[keyValues.length + valValues.length];
+        PersistenceSettings keySettings = persistenceSettings.getKeyPersistenceSettings();
+        PersistenceSettings valSettings = persistenceSettings.getValuePersistenceSettings();
 
-        int i = 0;
-
-        for (Object keyVal : keyValues) {
-            values[i] = keyVal;
-            i++;
-        }
-
-        for (Object valVal : valValues) {
-            values[i] = valVal;
-            i++;
-        }
+        int offset = bindValues(keySettings.getStrategy(), keySettings.getSerializer(), keyUniquePojoFields, key, values, 0);
+        bindValues(valSettings.getStrategy(), valSettings.getSerializer(), valUniquePojoFields, val, values, offset);
 
         return statement.bind(values);
     }
@@ -286,6 +228,128 @@ public class PersistenceController {
     }
 
     /**
+     * Service method to prepare CQL write statement.
+     *
+     * @return CQL write statement.
+     */
+    private String prepareWriteStatement() {
+        Collection<String> cols = persistenceSettings.getTableColumns();
+
+        StringBuilder colsList = new StringBuilder();
+        StringBuilder questionsList = new StringBuilder();
+
+        for (String column : cols) {
+            if (colsList.length() != 0) {
+                colsList.append(", ");
+                questionsList.append(",");
+            }
+
+            colsList.append("\"").append(column).append("\"");
+            questionsList.append("?");
+        }
+
+        String statement = "insert into \"" + persistenceSettings.getKeyspace() + "\".\"" +
+                persistenceSettings.getTable() + "\" (" + colsList + ") values (" + questionsList + ")";
+
+        if (persistenceSettings.getTTL() != null)
+            statement += " using ttl " + persistenceSettings.getTTL();
+
+        return statement + ";";
+    }
+
+    /**
+     * Service method to prepare CQL delete statement.
+     *
+     * @return CQL write statement.
+     */
+    private String prepareDeleteStatement() {
+        Collection<String> cols = persistenceSettings.getKeyPersistenceSettings().getTableColumns();
+
+        StringBuilder statement = new StringBuilder();
+
+        for (String column : cols) {
+            if (statement.length() != 0)
+                statement.append(" and ");
+
+            statement.append("\"").append(column).append("\"=?");
+        }
+
+        statement.append(";");
+
+        return "delete from \"" +
+            persistenceSettings.getKeyspace() + "\".\"" +
+            persistenceSettings.getTable() + "\" where " +
+            statement;
+    }
+
+    /**
+     * Service method to prepare CQL load statements including and excluding key columns.
+     *
+     * @return array having two CQL statements (including and excluding key columns).
+     */
+    private String[] prepareLoadStatements() {
+        PersistenceSettings settings = persistenceSettings.getKeyPersistenceSettings();
+        boolean pojoStrategy = PersistenceStrategy.POJO == settings.getStrategy();
+        Collection<String> keyCols = settings.getTableColumns();
+        StringBuilder hdrWithKeyFields = new StringBuilder();
+
+
+        for (String column : keyCols) {
+            // omit calculated fields in load statement
+            if (pojoStrategy && settings.getFieldByColumn(column).calculatedField())
+                continue;
+
+            if (hdrWithKeyFields.length() > 0)
+                hdrWithKeyFields.append(", ");
+
+            hdrWithKeyFields.append("\"").append(column).append("\"");
+        }
+
+        settings = persistenceSettings.getValuePersistenceSettings();
+        pojoStrategy = PersistenceStrategy.POJO == settings.getStrategy();
+        Collection<String> valCols = settings.getTableColumns();
+        StringBuilder hdr = new StringBuilder();
+
+        for (String column : valCols) {
+            // omit calculated fields in load statement
+            if (pojoStrategy && settings.getFieldByColumn(column).calculatedField())
+                continue;
+
+            if (hdr.length() > 0)
+                hdr.append(", ");
+
+            hdr.append("\"").append(column).append("\"");
+
+            if (!keyCols.contains(column))
+                hdrWithKeyFields.append(", \"").append(column).append("\"");
+        }
+
+        hdrWithKeyFields.insert(0, "select ");
+        hdr.insert(0, "select ");
+
+        StringBuilder statement = new StringBuilder();
+
+        statement.append(" from \"");
+        statement.append(persistenceSettings.getKeyspace());
+        statement.append("\".\"").append(persistenceSettings.getTable());
+        statement.append("\" where ");
+
+        int i = 0;
+
+        for (String column : keyCols) {
+            if (i > 0)
+                statement.append(" and ");
+
+            statement.append("\"").append(column).append("\"=?");
+            i++;
+        }
+
+        statement.append(";");
+
+        return new String[] {hdrWithKeyFields + statement.toString(), hdr + statement.toString()};
+    }
+
+    /**
      * Builds object from Cassandra table row.
      *
      * @param row Cassandra table row.
@@ -297,20 +361,19 @@ public class PersistenceController {
         if (row == null)
             return null;
 
-        PersistenceStrategy stgy = settings.getStrategy();
+        PersistenceStrategy stg = settings.getStrategy();
 
         Class clazz = settings.getJavaClass();
-
         String col = settings.getColumn();
 
-        List<PojoField> fields = settings.getFields();
-
-        if (PersistenceStrategy.PRIMITIVE.equals(stgy))
+        if (PersistenceStrategy.PRIMITIVE == stg)
             return PropertyMappingHelper.getCassandraColumnValue(row, col, clazz, null);
 
-        if (PersistenceStrategy.BLOB.equals(stgy))
+        if (PersistenceStrategy.BLOB == stg)
             return settings.getSerializer().deserialize(row.getBytes(col));
 
+        List<PojoField> fields = settings.getFields();
+
         Object obj;
 
         try {
@@ -320,40 +383,50 @@ public class PersistenceController {
             throw new IgniteException("Failed to instantiate object of type '" + clazz.getName() + "' using reflection", e);
         }
 
-        for (PojoField field : fields)
-            field.setValueFromRow(row, obj, settings.getSerializer());
+        for (PojoField field : fields) {
+            if (!field.calculatedField())
+                field.setValueFromRow(row, obj, settings.getSerializer());
+        }
 
         return obj;
     }
 
     /**
-     * Extracts field values from POJO object and converts them into Java types
-     * which could be mapped to Cassandra types.
+     * Extracts field values from POJO object, converts into Java types
+     * which could be mapped to Cassandra types and stores them inside provided values
+     * array starting from specified offset.
      *
-     * @param stgy persistence strategy to use.
-     * @param serializer serializer to use for BLOBs.
-     * @param fields fields who's values should be extracted.
-     * @param obj object instance who's field values should be extracted.
+     * @param stgy Persistence strategy to use.
+     * @param serializer Serializer to use for BLOBs.
+     * @param fields Fields who's values should be extracted.
+     * @param obj Object instance who's field values should be extracted.
+     * @param values Array to store values.
+     * @param offset Offset starting from which to store fields values in the provided values array.
      *
-     * @return array of object field values converted into Java object instances having Cassandra compatible types
+     * @return next offset
      */
-    private Object[] getBindingValues(PersistenceStrategy stgy, Serializer serializer, List<PojoField> fields, Object obj) {
-        if (PersistenceStrategy.PRIMITIVE.equals(stgy)) {
+    private int bindValues(PersistenceStrategy stgy, Serializer serializer, List<PojoField> fields, Object obj,
+                            Object[] values, int offset) {
+        if (PersistenceStrategy.PRIMITIVE == stgy) {
             if (PropertyMappingHelper.getCassandraType(obj.getClass()) == null ||
                 obj.getClass().equals(ByteBuffer.class) || obj instanceof byte[]) {
                 throw new IllegalArgumentException("Couldn't deserialize instance of class '" +
                     obj.getClass().getName() + "' using PRIMITIVE strategy. Please use BLOB strategy for this case.");
             }
 
-            return new Object[] {obj};
+            values[offset] = obj;
+
+            return ++offset;
         }
 
-        if (PersistenceStrategy.BLOB.equals(stgy))
-            return new Object[] {serializer.serialize(obj)};
+        if (PersistenceStrategy.BLOB == stgy) {
+            values[offset] = serializer.serialize(obj);
 
-        Object[] values = new Object[fields.size()];
+            return ++offset;
+        }
 
-        int i = 0;
+        if (fields == null || fields.isEmpty())
+            return offset;
 
         for (PojoField field : fields) {
             Object val = field.getValueFromObject(obj, serializer);
@@ -361,61 +434,11 @@ public class PersistenceController {
             if (val instanceof byte[])
                 val = ByteBuffer.wrap((byte[]) val);
 
-            values[i] = val;
-
-            i++;
-        }
-
-        return values;
-    }
-
-    /**
-     * Returns list of Cassandra table columns mapped to Ignite cache key and value fields
-     *
-     * @return list of column names
-     */
-    private List<String> getKeyValueColumns() {
-        List<String> cols = getKeyColumns();
-
-        cols.addAll(getValueColumns());
-
-        return cols;
-    }
-
-    /**
-     * Returns list of Cassandra table columns mapped to Ignite cache key fields
-     *
-     * @return list of column names
-     */
-    private List<String> getKeyColumns() {
-        return getColumns(persistenceSettings.getKeyPersistenceSettings());
-    }
+            values[offset] = val;
 
-    /**
-     * Returns list of Cassandra table columns mapped to Ignite cache value fields
-     *
-     * @return list of column names
-     */
-    private List<String> getValueColumns() {
-        return getColumns(persistenceSettings.getValuePersistenceSettings());
-    }
-
-    /**
-     * Returns list of Cassandra table columns based on persistence strategy to use
-     *
-     * @return list of column names
-     */
-    private List<String> getColumns(PersistenceSettings settings) {
-        List<String> cols = new LinkedList<>();
-
-        if (!PersistenceStrategy.POJO.equals(settings.getStrategy())) {
-            cols.add(settings.getColumn());
-            return cols;
+            offset++;
         }
 
-        for (PojoField field : settings.getFields())
-            cols.add(field.getColumn());
-
-        return cols;
+        return offset;
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceSettings.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceSettings.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceSettings.java
index 1e858a3..f22c0a4 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceSettings.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PersistenceSettings.java
@@ -21,8 +21,14 @@ import com.datastax.driver.core.DataType;
 import java.beans.PropertyDescriptor;
 import java.io.Serializable;
 import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
+import java.util.Collections;
+
 import org.apache.ignite.IgniteException;
+import org.apache.ignite.cache.store.cassandra.common.CassandraHelper;
 import org.apache.ignite.cache.store.cassandra.common.PropertyMappingHelper;
 import org.apache.ignite.cache.store.cassandra.serializer.JavaSerializer;
 import org.apache.ignite.cache.store.cassandra.serializer.Serializer;
@@ -58,6 +64,15 @@ public abstract class PersistenceSettings implements Serializable {
     /** Serializer for BLOBs. */
     private Serializer serializer = new JavaSerializer();
 
+    /** List of Cassandra table columns */
+    private List<String> tableColumns;
+
+    /**
+     * List of POJO fields having unique mapping to Cassandra columns - skipping aliases pointing
+     *  to the same Cassandra table column.
+     */
+    private List<PojoField> casUniqueFields;
+
     /**
      * Extracts property descriptor from the descriptors list by its name.
      *
@@ -96,11 +111,11 @@ public abstract class PersistenceSettings implements Serializable {
         try {
             stgy = PersistenceStrategy.valueOf(el.getAttribute(STRATEGY_ATTR).trim().toUpperCase());
         }
-        catch (IllegalArgumentException e) {
+        catch (IllegalArgumentException ignored) {
             throw new IllegalArgumentException("Incorrect persistence strategy specified: " + el.getAttribute(STRATEGY_ATTR));
         }
 
-        if (!el.hasAttribute(CLASS_ATTR) && !PersistenceStrategy.BLOB.equals(stgy)) {
+        if (!el.hasAttribute(CLASS_ATTR) && PersistenceStrategy.BLOB != stgy) {
             throw new IllegalArgumentException("DOM element representing key/value persistence object should have '" +
                 CLASS_ATTR + "' attribute or have BLOB persistence strategy");
         }
@@ -113,19 +128,19 @@ public abstract class PersistenceSettings implements Serializable {
                 "for Cassandra persistence", e);
         }
 
-        if (!PersistenceStrategy.BLOB.equals(stgy) &&
+        if (PersistenceStrategy.BLOB != stgy &&
             (ByteBuffer.class.equals(javaCls) || byte[].class.equals(javaCls))) {
             throw new IllegalArgumentException("Java class '" + el.getAttribute(CLASS_ATTR) + "' " +
                 "specified could only be persisted using BLOB persistence strategy");
         }
 
-        if (PersistenceStrategy.PRIMITIVE.equals(stgy) &&
+        if (PersistenceStrategy.PRIMITIVE == stgy &&
             PropertyMappingHelper.getCassandraType(javaCls) == null) {
             throw new IllegalArgumentException("Current implementation doesn't support persisting '" +
                 javaCls.getName() + "' object using PRIMITIVE strategy");
         }
 
-        if (PersistenceStrategy.POJO.equals(stgy)) {
+        if (PersistenceStrategy.POJO == stgy) {
             if (javaCls == null)
                 throw new IllegalStateException("Object java class should be specified for POJO persistence strategy");
 
@@ -139,7 +154,7 @@ public abstract class PersistenceSettings implements Serializable {
         }
 
         if (el.hasAttribute(COLUMN_ATTR)) {
-            if (!PersistenceStrategy.BLOB.equals(stgy) && !PersistenceStrategy.PRIMITIVE.equals(stgy)) {
+            if (PersistenceStrategy.BLOB != stgy && PersistenceStrategy.PRIMITIVE != stgy) {
                 throw new IllegalArgumentException("Incorrect configuration of Cassandra key/value persistence settings, " +
                     "'" + COLUMN_ATTR + "' attribute is only applicable for PRIMITIVE or BLOB strategy");
             }
@@ -148,7 +163,7 @@ public abstract class PersistenceSettings implements Serializable {
         }
 
         if (el.hasAttribute(SERIALIZER_ATTR)) {
-            if (!PersistenceStrategy.BLOB.equals(stgy) && !PersistenceStrategy.POJO.equals(stgy)) {
+            if (PersistenceStrategy.BLOB != stgy && PersistenceStrategy.POJO != stgy) {
                 throw new IllegalArgumentException("Incorrect configuration of Cassandra key/value persistence settings, " +
                     "'" + SERIALIZER_ATTR + "' attribute is only applicable for BLOB and POJO strategies");
             }
@@ -164,7 +179,7 @@ public abstract class PersistenceSettings implements Serializable {
             serializer = (Serializer)obj;
         }
 
-        if ((PersistenceStrategy.BLOB.equals(stgy) || PersistenceStrategy.PRIMITIVE.equals(stgy)) && col == null)
+        if ((PersistenceStrategy.BLOB == stgy || PersistenceStrategy.PRIMITIVE == stgy) && col == null)
             col = defaultColumnName();
     }
 
@@ -206,36 +221,99 @@ public abstract class PersistenceSettings implements Serializable {
     }
 
     /**
-     * Returns list of POJO fields to be persisted.
+     * Returns a list of POJO fields to be persisted.
      *
      * @return list of fields.
      */
     public abstract List<PojoField> getFields();
 
     /**
+     * Returns POJO field by Cassandra table column name.
+     *
+     * @param column column name.
+     *
+     * @return POJO field or null if not exists.
+     */
+    public PojoField getFieldByColumn(String column) {
+        List<PojoField> fields = getFields();
+
+        if (fields == null || fields.isEmpty())
+            return null;
+
+        for (PojoField field : fields) {
+            if (field.getColumn().equals(column))
+                return field;
+        }
+
+        return null;
+    }
+
+    /**
+     * List of POJO fields having unique mapping to Cassandra columns - skipping aliases pointing
+     * to the same Cassandra table column.
+     *
+     * @return List of fields.
+     */
+    public List<PojoField> cassandraUniqueFields() {
+        return casUniqueFields;
+    }
+
+    /**
+     * Returns set of database column names, used to persist field values
+     *
+     * @return set of database column names
+     */
+    public List<String> getTableColumns() {
+        return tableColumns;
+    }
+
+    /**
      * Returns Cassandra table columns DDL, corresponding to POJO fields which should be persisted.
      *
-     * @return DDL statement for Cassandra table fields
+     * @return DDL statement for Cassandra table fields.
      */
     public String getTableColumnsDDL() {
-        if (PersistenceStrategy.BLOB.equals(stgy))
+        return getTableColumnsDDL(null);
+    }
+
+    /**
+     * Returns Cassandra table columns DDL, corresponding to POJO fields which should be persisted.
+     *
+     * @param ignoreColumns Table columns to ignore (exclude) from DDL.
+     * @return DDL statement for Cassandra table fields.
+     */
+    public String getTableColumnsDDL(Set<String> ignoreColumns) {
+        if (PersistenceStrategy.BLOB == stgy)
             return "  \"" + col + "\" " + DataType.Name.BLOB.toString();
 
-        if (PersistenceStrategy.PRIMITIVE.equals(stgy))
+        if (PersistenceStrategy.PRIMITIVE == stgy)
             return "  \"" + col + "\" " + PropertyMappingHelper.getCassandraType(javaCls);
 
+        List<PojoField> fields = getFields();
+
+        if (fields == null || fields.isEmpty()) {
+            throw new IllegalStateException("There are no POJO fields found for '" + javaCls.toString()
+                + "' class to be presented as a Cassandra primary key");
+        }
+
+        // Accumulating already processed columns in the set, to prevent duplicating columns
+        // shared by two different POJO fields.
+        Set<String> processedColumns = new HashSet<>();
+
         StringBuilder builder = new StringBuilder();
 
-        for (PojoField field : getFields()) {
+        for (PojoField field : fields) {
+            if ((ignoreColumns != null && ignoreColumns.contains(field.getColumn())) ||
+                    processedColumns.contains(field.getColumn())) {
+                continue;
+            }
+
             if (builder.length() > 0)
                 builder.append(",\n");
 
             builder.append("  ").append(field.getColumnDDL());
-        }
 
-        if (builder.length() == 0) {
-            throw new IllegalStateException("There are no POJO fields found for '" + javaCls.toString()
-                + "' class to be presented as a Cassandra primary key");
+            processedColumns.add(field.getColumn());
         }
 
         return builder.toString();
@@ -249,9 +327,40 @@ public abstract class PersistenceSettings implements Serializable {
     protected abstract String defaultColumnName();
 
     /**
-     * Checks if there are POJO filed with the same name or same Cassandra column specified in persistence settings
+     * Class instance initialization.
+     */
+    protected void init() {
+        if (getColumn() != null && !getColumn().trim().isEmpty()) {
+            tableColumns = new LinkedList<>();
+            tableColumns.add(getColumn());
+            tableColumns = Collections.unmodifiableList(tableColumns);
+
+            return;
+        }
+
+        List<PojoField> fields = getFields();
+
+        if (fields == null || fields.isEmpty())
+            return;
+
+        tableColumns = new LinkedList<>();
+        casUniqueFields = new LinkedList<>();
+
+        for (PojoField field : fields) {
+            if (!tableColumns.contains(field.getColumn())) {
+                tableColumns.add(field.getColumn());
+                casUniqueFields.add(field);
+            }
+        }
+
+        tableColumns = Collections.unmodifiableList(tableColumns);
+        casUniqueFields = Collections.unmodifiableList(casUniqueFields);
+    }
+
+    /**
+     * Checks if there are POJO filed with the same name or same Cassandra column specified in persistence settings.
      *
-     * @param fields list of fields to be persisted into Cassandra
+     * @param fields List of fields to be persisted into Cassandra.
      */
     protected void checkDuplicates(List<PojoField> fields) {
         if (fields == null || fields.isEmpty())
@@ -264,7 +373,7 @@ public abstract class PersistenceSettings implements Serializable {
             for (PojoField field2 : fields) {
                 if (field1.getName().equals(field2.getName())) {
                     if (sameNames) {
-                        throw new IllegalArgumentException("Incorrect Cassandra key persistence settings, " +
+                        throw new IllegalArgumentException("Incorrect Cassandra persistence settings, " +
                             "two POJO fields with the same name '" + field1.getName() + "' specified");
                     }
 
@@ -272,9 +381,11 @@ public abstract class PersistenceSettings implements Serializable {
                 }
 
                 if (field1.getColumn().equals(field2.getColumn())) {
-                    if (sameCols) {
-                        throw new IllegalArgumentException("Incorrect Cassandra persistence settings, " +
-                            "two POJO fields with the same column '" + field1.getColumn() + "' specified");
+                    if (sameCols && !CassandraHelper.isCassandraCompatibleTypes(field1.getJavaClass(), field2.getJavaClass())) {
+                        throw new IllegalArgumentException("Field '" + field1.getName() + "' shares the same Cassandra table " +
+                                "column '" + field1.getColumn() + "' with field '" + field2.getName() + "', but their Java " +
+                                "classes are different. Fields sharing the same column should have the same " +
+                                "Java class as their type or should be mapped to the same Cassandra primitive type.");
                     }
 
                     sameCols = true;

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PojoField.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PojoField.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PojoField.java
index 402aa9a..d708a34 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PojoField.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/PojoField.java
@@ -42,7 +42,7 @@ public abstract class PojoField implements Serializable {
     private String name;
 
     /** Java class to which the field belongs. */
-    private Class javaCls;
+    private Class objJavaCls;
 
     /** Field column name in Cassandra table. */
     private String col;
@@ -50,6 +50,9 @@ public abstract class PojoField implements Serializable {
     /** Field column DDL.  */
     private String colDDL;
 
+    /** Indicator for calculated field. */
+    private Boolean calculated;
+
     /** Field property descriptor. */
     private transient PropertyDescriptor desc;
 
@@ -88,7 +91,8 @@ public abstract class PojoField implements Serializable {
                 null :
                 desc.getWriteMethod().getAnnotation(QuerySqlField.class);
 
-        this.col = sqlField != null && sqlField.name() != null ? sqlField.name() : name.toLowerCase();
+        col = sqlField != null && sqlField.name() != null &&
+            !sqlField.name().trim().isEmpty() ? sqlField.name() : name.toLowerCase();
 
         init(desc);
 
@@ -104,6 +108,15 @@ public abstract class PojoField implements Serializable {
     }
 
     /**
+     * Returns java class of the field.
+     *
+     * @return Java class.
+     */
+    public Class getJavaClass() {
+        return propDesc().getPropertyType();
+    }
+
+    /**
      * @return Cassandra table column name.
      */
     public String getColumn() {
@@ -118,6 +131,21 @@ public abstract class PojoField implements Serializable {
     }
 
     /**
+     * Indicates if it's a calculated field - field which value just generated based on other field values.
+     * Such field will be stored in Cassandra as all other POJO fields, but it's value shouldn't be read from
+     * Cassandra - cause it's again just generated based on other field values. One of the good applications of such
+     * kind of fields - Cassandra materialized views build on top of other tables.
+     *
+     * @return {@code true} if it's auto generated field, {@code false} if not.
+     */
+    public boolean calculatedField() {
+        if (calculated != null)
+            return calculated;
+
+        return calculated = propDesc().getWriteMethod() == null;
+    }
+
+    /**
      * Gets field value as an object having Cassandra compatible type.
      * This it could be stored directly into Cassandra without any conversions.
      *
@@ -158,6 +186,9 @@ public abstract class PojoField implements Serializable {
      * @param serializer {@link org.apache.ignite.cache.store.cassandra.serializer.Serializer} to use.
      */
     public void setValueFromRow(Row row, Object obj, Serializer serializer) {
+        if (calculatedField())
+            return;
+
         Object val = PropertyMappingHelper.getCassandraColumnValue(row, col, propDesc().getPropertyType(), serializer);
 
         try {
@@ -188,22 +219,16 @@ public abstract class PojoField implements Serializable {
                 "' doesn't provide getter method");
         }
 
-        if (desc.getWriteMethod() == null) {
-            throw new IllegalArgumentException("Field '" + desc.getName() +
-                "' of POJO object instance of the class '" + desc.getPropertyType().getName() +
-                "' doesn't provide write method");
-        }
-
         if (!desc.getReadMethod().isAccessible())
             desc.getReadMethod().setAccessible(true);
 
-        if (!desc.getWriteMethod().isAccessible())
+        if (desc.getWriteMethod() != null && !desc.getWriteMethod().isAccessible())
             desc.getWriteMethod().setAccessible(true);
 
         DataType.Name cassandraType = PropertyMappingHelper.getCassandraType(desc.getPropertyType());
         cassandraType = cassandraType == null ? DataType.Name.BLOB : cassandraType;
 
-        this.javaCls = desc.getReadMethod().getDeclaringClass();
+        this.objJavaCls = desc.getReadMethod().getDeclaringClass();
         this.desc = desc;
         this.colDDL = "\"" + col + "\" " + cassandraType.toString();
     }
@@ -214,6 +239,6 @@ public abstract class PojoField implements Serializable {
      * @return Property descriptor
      */
     private PropertyDescriptor propDesc() {
-        return desc != null ? desc : (desc = PropertyMappingHelper.getPojoPropertyDescriptor(javaCls, name));
+        return desc != null ? desc : (desc = PropertyMappingHelper.getPojoPropertyDescriptor(objJavaCls, name));
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/ValuePersistenceSettings.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/ValuePersistenceSettings.java b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/ValuePersistenceSettings.java
index 877167d..f117fb6 100644
--- a/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/ValuePersistenceSettings.java
+++ b/modules/cassandra/store/src/main/java/org/apache/ignite/cache/store/cassandra/persistence/ValuePersistenceSettings.java
@@ -21,6 +21,8 @@ import java.beans.PropertyDescriptor;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
 import org.apache.ignite.cache.store.cassandra.common.PropertyMappingHelper;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
@@ -43,8 +45,11 @@ public class ValuePersistenceSettings extends PersistenceSettings {
     public ValuePersistenceSettings(Element el) {
         super(el);
 
-        if (!PersistenceStrategy.POJO.equals(getStrategy()))
+        if (PersistenceStrategy.POJO != getStrategy()) {
+            init();
+
             return;
+        }
 
         NodeList nodes = el.getElementsByTagName(FIELD_ELEMENT);
 
@@ -54,12 +59,14 @@ public class ValuePersistenceSettings extends PersistenceSettings {
             throw new IllegalStateException("Failed to initialize value fields for class '" + getJavaClass().getName() + "'");
 
         checkDuplicates(fields);
+
+        init();
     }
 
     /**
      * @return List of value fields.
      */
-    public List<PojoField> getFields() {
+    @Override public List<PojoField> getFields() {
         return fields == null ? null : Collections.unmodifiableList(fields);
     }
 
@@ -79,8 +86,14 @@ public class ValuePersistenceSettings extends PersistenceSettings {
 
         if (fieldNodes == null || fieldNodes.getLength() == 0) {
             List<PropertyDescriptor> primitivePropDescriptors = PropertyMappingHelper.getPojoPropertyDescriptors(getJavaClass(), true);
-            for (PropertyDescriptor descriptor : primitivePropDescriptors)
-                list.add(new PojoValueField(descriptor));
+            for (PropertyDescriptor desc : primitivePropDescriptors) {
+                boolean valid = desc.getWriteMethod() != null ||
+                        desc.getReadMethod().getAnnotation(QuerySqlField.class) != null;
+
+                // Skip POJO field if it's read-only and is not annotated with @QuerySqlField.
+                if (valid)
+                    list.add(new PojoValueField(desc));
+            }
 
             return list;
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/test/java/org/apache/ignite/tests/IgnitePersistentStoreTest.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/test/java/org/apache/ignite/tests/IgnitePersistentStoreTest.java b/modules/cassandra/store/src/test/java/org/apache/ignite/tests/IgnitePersistentStoreTest.java
index 5da6ba2..8fa5cc5 100644
--- a/modules/cassandra/store/src/test/java/org/apache/ignite/tests/IgnitePersistentStoreTest.java
+++ b/modules/cassandra/store/src/test/java/org/apache/ignite/tests/IgnitePersistentStoreTest.java
@@ -178,7 +178,7 @@ public class IgnitePersistentStoreTest {
 
             LOGGER.info("Running single operation write tests");
             longCache.put(1L, 1L);
-            personCache.put(1L, TestsHelper.generateRandomPerson());
+            personCache.put(1L, TestsHelper.generateRandomPerson(1L));
             LOGGER.info("Single operation write tests passed");
 
             LOGGER.info("Running bulk operation write tests");
@@ -251,9 +251,15 @@ public class IgnitePersistentStoreTest {
             IgniteCache<PersonId, Person> personCache3 = ignite.getOrCreateCache(new CacheConfiguration<PersonId, Person>("cache3"));
 
             LOGGER.info("Running single operation write tests");
-            personCache1.put(1L, TestsHelper.generateRandomPerson());
-            personCache2.put(TestsHelper.generateRandomPersonId(), TestsHelper.generateRandomPerson());
-            personCache3.put(TestsHelper.generateRandomPersonId(), TestsHelper.generateRandomPerson());
+
+            personCache1.put(1L, TestsHelper.generateRandomPerson(1L));
+
+            PersonId id = TestsHelper.generateRandomPersonId();
+            personCache2.put(id, TestsHelper.generateRandomPerson(id.getPersonNumber()));
+
+            id = TestsHelper.generateRandomPersonId();
+            personCache3.put(id, TestsHelper.generateRandomPerson(id.getPersonNumber()));
+
             LOGGER.info("Single operation write tests passed");
 
             LOGGER.info("Running bulk operation write tests");

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/test/java/org/apache/ignite/tests/load/PersonGenerator.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/test/java/org/apache/ignite/tests/load/PersonGenerator.java b/modules/cassandra/store/src/test/java/org/apache/ignite/tests/load/PersonGenerator.java
index 0317320..01c5c77 100644
--- a/modules/cassandra/store/src/test/java/org/apache/ignite/tests/load/PersonGenerator.java
+++ b/modules/cassandra/store/src/test/java/org/apache/ignite/tests/load/PersonGenerator.java
@@ -38,6 +38,6 @@ public class PersonGenerator implements Generator {
 
     /** {@inheritDoc} */
     @Override public Object generate(long i) {
-        return new Person(Long.toString(i), Long.toString(i), (int)(i % 100), i % 2 == 0, i, i, DATE, PHONES);
+        return new Person(i, Long.toString(i), Long.toString(i), (int)(i % 100), i % 2 == 0, i, i, DATE, PHONES);
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/test/java/org/apache/ignite/tests/pojos/Person.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/test/java/org/apache/ignite/tests/pojos/Person.java b/modules/cassandra/store/src/test/java/org/apache/ignite/tests/pojos/Person.java
index 8a1e623..16b64bd 100644
--- a/modules/cassandra/store/src/test/java/org/apache/ignite/tests/pojos/Person.java
+++ b/modules/cassandra/store/src/test/java/org/apache/ignite/tests/pojos/Person.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.tests.pojos;
 
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+
 import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
@@ -29,6 +31,9 @@ import java.util.List;
  */
 public class Person implements Externalizable {
     /** */
+    private long personNum;
+
+    /** */
     private String firstName;
 
     /** */
@@ -58,8 +63,9 @@ public class Person implements Externalizable {
     }
 
     /** */
-    public Person(String firstName, String lastName, int age, boolean married,
+    public Person(long personNum, String firstName, String lastName, int age, boolean married,
         long height, float weight, Date birthDate, List<String> phones) {
+        this.personNum = personNum;
         this.firstName = firstName;
         this.lastName = lastName;
         this.age = age;
@@ -73,6 +79,7 @@ public class Person implements Externalizable {
 
     /** {@inheritDoc} */
     @Override public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeLong(personNum);
         out.writeObject(firstName);
         out.writeObject(lastName);
         out.writeInt(age);
@@ -86,6 +93,7 @@ public class Person implements Externalizable {
     /** {@inheritDoc} */
     @SuppressWarnings("unchecked")
     @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        personNum = in.readLong();
         firstName = (String)in.readObject();
         lastName = (String)in.readObject();
         age = in.readInt();
@@ -104,6 +112,9 @@ public class Person implements Externalizable {
 
         Person person = (Person)obj;
 
+        if (personNum != person.personNum)
+            return false;
+
         if ((firstName != null && !firstName.equals(person.firstName)) ||
             (person.firstName != null && !person.firstName.equals(firstName)))
             return false;
@@ -132,6 +143,9 @@ public class Person implements Externalizable {
 
         Person person = (Person)obj;
 
+        if (personNum != person.personNum)
+            return false;
+
         if ((firstName != null && !firstName.equals(person.firstName)) ||
             (person.firstName != null && !person.firstName.equals(firstName)))
             return false;
@@ -150,6 +164,18 @@ public class Person implements Externalizable {
 
     /** */
     @SuppressWarnings("UnusedDeclaration")
+    public void setPersonNumber(long personNum) {
+        this.personNum = personNum;
+    }
+
+    /** */
+    @SuppressWarnings("UnusedDeclaration")
+    public long getPersonNumber() {
+        return personNum;
+    }
+
+    /** */
+    @SuppressWarnings("UnusedDeclaration")
     public void setFirstName(String name) {
         firstName = name;
     }
@@ -174,6 +200,13 @@ public class Person implements Externalizable {
 
     /** */
     @SuppressWarnings("UnusedDeclaration")
+    @QuerySqlField
+    public String getFullName() {
+        return firstName + " " + lastName;
+    }
+
+    /** */
+    @SuppressWarnings("UnusedDeclaration")
     public void setAge(int age) {
         this.age = age;
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/test/java/org/apache/ignite/tests/utils/TestsHelper.java
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/test/java/org/apache/ignite/tests/utils/TestsHelper.java b/modules/cassandra/store/src/test/java/org/apache/ignite/tests/utils/TestsHelper.java
index 0bbda7f..2e266f6 100644
--- a/modules/cassandra/store/src/test/java/org/apache/ignite/tests/utils/TestsHelper.java
+++ b/modules/cassandra/store/src/test/java/org/apache/ignite/tests/utils/TestsHelper.java
@@ -223,7 +223,7 @@ public class TestsHelper {
         Map<Long, Person> map = new HashMap<>();
 
         for (long i = 0; i < BULK_OPERATION_SIZE; i++)
-            map.put(i, generateRandomPerson());
+            map.put(i, generateRandomPerson(i));
 
         return map;
     }
@@ -233,7 +233,7 @@ public class TestsHelper {
         Collection<CacheEntryImpl<Long, Person>> entries = new LinkedList<>();
 
         for (long i = 0; i < BULK_OPERATION_SIZE; i++)
-            entries.add(new CacheEntryImpl<>(i, generateRandomPerson()));
+            entries.add(new CacheEntryImpl<>(i, generateRandomPerson(i)));
 
         return entries;
     }
@@ -247,8 +247,11 @@ public class TestsHelper {
     public static Map<PersonId, Person> generatePersonIdsPersonsMap(int cnt) {
         Map<PersonId, Person> map = new HashMap<>();
 
-        for (int i = 0; i < cnt; i++)
-            map.put(generateRandomPersonId(), generateRandomPerson());
+        for (int i = 0; i < cnt; i++) {
+            PersonId id = generateRandomPersonId();
+
+            map.put(id, generateRandomPerson(id.getPersonNumber()));
+        }
 
         return map;
     }
@@ -262,14 +265,17 @@ public class TestsHelper {
     public static Collection<CacheEntryImpl<PersonId, Person>> generatePersonIdsPersonsEntries(int cnt) {
         Collection<CacheEntryImpl<PersonId, Person>> entries = new LinkedList<>();
 
-        for (int i = 0; i < cnt; i++)
-            entries.add(new CacheEntryImpl<>(generateRandomPersonId(), generateRandomPerson()));
+        for (int i = 0; i < cnt; i++) {
+            PersonId id = generateRandomPersonId();
+
+            entries.add(new CacheEntryImpl<>(id, generateRandomPerson(id.getPersonNumber())));
+        }
 
         return entries;
     }
 
     /** */
-    public static Person generateRandomPerson() {
+    public static Person generateRandomPerson(long personNum) {
         int phonesCnt = RANDOM.nextInt(4);
 
         List<String> phones = new LinkedList<>();
@@ -277,7 +283,7 @@ public class TestsHelper {
         for (int i = 0; i < phonesCnt; i++)
             phones.add(randomNumber(4));
 
-        return new Person(randomString(4), randomString(4), RANDOM.nextInt(100),
+        return new Person(personNum, randomString(4), randomString(4), RANDOM.nextInt(100),
             RANDOM.nextBoolean(), RANDOM.nextLong(), RANDOM.nextFloat(), new Date(), phones);
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/5281a0fe/modules/cassandra/store/src/test/resources/org/apache/ignite/tests/persistence/pojo/persistence-settings-3.xml
----------------------------------------------------------------------
diff --git a/modules/cassandra/store/src/test/resources/org/apache/ignite/tests/persistence/pojo/persistence-settings-3.xml b/modules/cassandra/store/src/test/resources/org/apache/ignite/tests/persistence/pojo/persistence-settings-3.xml
index b3dc4e6..f602508 100644
--- a/modules/cassandra/store/src/test/resources/org/apache/ignite/tests/persistence/pojo/persistence-settings-3.xml
+++ b/modules/cassandra/store/src/test/resources/org/apache/ignite/tests/persistence/pojo/persistence-settings-3.xml
@@ -161,8 +161,10 @@ Attributes:
            5) indexClass   [optional] - custom index java class name, in case you want to use custom index
            6) indexOptions [optional] - custom index options
         -->
+        <field name="personNumber" column="number" />
         <field name="firstName" column="first_name" />
         <field name="lastName" column="last_name" />
+        <field name="fullName" />
         <field name="age" />
         <field name="married" index="true"/>
         <field name="height" />