You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by is...@apache.org on 2020/12/25 13:04:51 UTC

[ignite] branch master updated: IGNITE-13908: ODBC nullability info for columns

This is an automated email from the ASF dual-hosted git repository.

isapego pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new eb77c8a  IGNITE-13908: ODBC nullability info for columns
eb77c8a is described below

commit eb77c8af710995237e69c8946e2fc5fa20d7c7a1
Author: Igor Sapego <is...@apache.org>
AuthorDate: Fri Dec 25 16:03:49 2020 +0300

    IGNITE-13908: ODBC nullability info for columns
    
    This closes #8610
---
 .../processors/odbc/odbc/OdbcColumnMeta.java       | 39 ++++++++++---------
 .../odbc/odbc/OdbcConnectionContext.java           |  6 ++-
 .../processors/odbc/odbc/OdbcMessageParser.java    |  4 +-
 .../processors/odbc/odbc/OdbcRequestHandler.java   |  7 +++-
 .../processors/odbc/odbc/OdbcResultSet.java        |  2 +-
 .../internal/processors/odbc/odbc/OdbcUtils.java   |  7 +---
 .../processors/query/GridQueryFieldMetadata.java   |  9 +++++
 .../processors/query/h2/H2SqlFieldMetadata.java    | 11 +++++-
 .../internal/processors/query/h2/H2Utils.java      |  7 +++-
 .../cpp/odbc-test/src/attributes_test.cpp          | 24 ++++++++++++
 .../cpp/odbc-test/src/meta_queries_test.cpp        | 44 +++++++++++-----------
 .../odbc/include/ignite/odbc/meta/column_meta.h    | 41 +++++++++++++++++++-
 .../odbc/include/ignite/odbc/protocol_version.h    |  3 ++
 .../platforms/cpp/odbc/src/meta/column_meta.cpp    | 28 +++++++++++++-
 .../platforms/cpp/odbc/src/protocol_version.cpp    |  6 ++-
 15 files changed, 179 insertions(+), 59 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcColumnMeta.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcColumnMeta.java
index a8f749e..58ae98f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcColumnMeta.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcColumnMeta.java
@@ -35,19 +35,19 @@ public class OdbcColumnMeta {
     private final String tableName;
 
     /** Column name. */
-    public final String columnName;
+    private final String columnName;
 
     /** Data type. */
     private final Class<?> dataType;
 
     /** Precision. */
-    public final int precision;
+    private final int precision;
 
     /** Scale. */
-    public final int scale;
+    private final int scale;
 
-    /** Client version. */
-    private final ClientListenerProtocolVersion ver;
+    /** Nullability. See {@link java.sql.ResultSetMetaData#isNullable(int)} for more info. */
+    private final int nullability;
 
     /**
      * @param schemaName Cache name.
@@ -56,30 +56,30 @@ public class OdbcColumnMeta {
      * @param dataType Data type.
      * @param precision Precision.
      * @param scale Scale.
-     * @param ver Client version.
+     * @param nullability Nullability.
      */
     public OdbcColumnMeta(String schemaName, String tableName, String columnName, Class<?> dataType,
-        int precision, int scale, ClientListenerProtocolVersion ver) {
+        int precision, int scale, int nullability) {
         this.schemaName = OdbcUtils.addQuotationMarksIfNeeded(schemaName);
         this.tableName = tableName;
         this.columnName = columnName;
         this.dataType = dataType;
         this.precision = precision;
         this.scale = scale;
-        this.ver = ver;
+        this.nullability = nullability;
     }
 
     /**
+     * Constructor for result set column.
      * @param info Field metadata.
-     * @param ver Client version.
      */
-    public OdbcColumnMeta(GridQueryFieldMetadata info, ClientListenerProtocolVersion ver) {
-        this.schemaName = OdbcUtils.addQuotationMarksIfNeeded(info.schemaName());
-        this.tableName = info.typeName();
-        this.columnName = info.fieldName();
-        this.precision = info.precision();
-        this.scale = info.scale();
-        this.ver = ver;
+    public OdbcColumnMeta(GridQueryFieldMetadata info) {
+        schemaName = OdbcUtils.addQuotationMarksIfNeeded(info.schemaName());
+        tableName = info.typeName();
+        columnName = info.fieldName();
+        precision = info.precision();
+        scale = info.scale();
+        nullability = info.nullability();
 
         Class<?> type;
 
@@ -123,8 +123,9 @@ public class OdbcColumnMeta {
      * Write in a binary format.
      *
      * @param writer Binary writer.
+     * @param ver Client version.
      */
-    public void write(BinaryRawWriter writer) {
+    public void write(BinaryRawWriter writer, ClientListenerProtocolVersion ver) {
         writer.writeString(schemaName);
         writer.writeString(tableName);
         writer.writeString(columnName);
@@ -137,6 +138,10 @@ public class OdbcColumnMeta {
             writer.writeInt(precision);
             writer.writeInt(scale);
         }
+
+        if (ver.compareTo(OdbcConnectionContext.VER_2_8_0) >= 0) {
+            writer.writeByte((byte)nullability);
+        }
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
index 74def0d..5b78b6e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
@@ -56,8 +56,11 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte
     /** Version 2.7.0: added precision and scale. */
     public static final ClientListenerProtocolVersion VER_2_7_0 = ClientListenerProtocolVersion.create(2, 7, 0);
 
+    /** Version 2.8.0: added column nullability info. */
+    public static final ClientListenerProtocolVersion VER_2_8_0 = ClientListenerProtocolVersion.create(2, 8, 0);
+
     /** Current version. */
-    private static final ClientListenerProtocolVersion CURRENT_VER = VER_2_7_0;
+    private static final ClientListenerProtocolVersion CURRENT_VER = VER_2_8_0;
 
     /** Supported versions. */
     private static final Set<ClientListenerProtocolVersion> SUPPORTED_VERS = new HashSet<>();
@@ -79,6 +82,7 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte
 
     static {
         SUPPORTED_VERS.add(CURRENT_VER);
+        SUPPORTED_VERS.add(VER_2_7_0);
         SUPPORTED_VERS.add(VER_2_5_0);
         SUPPORTED_VERS.add(VER_2_3_0);
         SUPPORTED_VERS.add(VER_2_3_2);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcMessageParser.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcMessageParser.java
index 33f1c4b..2d0d2d4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcMessageParser.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcMessageParser.java
@@ -419,13 +419,13 @@ public class OdbcMessageParser implements ClientListenerMessageParser {
      * @param writer Writer.
      * @param meta Metadata
      */
-    private static void writeResultsetMeta(BinaryWriterExImpl writer, Collection<OdbcColumnMeta> meta) {
+    private void writeResultsetMeta(BinaryWriterExImpl writer, Collection<OdbcColumnMeta> meta) {
         assert meta != null;
 
         writer.writeInt(meta.size());
 
         for (OdbcColumnMeta columnMeta : meta)
-            columnMeta.write(writer);
+            columnMeta.write(writer, ver);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcRequestHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcRequestHandler.java
index a21caeb..8cc24a1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcRequestHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcRequestHandler.java
@@ -69,6 +69,8 @@ import org.apache.ignite.transactions.TransactionMixedModeException;
 import org.apache.ignite.transactions.TransactionSerializationException;
 import org.apache.ignite.transactions.TransactionUnsupportedConcurrencyException;
 
+import static java.sql.ResultSetMetaData.columnNoNulls;
+import static java.sql.ResultSetMetaData.columnNullable;
 import static org.apache.ignite.internal.processors.odbc.odbc.OdbcRequest.META_COLS;
 import static org.apache.ignite.internal.processors.odbc.odbc.OdbcRequest.META_PARAMS;
 import static org.apache.ignite.internal.processors.odbc.odbc.OdbcRequest.META_RESULTSET;
@@ -696,7 +698,8 @@ public class OdbcRequestHandler implements ClientListenerRequestHandler {
                         GridQueryProperty prop = table.property(field.getKey());
 
                         OdbcColumnMeta columnMeta = new OdbcColumnMeta(table.schemaName(), table.tableName(),
-                            field.getKey(), field.getValue(), prop.precision(), prop.scale(), ver);
+                            field.getKey(), field.getValue(), prop.precision(), prop.scale(),
+                            prop.notNull() ? columnNoNulls : columnNullable);
 
                         if (!meta.contains(columnMeta))
                             meta.add(columnMeta);
@@ -802,7 +805,7 @@ public class OdbcRequestHandler implements ClientListenerRequestHandler {
             SqlFieldsQueryEx qry = makeQuery(schema, sql);
 
             List<GridQueryFieldMetadata> columns = ctx.query().getIndexing().resultMetaData(schema, qry);
-            Collection<OdbcColumnMeta> meta = OdbcUtils.convertMetadata(columns, ver);
+            Collection<OdbcColumnMeta> meta = OdbcUtils.convertMetadata(columns);
 
             OdbcQueryGetResultsetMetaResult res = new OdbcQueryGetResultsetMetaResult(meta);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcResultSet.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcResultSet.java
index 1eba63d..879acde 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcResultSet.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcResultSet.java
@@ -54,7 +54,7 @@ public class OdbcResultSet {
 
         if (this.cursor.isQuery()) {
             iter = this.cursor.iterator();
-            meta = OdbcUtils.convertMetadata(this.cursor.fieldsMeta(), ver);
+            meta = OdbcUtils.convertMetadata(this.cursor.fieldsMeta());
         }
         else {
             iter = null;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcUtils.java
index 80fab2e..926524a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcUtils.java
@@ -25,7 +25,6 @@ import org.apache.ignite.IgniteException;
 import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.internal.processors.cache.QueryCursorImpl;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
-import org.apache.ignite.internal.processors.odbc.ClientListenerProtocolVersion;
 import org.apache.ignite.internal.processors.odbc.SqlListenerDataTypes;
 import org.apache.ignite.internal.processors.odbc.SqlListenerUtils;
 import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
@@ -252,16 +251,14 @@ public class OdbcUtils {
      * {@link OdbcColumnMeta}.
      *
      * @param meta Internal query field metadata.
-     * @param ver Client version.
      * @return Odbc query field metadata.
      */
-    public static Collection<OdbcColumnMeta> convertMetadata(Collection<GridQueryFieldMetadata> meta,
-        ClientListenerProtocolVersion ver) {
+    public static Collection<OdbcColumnMeta> convertMetadata(Collection<GridQueryFieldMetadata> meta) {
         List<OdbcColumnMeta> res = new ArrayList<>();
 
         if (meta != null) {
             for (GridQueryFieldMetadata info : meta)
-                res.add(new OdbcColumnMeta(info, ver));
+                res.add(new OdbcColumnMeta(info));
         }
 
         return res;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryFieldMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryFieldMetadata.java
index f9df499..3c28e38 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryFieldMetadata.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryFieldMetadata.java
@@ -65,4 +65,13 @@ public interface GridQueryFieldMetadata extends Externalizable {
      * @return Field scale.
      */
     public int scale();
+
+    /**
+     * Gets field nullability.
+     *
+     * See {@link java.sql.ResultSetMetaData#isNullable(int)} for more info.
+     *
+     * @return Field nullability.
+     */
+    public int nullability();
 }
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2SqlFieldMetadata.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2SqlFieldMetadata.java
index a42771e..08cf1b3 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2SqlFieldMetadata.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2SqlFieldMetadata.java
@@ -51,6 +51,9 @@ public class H2SqlFieldMetadata implements GridQueryFieldMetadata {
     /** Scale. */
     private int scale;
 
+    /** Nullability. See {@link java.sql.ResultSetMetaData#isNullable(int)} */
+    private int nullability;
+
     /**
      * Required by {@link Externalizable}.
      */
@@ -67,7 +70,7 @@ public class H2SqlFieldMetadata implements GridQueryFieldMetadata {
      * @param scale Scale.
      */
     H2SqlFieldMetadata(@Nullable String schemaName, @Nullable String typeName, String name, String type,
-        int precision, int scale) {
+        int precision, int scale, int nullability) {
         assert name != null && type != null : schemaName + " | " + typeName + " | " + name + " | " + type;
 
         this.schemaName = schemaName;
@@ -76,6 +79,7 @@ public class H2SqlFieldMetadata implements GridQueryFieldMetadata {
         this.type = type;
         this.precision = precision;
         this.scale = scale;
+        this.nullability = nullability;
     }
 
     /** {@inheritDoc} */
@@ -109,6 +113,11 @@ public class H2SqlFieldMetadata implements GridQueryFieldMetadata {
     }
 
     /** {@inheritDoc} */
+    @Override public int nullability() {
+        return nullability;
+    }
+
+    /** {@inheritDoc} */
     @Override public void writeExternal(ObjectOutput out) throws IOException {
         U.writeString(out, schemaName);
         U.writeString(out, typeName);
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
index f766de9..e55e6ef 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
@@ -105,6 +105,7 @@ import org.h2.value.ValueUuid;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import static java.sql.ResultSetMetaData.columnNullableUnknown;
 import static org.apache.ignite.internal.processors.query.QueryUtils.KEY_COL;
 import static org.apache.ignite.internal.processors.query.QueryUtils.KEY_FIELD_NAME;
 import static org.apache.ignite.internal.processors.query.QueryUtils.VAL_FIELD_NAME;
@@ -136,7 +137,8 @@ public class H2Utils {
 
     /** Dummy metadata for update result. */
     public static final List<GridQueryFieldMetadata> UPDATE_RESULT_META =
-        Collections.singletonList(new H2SqlFieldMetadata(null, null, "UPDATED", Long.class.getName(), -1, -1));
+        Collections.singletonList(new H2SqlFieldMetadata(null, null, "UPDATED", Long.class.getName(), -1, -1,
+                columnNullableUnknown));
 
     /** Spatial index class name. */
     private static final String SPATIAL_IDX_CLS =
@@ -375,11 +377,12 @@ public class H2Utils {
             String type = rsMeta.getColumnClassName(i);
             int precision = rsMeta.getPrecision(i);
             int scale = rsMeta.getScale(i);
+            int nullability = rsMeta.isNullable(i);
 
             if (type == null) // Expression always returns NULL.
                 type = Void.class.getName();
 
-            meta.add(new H2SqlFieldMetadata(schemaName, typeName, name, type, precision, scale));
+            meta.add(new H2SqlFieldMetadata(schemaName, typeName, name, type, precision, scale, nullability));
         }
 
         return meta;
diff --git a/modules/platforms/cpp/odbc-test/src/attributes_test.cpp b/modules/platforms/cpp/odbc-test/src/attributes_test.cpp
index 278a066..8d6f6e3 100644
--- a/modules/platforms/cpp/odbc-test/src/attributes_test.cpp
+++ b/modules/platforms/cpp/odbc-test/src/attributes_test.cpp
@@ -116,6 +116,30 @@ BOOST_AUTO_TEST_CASE(TestConnectionProtocolVersion_2_3_2)
     InsertTestBatch(11, 20, 9);
 }
 
+BOOST_AUTO_TEST_CASE(TestConnectionProtocolVersion_2_5_0)
+{
+    Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110;SCHEMA=cache;PROTOCOL_VERSION=2.5.0");
+
+    InsertTestStrings(10, false);
+    InsertTestBatch(11, 20, 9);
+}
+
+BOOST_AUTO_TEST_CASE(TestConnectionProtocolVersion_2_7_0)
+{
+    Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110;SCHEMA=cache;PROTOCOL_VERSION=2.7.0");
+
+    InsertTestStrings(10, false);
+    InsertTestBatch(11, 20, 9);
+}
+
+BOOST_AUTO_TEST_CASE(TestConnectionProtocolVersion_2_8_0)
+{
+    Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110;SCHEMA=cache;PROTOCOL_VERSION=2.8.0");
+
+    InsertTestStrings(10, false);
+    InsertTestBatch(11, 20, 9);
+}
+
 BOOST_AUTO_TEST_CASE(TestConnectionRangeBegin)
 {
     Connect("DRIVER={Apache Ignite};ADDRESS=127.0.0.1:11110..11115;SCHEMA=cache");
diff --git a/modules/platforms/cpp/odbc-test/src/meta_queries_test.cpp b/modules/platforms/cpp/odbc-test/src/meta_queries_test.cpp
index 05a0e46..696c878 100644
--- a/modules/platforms/cpp/odbc-test/src/meta_queries_test.cpp
+++ b/modules/platforms/cpp/odbc-test/src/meta_queries_test.cpp
@@ -189,7 +189,7 @@ struct MetaQueriesTestSuiteFixture : public odbc::OdbcTestSuite
             "   dec3 decimal,"
             "   char1 char(3),"
             "   char2 char(42),"
-            "   char3 char,"
+            "   char3 char not null,"
             "   vchar varchar"
             ")");
 
@@ -218,14 +218,14 @@ struct MetaQueriesTestSuiteFixture : public odbc::OdbcTestSuite
 
         BOOST_CHECK_EQUAL(columnCount, 8);
 
-        CheckColumnMetaWithSQLDescribeCol(stmt, 1, "ID", SQL_INTEGER, 10, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLDescribeCol(stmt, 2, "DEC1", SQL_DECIMAL, 3, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLDescribeCol(stmt, 3, "DEC2", SQL_DECIMAL, 42, 12, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLDescribeCol(stmt, 4, "DEC3", SQL_DECIMAL, 65535, 32767, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLDescribeCol(stmt, 5, "CHAR1", SQL_VARCHAR, 3, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLDescribeCol(stmt, 6, "CHAR2", SQL_VARCHAR, 42, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLDescribeCol(stmt, 7, "CHAR3", SQL_VARCHAR, 2147483647, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLDescribeCol(stmt, 8, "VCHAR", SQL_VARCHAR, 2147483647, 0, SQL_NULLABLE_UNKNOWN);
+        CheckColumnMetaWithSQLDescribeCol(stmt, 1, "ID", SQL_INTEGER, 10, 0, SQL_NULLABLE);
+        CheckColumnMetaWithSQLDescribeCol(stmt, 2, "DEC1", SQL_DECIMAL, 3, 0, SQL_NULLABLE);
+        CheckColumnMetaWithSQLDescribeCol(stmt, 3, "DEC2", SQL_DECIMAL, 42, 12, SQL_NULLABLE);
+        CheckColumnMetaWithSQLDescribeCol(stmt, 4, "DEC3", SQL_DECIMAL, 65535, 32767, SQL_NULLABLE);
+        CheckColumnMetaWithSQLDescribeCol(stmt, 5, "CHAR1", SQL_VARCHAR, 3, 0, SQL_NULLABLE);
+        CheckColumnMetaWithSQLDescribeCol(stmt, 6, "CHAR2", SQL_VARCHAR, 42, 0, SQL_NULLABLE);
+        CheckColumnMetaWithSQLDescribeCol(stmt, 7, "CHAR3", SQL_VARCHAR, 2147483647, 0, SQL_NO_NULLS);
+        CheckColumnMetaWithSQLDescribeCol(stmt, 8, "VCHAR", SQL_VARCHAR, 2147483647, 0, SQL_NULLABLE);
     }
 
     /**
@@ -298,7 +298,7 @@ struct MetaQueriesTestSuiteFixture : public odbc::OdbcTestSuite
             "   dec3 decimal,"
             "   char1 char(3),"
             "   char2 char(42),"
-            "   char3 char,"
+            "   char3 char not null,"
             "   vchar varchar"
             ")");
 
@@ -327,14 +327,14 @@ struct MetaQueriesTestSuiteFixture : public odbc::OdbcTestSuite
 
         BOOST_CHECK_EQUAL(columnCount, 8);
 
-        CheckColumnMetaWithSQLColAttribute(stmt, 1, "ID", SQL_INTEGER, 10, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLColAttribute(stmt, 2, "DEC1", SQL_DECIMAL, 3, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLColAttribute(stmt, 3, "DEC2", SQL_DECIMAL, 42, 12, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLColAttribute(stmt, 4, "DEC3", SQL_DECIMAL, 65535, 32767, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLColAttribute(stmt, 5, "CHAR1", SQL_VARCHAR, 3, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLColAttribute(stmt, 6, "CHAR2", SQL_VARCHAR, 42, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLColAttribute(stmt, 7, "CHAR3", SQL_VARCHAR, 2147483647, 0, SQL_NULLABLE_UNKNOWN);
-        CheckColumnMetaWithSQLColAttribute(stmt, 8, "VCHAR", SQL_VARCHAR, 2147483647, 0, SQL_NULLABLE_UNKNOWN);
+        CheckColumnMetaWithSQLColAttribute(stmt, 1, "ID", SQL_INTEGER, 10, 0, SQL_NULLABLE);
+        CheckColumnMetaWithSQLColAttribute(stmt, 2, "DEC1", SQL_DECIMAL, 3, 0, SQL_NULLABLE);
+        CheckColumnMetaWithSQLColAttribute(stmt, 3, "DEC2", SQL_DECIMAL, 42, 12, SQL_NULLABLE);
+        CheckColumnMetaWithSQLColAttribute(stmt, 4, "DEC3", SQL_DECIMAL, 65535, 32767, SQL_NULLABLE);
+        CheckColumnMetaWithSQLColAttribute(stmt, 5, "CHAR1", SQL_VARCHAR, 3, 0, SQL_NULLABLE);
+        CheckColumnMetaWithSQLColAttribute(stmt, 6, "CHAR2", SQL_VARCHAR, 42, 0, SQL_NULLABLE);
+        CheckColumnMetaWithSQLColAttribute(stmt, 7, "CHAR3", SQL_VARCHAR, 2147483647, 0, SQL_NO_NULLS);
+        CheckColumnMetaWithSQLColAttribute(stmt, 8, "VCHAR", SQL_VARCHAR, 2147483647, 0, SQL_NULLABLE);
     }
 
     /**
@@ -883,7 +883,7 @@ BOOST_AUTO_TEST_CASE(TestSQLNumResultColsAfterSQLPrepare)
  * 2. Connect to node using ODBC.
  * 3. Create table with decimal and char columns with specified size and scale.
  * 4. Prepare statement.
- * 5. Check presicion and scale of every column using SQLDescribeCol.
+ * 5. Check precision and scale of every column using SQLDescribeCol.
  */
 BOOST_AUTO_TEST_CASE(TestSQLDescribeColPrecisionAndScaleAfterPrepare)
 {
@@ -897,7 +897,7 @@ BOOST_AUTO_TEST_CASE(TestSQLDescribeColPrecisionAndScaleAfterPrepare)
  * 2. Connect to node using ODBC.
  * 3. Create table with decimal and char columns with specified size and scale.
  * 4. Execute statement.
- * 5. Check presicion and scale of every column using SQLDescribeCol. */
+ * 5. Check precision and scale of every column using SQLDescribeCol. */
 BOOST_AUTO_TEST_CASE(TestSQLDescribeColPrecisionAndScaleAfterExec)
 {
     CheckSQLDescribeColPrecisionAndScale(&OdbcTestSuite::ExecQuery);
@@ -910,7 +910,7 @@ BOOST_AUTO_TEST_CASE(TestSQLDescribeColPrecisionAndScaleAfterExec)
  * 2. Connect to node using ODBC.
  * 3. Create table with decimal and char columns with specified size and scale.
  * 4. Prepare statement.
- * 5. Check presicion and scale of every column using SQLColAttribute.
+ * 5. Check precision and scale of every column using SQLColAttribute.
  */
 BOOST_AUTO_TEST_CASE(TestSQLColAttributePrecisionAndScaleAfterPrepare)
 {
@@ -924,7 +924,7 @@ BOOST_AUTO_TEST_CASE(TestSQLColAttributePrecisionAndScaleAfterPrepare)
  * 2. Connect to node using ODBC.
  * 3. Create table with decimal and char columns with specified size and scale.
  * 4. Execute statement.
- * 5. Check presicion and scale of every column using SQLColAttribute. */
+ * 5. Check precision and scale of every column using SQLColAttribute. */
 BOOST_AUTO_TEST_CASE(TestSQLColAttributePrecisionAndScaleAfterExec)
 {
     CheckSQLColAttributePrecisionAndScale(&OdbcTestSuite::ExecQuery);
diff --git a/modules/platforms/cpp/odbc/include/ignite/odbc/meta/column_meta.h b/modules/platforms/cpp/odbc/include/ignite/odbc/meta/column_meta.h
index c4aeae0..5144fa3 100644
--- a/modules/platforms/cpp/odbc/include/ignite/odbc/meta/column_meta.h
+++ b/modules/platforms/cpp/odbc/include/ignite/odbc/meta/column_meta.h
@@ -33,6 +33,29 @@ namespace ignite
     {
         namespace meta
         {
+            /**
+             * Nullability type.
+             */
+            struct Nullability
+            {
+                enum Type
+                {
+                    NO_NULL = 0,
+
+                    NULLABLE = 1,
+
+                    NULLABILITY_UNKNOWN = 2
+                };
+
+                /**
+                 * Convert to SQL constant.
+                 *
+                 * @param nullability Nullability.
+                 * @return SQL constant.
+                 */
+                static SqlLen ToSql(int32_t nullability);
+            };
+
             using namespace ignite::odbc;
 
             /**
@@ -69,7 +92,7 @@ namespace ignite
                 ColumnMeta(const std::string& schemaName, const std::string& tableName,
                            const std::string& columnName, int8_t dataType) :
                     schemaName(schemaName), tableName(tableName), columnName(columnName), dataType(dataType),
-                    precision(-1), scale(-1)
+                    precision(-1), scale(-1), nullability(Nullability::NULLABILITY_UNKNOWN)
                 {
                     // No-op.
                 }
@@ -91,7 +114,8 @@ namespace ignite
                     columnName(other.columnName),
                     dataType(other.dataType),
                     precision(other.precision),
-                    scale(other.scale)
+                    scale(other.scale),
+                    nullability(other.nullability)
                 {
                     // No-op.
                 }
@@ -107,6 +131,7 @@ namespace ignite
                     dataType = other.dataType;
                     precision = other.precision;
                     scale = other.scale;
+                    nullability = other.nullability;
 
                     return *this;
                 }
@@ -173,6 +198,15 @@ namespace ignite
                 }
 
                 /**
+                 * Get column nullability.
+                 * @return Column nullability.
+                 */
+                int32_t GetNullability() const
+                {
+                    return nullability;
+                }
+
+                /**
                  * Try to get attribute of a string type.
                  *
                  * @param fieldId Field ID.
@@ -208,6 +242,9 @@ namespace ignite
 
                 /** Column scale. */
                 int32_t scale;
+
+                /** Column nullability. */
+                int32_t nullability;
             };
 
             /** Column metadata vector alias. */
diff --git a/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h b/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h
index 1a722e3..f4c0e92 100644
--- a/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h
+++ b/modules/platforms/cpp/odbc/include/ignite/odbc/protocol_version.h
@@ -49,6 +49,9 @@ namespace ignite
             /** Version 2.7.0: added fields precision and scale. */
             static const ProtocolVersion VERSION_2_7_0;
 
+            /** Version 2.8.0: added column nullability info. */
+            static const ProtocolVersion VERSION_2_8_0;
+
             typedef std::set<ProtocolVersion> VersionSet;
 
             /**
diff --git a/modules/platforms/cpp/odbc/src/meta/column_meta.cpp b/modules/platforms/cpp/odbc/src/meta/column_meta.cpp
index 203b4ad..0270888 100644
--- a/modules/platforms/cpp/odbc/src/meta/column_meta.cpp
+++ b/modules/platforms/cpp/odbc/src/meta/column_meta.cpp
@@ -74,6 +74,27 @@ namespace ignite
 
 #undef DBG_STR_CASE
 
+            SqlLen Nullability::ToSql(int32_t nullability)
+            {
+                switch (nullability)
+                {
+                    case Nullability::NO_NULL:
+                        return SQL_NO_NULLS;
+
+                    case Nullability::NULLABLE:
+                        return SQL_NULLABLE;
+
+                    case Nullability::NULLABILITY_UNKNOWN:
+                        return SQL_NULLABLE_UNKNOWN;
+
+                    default:
+                        break;
+                }
+
+                assert(false);
+                return SQL_NULLABLE_UNKNOWN;
+            }
+
             void ColumnMeta::Read(ignite::impl::binary::BinaryReaderImpl& reader, const ProtocolVersion& ver)
             {
                 utility::ReadString(reader, schemaName);
@@ -87,6 +108,9 @@ namespace ignite
                     precision = reader.ReadInt32();
                     scale = reader.ReadInt32();
                 }
+
+                if (ver >= ProtocolVersion::VERSION_2_8_0)
+                    nullability = reader.ReadInt8();
             }
 
             bool ColumnMeta::GetAttribute(uint16_t fieldId, std::string& value) const 
@@ -235,7 +259,7 @@ namespace ignite
 
                     case SQL_DESC_NULLABLE:
                     {
-                        value = type_traits::BinaryTypeNullability(dataType);
+                        value = Nullability::ToSql(nullability);
 
                         break;
                     }
@@ -312,7 +336,7 @@ namespace ignite
             }
 
             void ReadColumnMetaVector(ignite::impl::binary::BinaryReaderImpl& reader, ColumnMetaVector& meta,
-                    const ProtocolVersion& ver)
+                const ProtocolVersion& ver)
             {
                 int32_t metaNum = reader.ReadInt32();
 
diff --git a/modules/platforms/cpp/odbc/src/protocol_version.cpp b/modules/platforms/cpp/odbc/src/protocol_version.cpp
index 01937e2..a073ab2 100644
--- a/modules/platforms/cpp/odbc/src/protocol_version.cpp
+++ b/modules/platforms/cpp/odbc/src/protocol_version.cpp
@@ -32,6 +32,7 @@ namespace ignite
         const ProtocolVersion ProtocolVersion::VERSION_2_3_2(2, 3, 2);
         const ProtocolVersion ProtocolVersion::VERSION_2_5_0(2, 5, 0);
         const ProtocolVersion ProtocolVersion::VERSION_2_7_0(2, 7, 0);
+        const ProtocolVersion ProtocolVersion::VERSION_2_8_0(2, 8, 0);
 
         ProtocolVersion::VersionSet::value_type supportedArray[] = {
             ProtocolVersion::VERSION_2_1_0,
@@ -39,7 +40,8 @@ namespace ignite
             ProtocolVersion::VERSION_2_3_0,
             ProtocolVersion::VERSION_2_3_2,
             ProtocolVersion::VERSION_2_5_0,
-            ProtocolVersion::VERSION_2_7_0
+            ProtocolVersion::VERSION_2_7_0,
+            ProtocolVersion::VERSION_2_8_0
         };
 
         const ProtocolVersion::VersionSet ProtocolVersion::supported(supportedArray,
@@ -68,7 +70,7 @@ namespace ignite
 
         const ProtocolVersion& ProtocolVersion::GetCurrent()
         {
-            return VERSION_2_7_0;
+            return VERSION_2_8_0;
         }
 
         void ThrowParseError()