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

sergeykamov pushed a commit to branch NLPCRAFT-319
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git

commit 69f665a207b18e8d4f3b2b2b8b6b7bbbc6b1c888
Author: Sergey Kamov <skhdlem...@gmail.com>
AuthorDate: Mon May 17 19:05:45 2021 +0300

    WIP.
---
 nlpcraft/src/main/resources/sql/create_schema.sql  |  27 +-
 nlpcraft/src/main/resources/sql/drop_schema.sql    |   2 -
 .../org/apache/nlpcraft/common/util/NCUtils.scala  |  44 +++-
 .../model/tools/embedded/NCEmbeddedResult.java     |   5 +
 .../model/tools/test/NCTestClientBuilder.java      |   1 +
 .../probe/mgrs/nlp/NCProbeEnrichmentManager.scala  |  13 +
 .../probe/mgrs/nlp/impl/NCRequestImpl.scala        |  15 +-
 .../nlpcraft/server/company/NCCompanyManager.scala |  22 +-
 .../apache/nlpcraft/server/mdo/NCCompanyMdo.scala  |   1 +
 .../nlpcraft/server/mdo/NCCompanyPropertyMdo.scala |  41 ---
 .../nlpcraft/server/mdo/NCQueryStateMdo.scala      |   4 +-
 .../org/apache/nlpcraft/server/mdo/NCUserMdo.scala |   7 +-
 .../nlpcraft/server/mdo/NCUserPropertyMdo.scala    |  41 ---
 .../nlpcraft/server/probe/NCProbeManager.scala     |  28 +-
 .../server/proclog/NCProcessLogManager.scala       |   3 +
 .../nlpcraft/server/query/NCQueryManager.scala     |  29 ++-
 .../nlpcraft/server/rest/NCBasicRestApi.scala      | 191 ++++++++------
 .../apache/nlpcraft/server/sql/NCSqlManager.scala  | 284 ++++++++-------------
 .../nlpcraft/server/user/NCUserManager.scala       |  22 +-
 .../apache/nlpcraft/server/rest/NCRestSpec.scala   |   3 +
 openapi/nlpcraft_swagger.yml                       |   8 +-
 sql/mysql/drop_schema.sql                          |   2 -
 sql/mysql/schema.sql                               |  29 +--
 sql/oracle/drop_schema.sql                         |  16 --
 sql/oracle/schema.sql                              |  33 +--
 sql/postgres/drop_schema.sql                       |   2 -
 sql/postgres/schema.sql                            |  33 +--
 27 files changed, 335 insertions(+), 571 deletions(-)

diff --git a/nlpcraft/src/main/resources/sql/create_schema.sql 
b/nlpcraft/src/main/resources/sql/create_schema.sql
index 570e215..1965ca0 100644
--- a/nlpcraft/src/main/resources/sql/create_schema.sql
+++ b/nlpcraft/src/main/resources/sql/create_schema.sql
@@ -30,6 +30,7 @@ CREATE TABLE nc_company (
     postal_code VARCHAR,
     auth_token VARCHAR NOT NULL, -- Unique.
     auth_token_hash VARCHAR NOT NULL, -- Unique.
+    properties_gzip VARCHAR NULL,
     created_on TIMESTAMP NOT NULL,
     last_modified_on TIMESTAMP NOT NULL
 ) WITH "template=replicated, atomicity=transactional";
@@ -38,18 +39,6 @@ CREATE INDEX nc_company_idx_1 ON nc_company(name);
 CREATE INDEX nc_company_idx_2 ON nc_company(auth_token);
 CREATE INDEX nc_company_idx_3 ON nc_company(auth_token_hash);
 
-DROP TABLE IF EXISTS nc_company_property;
-CREATE TABLE nc_company_property (
-    id LONG PRIMARY KEY,
-    company_id LONG NOT NULL, -- Foreign key nc_company.id.
-    property VARCHAR NOT NULL,
-    value VARCHAR NULL,
-    created_on TIMESTAMP NOT NULL,
-    last_modified_on TIMESTAMP NOT NULL
-) WITH "template=replicated, atomicity=transactional";
-
-CREATE INDEX nc_company_property_idx_1 ON nc_company_property(company_id);
-
 DROP TABLE IF EXISTS nc_user;
 CREATE TABLE nc_user (
     id LONG PRIMARY KEY,
@@ -61,6 +50,7 @@ CREATE TABLE nc_user (
     last_name VARCHAR NULL,
     is_admin BOOL NOT NULL,
     passwd_salt VARCHAR NULL,
+    properties_gzip VARCHAR NULL,
     created_on TIMESTAMP NOT NULL,
     last_modified_on TIMESTAMP NOT NULL
 ) WITH "template=replicated, atomicity=transactional";
@@ -69,18 +59,6 @@ CREATE INDEX nc_user_idx_1 ON nc_user(email);
 CREATE INDEX nc_user_idx_2 ON nc_user(company_id, ext_id);
 CREATE INDEX nc_user_idx_3 ON nc_user(company_id);
 
-DROP TABLE IF EXISTS nc_user_property;
-CREATE TABLE nc_user_property (
-    id LONG PRIMARY KEY,
-    user_id LONG NOT NULL, -- Foreign key nc_user.id.
-    property VARCHAR NOT NULL,
-    value VARCHAR NULL,
-    created_on TIMESTAMP NOT NULL,
-    last_modified_on TIMESTAMP NOT NULL
-) WITH "template=replicated, atomicity=transactional";
-
-CREATE INDEX nc_user_property_idx_1 ON nc_user_property(user_id);
-
 DROP TABLE IF EXISTS passwd_pool;
 CREATE TABLE passwd_pool (
     id LONG PRIMARY KEY,
@@ -103,6 +81,7 @@ CREATE TABLE proc_log (
     cancel_tstamp TIMESTAMP NULL,
     res_type VARCHAR NULL,
     res_body_gzip VARCHAR NULL,
+    res_meta_gzip VARCHAR NULL,
     intent_id VARCHAR NULL,
     error VARCHAR NULL,
     probe_token VARCHAR NULL,
diff --git a/nlpcraft/src/main/resources/sql/drop_schema.sql 
b/nlpcraft/src/main/resources/sql/drop_schema.sql
index b1ce0b9..f985bae 100644
--- a/nlpcraft/src/main/resources/sql/drop_schema.sql
+++ b/nlpcraft/src/main/resources/sql/drop_schema.sql
@@ -16,9 +16,7 @@
 --
 
 DROP TABLE IF EXISTS proc_log;
-DROP TABLE IF EXISTS nc_user_property;
 DROP TABLE IF EXISTS nc_user;
-DROP TABLE IF EXISTS nc_company_property;
 DROP TABLE IF EXISTS nc_company;
 DROP TABLE IF EXISTS passwd_pool;
 DROP TABLE IF EXISTS feedback;
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
index 0248b56..2b3131a 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
@@ -1149,14 +1149,21 @@ object NCUtils extends LazyLogging {
       * @param rawStr String to compress.
       * @return Compressed Base64-encoded string.
       */
+    @throws[NCE]
     def compress(rawStr: String): String = {
         val arr = new ByteArrayOutputStream(1024)
 
-        managed(new GOS(arr)) acquireAndGet { zip ⇒
-            zip.write(rawStr.getBytes)
-        }
+        try {
+            managed(new GOS(arr)) acquireAndGet { zip ⇒
+                zip.write(rawStr.getBytes)
+            }
 
-        Base64.encodeBase64String(arr.toByteArray)
+            Base64.encodeBase64String(arr.toByteArray)
+        }
+        catch {
+            // TODO: text.
+            case e: Exception ⇒ throw new NCE("Error compressing data", e)
+        }
     }
 
     /**
@@ -1165,8 +1172,14 @@ object NCUtils extends LazyLogging {
       * @param zipStr Compressed string.
       * @return Uncompressed string.
       */
+    @throws[NCE]
     def uncompress(zipStr: String): String =
-        IOUtils.toString(new GIS(new 
ByteArrayInputStream(Base64.decodeBase64(zipStr))), Charset.defaultCharset())
+        try
+            IOUtils.toString(new GIS(new 
ByteArrayInputStream(Base64.decodeBase64(zipStr))), Charset.defaultCharset())
+        catch {
+            // TODO: text.
+            case e: Exception ⇒ throw new NCE("Error decompressing data", e)
+        }
 
     /**
       * Sleeps number of milliseconds properly handling exceptions.
@@ -1672,6 +1685,7 @@ object NCUtils extends LazyLogging {
      * @param json JSON to convert.
      * @return
      */
+    @throws[Exception]
     def jsonToScalaMap(json: String): Map[String, Object] =
         GSON.fromJson(json, classOf[java.util.HashMap[String, 
Object]]).asScala.toMap
 
@@ -1681,8 +1695,15 @@ object NCUtils extends LazyLogging {
      * @param json JSON to convert.
      * @return
      */
-    def jsonToJavaMap(json: String): java.util.Map[String, Object] =
-        GSON.fromJson(json, classOf[java.util.HashMap[String, Object]])
+    @throws[NCE]
+    def jsonToJavaMap(json: String): java.util.Map[String, Object] = {
+        try
+            GSON.fromJson(json, classOf[java.util.HashMap[String, Object]])
+        catch {
+            // TODO: text
+            case e: Exception ⇒ throw new NCE(s"Cannot deserialize JSON to 
map: '$json'", e)
+        }
+    }
 
     /**
      *
@@ -1690,9 +1711,14 @@ object NCUtils extends LazyLogging {
      * @param field
      * @return
      */
-    @throws[Exception]
+    @throws[NCE]
     def getJsonBooleanField(json: String, field: String): Boolean =
-        
GSON.getAdapter(classOf[JsonElement]).fromJson(json).getAsJsonObject.get(field).getAsBoolean
+        try
+            
GSON.getAdapter(classOf[JsonElement]).fromJson(json).getAsJsonObject.get(field).getAsBoolean
+        catch {
+            // TODO: text
+            case e: Exception ⇒ throw new NCE(s"Cannot deserialize JSON to 
map: '$json'", e)
+        }
 
     /**
      *
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/embedded/NCEmbeddedResult.java
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/embedded/NCEmbeddedResult.java
index 3ece971..8d20981 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/embedded/NCEmbeddedResult.java
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/embedded/NCEmbeddedResult.java
@@ -18,6 +18,8 @@
 package org.apache.nlpcraft.model.tools.embedded;
 
 import org.apache.nlpcraft.model.*;
+
+import java.util.Map;
 import java.util.function.*;
 
 /**
@@ -68,6 +70,9 @@ public interface NCEmbeddedResult {
      */
     String getBody();
 
+    // TODO:
+    Map<String, Object> getMeta();
+
     /**
      * Gets optional result type. Note that either both result body and type 
are set or
      * error message and error code are set, but not both pairs.
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/NCTestClientBuilder.java
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/NCTestClientBuilder.java
index df39787..550d0a3 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/NCTestClientBuilder.java
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/NCTestClientBuilder.java
@@ -192,6 +192,7 @@ public class NCTestClientBuilder {
         @SerializedName("usrId") private long userId;
         @SerializedName("resType") private String resType;
         @SerializedName("resBody") private Object resBody;
+        @SerializedName("resMeta") private Object resMeta;
         @SerializedName("status") private String status;
         @SerializedName("error") private String error;
         @SerializedName("createTstamp") private long createTstamp;
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/NCProbeEnrichmentManager.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/NCProbeEnrichmentManager.scala
index 398d1c2..e2d949a 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/NCProbeEnrichmentManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/NCProbeEnrichmentManager.scala
@@ -275,6 +275,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
           *
           * @param resType Result type.
           * @param resBody Result body.
+          * @param resMeta Result meta.
           * @param errMsg Error message.
           * @param errCode Error code.
           * @param msgName Message name.
@@ -284,6 +285,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
         def respond(
             resType: Option[String],
             resBody: Option[String],
+            resMeta: Option[JavaMeta],
             errMsg: Option[String],
             errCode: Option[Int],
             msgName: String,
@@ -302,6 +304,10 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
                 if (vOpt.isDefined)
                     msg += name → vOpt.get
 
+            def addMeta(name: String, vOpt: Option[JavaMeta]): Unit =
+                if (vOpt.isDefined)
+                    msg += name → vOpt.get.asInstanceOf[Serializable]
+
             if (resBody.isDefined && resBody.get.length > 
Config.resultMaxSize) {
                 addOptional("error", Some("Result is too big. Model result 
must be corrected."))
                 addOptional("errorCode", Some(RESULT_TOO_BIG))
@@ -311,6 +317,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
                 addOptional("errorCode", errCode.map(Integer.valueOf))
                 addOptional("resType", resType)
                 addOptional("resBody", resBody)
+                addMeta("resMeta", resMeta)
                 addOptional("log", log)
                 addOptional("intentId", intentId)
             }
@@ -322,6 +329,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
                     override val getOriginalText: String = txt
                     override val getUserId: Long = usrId
                     override val getBody: String = 
msg.dataOpt[String]("resBody").orNull
+                    override val getMeta: JavaMeta = 
msg.dataOpt[JavaMeta]("resMeta").orNull
                     override val getType: String = 
msg.dataOpt[String]("resType").orNull
                     override val getErrorMessage: String = 
msg.dataOpt[String]("error").orNull
                     override val getErrorCode: Int = 
msg.dataOpt[Int]("errorCode").getOrElse(0)
@@ -394,6 +402,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
             respond(
                 None,
                 None,
+                None,
                 Some(errMsg),
                 Some(errCode),
                 "P2S_ASK_RESULT",
@@ -531,6 +540,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
                 respond(
                     None,
                     None,
+                    None,
                     Some(errMsg),
                     Some(errCode),
                     "P2S_ASK_RESULT",
@@ -621,6 +631,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
         def respondWithResult(res: NCResult, log: Option[String]): Unit = 
respond(
             Some(res.getType),
             Some(res.getBody),
+            Some(res.getMetadata),
             None,
             None,
             "P2S_ASK_RESULT",
@@ -693,6 +704,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
                             respond(
                                 None,
                                 None,
+                                None,
                                 Some(e.getMessage), // User provided rejection 
message.
                                 Some(MODEL_REJECTION),
                                 "P2S_ASK_RESULT",
@@ -722,6 +734,7 @@ object NCProbeEnrichmentManager extends NCService with 
NCOpenCensusModelStats {
                             respond(
                                 None,
                                 None,
+                                None,
                                 Some("Processing failed with unexpected 
error."), // System error message.
                                 Some(UNEXPECTED_ERROR),
                                 "P2S_ASK_RESULT",
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/impl/NCRequestImpl.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/impl/NCRequestImpl.scala
index d828961..394a96e 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/impl/NCRequestImpl.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/impl/NCRequestImpl.scala
@@ -17,14 +17,13 @@
 
 package org.apache.nlpcraft.probe.mgrs.nlp.impl
 
-import java.util.{Collections, Optional}
 import org.apache.nlpcraft.common._
 import org.apache.nlpcraft.model._
 import org.apache.nlpcraft.model.impl._
 
-import java.util
-import scala.collection._
+import java.util.{Collections, Optional}
 import scala.collection.JavaConverters._
+import scala.collection._
 import scala.compat.java8.OptionConverters._
 
 /**
@@ -55,7 +54,7 @@ case class NCRequestImpl(nlpMeta: Map[String, Any], srvReqId: 
String) extends NC
         getOpt("COMPANY_CITY"),
         getOpt("COMPANY_ADDRESS"),
         getOpt("COMPANY_POSTAL"),
-        getMap("COMPANY_META")
+        getOpt("COMPANY_META").orElse(Collections.emptyMap())
     )
     override lazy val getUser: NCUser = new NCUserImpl(
         nlpMeta("USER_ID").asInstanceOf[Long],
@@ -63,7 +62,7 @@ case class NCRequestImpl(nlpMeta: Map[String, Any], srvReqId: 
String) extends NC
         getOpt("LAST_NAME"),
         getOpt("EMAIL"),
         getOpt("AVATAR_URL"),
-        getMap("META"),
+        getOpt("META").orElse(Collections.emptyMap()),
         nlpMeta("IS_ADMIN").asInstanceOf[Boolean],
         nlpMeta("SIGNUP_TSTAMP").asInstanceOf[Long]
     )
@@ -73,10 +72,4 @@ case class NCRequestImpl(nlpMeta: Map[String, Any], 
srvReqId: String) extends NC
             case Some(v) ⇒ Optional.of(v.asInstanceOf[T])
             case None ⇒ Optional.empty()
         }
-
-    private def getMap(key: String): util.Map[String, AnyRef] = {
-        val m: Optional[JavaMeta] = getOpt(key)
-
-        if (m.isPresent) m.get() else Collections.emptyMap()
-    }
 }
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/company/NCCompanyManager.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/company/NCCompanyManager.scala
index 41a6444..0e3937f 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/company/NCCompanyManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/company/NCCompanyManager.scala
@@ -21,7 +21,7 @@ import io.opencensus.trace.Span
 import org.apache.ignite.{IgniteAtomicSequence, IgniteSemaphore}
 import org.apache.nlpcraft.common.{NCService, _}
 import org.apache.nlpcraft.server.ignite.NCIgniteInstance
-import org.apache.nlpcraft.server.mdo.{NCCompanyMdo, NCCompanyPropertyMdo}
+import org.apache.nlpcraft.server.mdo.NCCompanyMdo
 import org.apache.nlpcraft.server.sql.{NCSql, NCSqlManager}
 import org.apache.nlpcraft.server.user.NCUserManager
 
@@ -168,7 +168,7 @@ object NCCompanyManager extends NCService with 
NCIgniteInstance {
         city: Option[String],
         address: Option[String],
         postalCode: Option[String],
-        props: Option[Map[String, String]],
+        props: Option[String],
         parent: Span = null
     ): Unit =
         startScopedSpan("updateCompany", parent, "id" → id) { span ⇒
@@ -282,7 +282,7 @@ object NCCompanyManager extends NCService with 
NCIgniteInstance {
         adminFirstName: String,
         adminLastName: String,
         adminAvatarUrl: Option[String],
-        props: Option[Map[String, String]],
+        props: Option[String],
         mkToken: () ⇒ String,
         parent: Span = null
     ): NCCompanyCreationData = {
@@ -371,7 +371,7 @@ object NCCompanyManager extends NCService with 
NCIgniteInstance {
         adminFirstName: String,
         adminLastName: String,
         adminAvatarUrl: Option[String],
-        props: Option[Map[String, String]],
+        props: Option[String],
         parent: Span = null
     ): NCCompanyCreationData = startScopedSpan("addCompany", parent, "name" → 
name) { _ ⇒
         addCompany0(
@@ -391,18 +391,4 @@ object NCCompanyManager extends NCService with 
NCIgniteInstance {
             () ⇒ mkToken()
         )
     }
-
-    /**
-      * Gets company properties for given company ID.
-      *
-      * @param id User ID.
-      * @param parent Optional parent span.
-      */
-    @throws[NCE]
-    def getCompanyProperties(id: Long, parent: Span = null): 
Seq[NCCompanyPropertyMdo] =
-        startScopedSpan("getCompanyProperties", parent, "companyId" → id) { 
span ⇒
-            NCSql.sql {
-                NCSqlManager.getCompanyProperties(id, span)
-            }
-        }
 }
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyMdo.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyMdo.scala
index c4aa5a8..2405bf2 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyMdo.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyMdo.scala
@@ -37,6 +37,7 @@ case class NCCompanyMdo(
     @NCMdoField(column = "postal_code") postalCode: Option[String],
     @NCMdoField(column = "auth_token") authToken: String,
     @NCMdoField(column = "auth_token_hash") authTokenHash: String,
+    @NCMdoField(column = "properties_gzip") propertiesGzip: Option[String],
     @NCMdoField(column = "created_on") createdOn: Timestamp,
     @NCMdoField(column = "last_modified_on") lastModifiedOn: Timestamp
 ) extends NCAnnotatedMdo[NCCompanyMdo]
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyPropertyMdo.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyPropertyMdo.scala
deleted file mode 100644
index 695a41c..0000000
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyPropertyMdo.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.nlpcraft.server.mdo
-
-import org.apache.nlpcraft.server.mdo.impl._
-import org.apache.nlpcraft.server.sql.NCSql.Implicits.RsParser
-
-import java.sql.Timestamp
-
-/**
-  * Company property MDO.
-  */
-@NCMdoEntity(table = "nc_company_property")
-case class NCCompanyPropertyMdo(
-    @NCMdoField(column = "id", pk = true) id: Long,
-    @NCMdoField(column = "company_id") userId: Long,
-    @NCMdoField(column = "property") property: String,
-    @NCMdoField(column = "value") value: String,
-    @NCMdoField(column = "created_on") createdOn: Timestamp,
-    @NCMdoField(column = "last_modified_on") lastModifiedOn: Timestamp
-) extends NCAnnotatedMdo[NCCompanyPropertyMdo]
-
-object NCCompanyPropertyMdo {
-    implicit val x: RsParser[NCCompanyPropertyMdo] =
-        NCAnnotatedMdo.mkRsParser(classOf[NCCompanyPropertyMdo])
-}
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCQueryStateMdo.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCQueryStateMdo.scala
index 27f440a..7677b36 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCQueryStateMdo.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCQueryStateMdo.scala
@@ -17,8 +17,9 @@
 
 package org.apache.nlpcraft.server.mdo
 
-import java.sql.Timestamp
+import org.apache.nlpcraft.common.JavaMeta
 
+import java.sql.Timestamp
 import org.apache.nlpcraft.server.sql.NCSql.Implicits.RsParser
 import org.apache.nlpcraft.server.mdo.impl._
 
@@ -45,6 +46,7 @@ case class NCQueryStateMdo(
     // Query OK.
     @NCMdoField var resultType: Option[String] = None,
     @NCMdoField var resultBody: Option[String] = None,
+    @NCMdoField var resultMeta: Option[JavaMeta] = None,
     // Query ERROR.
     @NCMdoField var error: Option[String] = None,
     @NCMdoField var errorCode: Option[Int] = None
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserMdo.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserMdo.scala
index 9d7be9d..063a4be 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserMdo.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserMdo.scala
@@ -36,6 +36,7 @@ case class NCUserMdo(
     @NCMdoField(column = "last_name") lastName: Option[String],
     @NCMdoField(column = "avatar_url") avatarUrl: Option[String],
     @NCMdoField(column = "passwd_salt") passwordSalt: Option[String],
+    @NCMdoField(column = "properties_gzip") propertiesGzip: Option[String],
     @NCMdoField(column = "is_admin") isAdmin: Boolean,
     @NCMdoField(column = "created_on") createdOn: Timestamp,
     @NCMdoField(column = "last_modified_on") lastModifiedOn: Timestamp
@@ -54,11 +55,12 @@ object NCUserMdo {
         lastName: Option[String],
         avatarUrl: Option[String],
         passwordSalt: Option[String],
+        propertiesGzip: Option[String],
         isAdmin: Boolean
     ): NCUserMdo = {
         val now = U.nowUtcTs()
 
-        NCUserMdo(id, companyId, extId, email, firstName, lastName, avatarUrl, 
passwordSalt, isAdmin, now, now)
+        NCUserMdo(id, companyId, extId, email, firstName, lastName, avatarUrl, 
passwordSalt, propertiesGzip, isAdmin, now, now)
     }
 
     def apply(
@@ -70,11 +72,12 @@ object NCUserMdo {
         lastName: Option[String],
         avatarUrl: Option[String],
         passwordSalt: Option[String],
+        propertiesGzip: Option[String],
         isAdmin: Boolean,
         createdOn: Timestamp
     ): NCUserMdo = {
         require(createdOn != null, "Created date cannot be null.")
 
-        NCUserMdo(id, companyId, extId, email, firstName, lastName, avatarUrl, 
passwordSalt, isAdmin, createdOn, createdOn)
+        NCUserMdo(id, companyId, extId, email, firstName, lastName, avatarUrl, 
passwordSalt, propertiesGzip, isAdmin, createdOn, createdOn)
     }
 }
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserPropertyMdo.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserPropertyMdo.scala
deleted file mode 100644
index f1813cd..0000000
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserPropertyMdo.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.nlpcraft.server.mdo
-
-import java.sql.Timestamp
-
-import org.apache.nlpcraft.server.mdo.impl._
-import org.apache.nlpcraft.server.sql.NCSql.Implicits.RsParser
-
-/**
-  * User property MDO.
-  */
-@NCMdoEntity(table = "nc_user_property")
-case class NCUserPropertyMdo(
-    @NCMdoField(column = "id", pk = true) id: Long,
-    @NCMdoField(column = "user_id") userId: Long,
-    @NCMdoField(column = "property") property: String,
-    @NCMdoField(column = "value") value: String,
-    @NCMdoField(column = "created_on") createdOn: Timestamp,
-    @NCMdoField(column = "last_modified_on") lastModifiedOn: Timestamp
-) extends NCAnnotatedMdo[NCUserPropertyMdo]
-
-object NCUserPropertyMdo {
-    implicit val x: RsParser[NCUserPropertyMdo] =
-        NCAnnotatedMdo.mkRsParser(classOf[NCUserPropertyMdo])
-}
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
index 34b654b..9452b83 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
@@ -722,6 +722,7 @@ object NCProbeManager extends NCService {
                         val errCodeOpt = probeMsg.dataOpt[Int]("errorCode")
                         val resTypeOpt = probeMsg.dataOpt[String]("resType")
                         val resBodyOpt = probeMsg.dataOpt[String]("resBody")
+                        val resMetaOpt = probeMsg.dataOpt[JavaMeta]("resMeta")
                         val logJson = probeMsg.dataOpt[String]("log")
                         val intentId = probeMsg.dataOpt[String]("intentId")
 
@@ -740,13 +741,11 @@ object NCProbeManager extends NCService {
                         else { // OK result.
                             require(resTypeOpt.isDefined && 
resBodyOpt.isDefined, "Result defined")
                      
-                            val resType = resTypeOpt.get
-                            val resBody = resBodyOpt.get
-                     
                             NCQueryManager.setResult(
                                 srvReqId,
-                                resType,
-                                resBody,
+                                resTypeOpt.get,
+                                resBodyOpt.get,
+                                resMetaOpt,
                                 logJson,
                                 intentId
                             )
@@ -849,22 +848,11 @@ object NCProbeManager extends NCService {
         usrAgent: Option[String],
         rmtAddr: Option[String],
         data: Option[String],
-        usrMeta: Option[Map[String, String]],
-        companyMeta: Option[Map[String, String]],
+        usrMeta: Option[JavaMeta],
+        companyMeta: Option[JavaMeta],
         enableLog: Boolean,
         parent: Span = null): Unit = {
         startScopedSpan("askProbe", parent, "srvReqId" → srvReqId, "usrId" → 
usr.id, "mdlId" → mdlId, "txt" → txt) { span ⇒
-            def convertMeta(metaOpt: Option[Map[String, String]]): 
util.HashMap[String, String] =
-                metaOpt match {
-                    case Some(meta) ⇒
-                        val map = new util.HashMap[String, String]()
-
-                        meta.foreach { case (k, v) ⇒ map.put(k, v) }
-
-                        map
-                    case None ⇒ null
-                }
-
             val senMeta = new util.HashMap[String, java.io.Serializable]()
 
             Map(
@@ -880,7 +868,7 @@ object NCProbeManager extends NCService {
                 "IS_ADMIN" → usr.isAdmin,
                 "AVATAR_URL" → usr.avatarUrl.orNull,
                 "DATA" → data.orNull,
-                "META" → convertMeta(usrMeta),
+                "META" → usrMeta.orNull,
                 "COMPANY_ID" → company.id,
                 "COMPANY_NAME" → company.name,
                 "COMPANY_WEBSITE" → company.website.orNull,
@@ -889,7 +877,7 @@ object NCProbeManager extends NCService {
                 "COMPANY_CITY" → company.city.orNull,
                 "COMPANY_ADDRESS" → company.address.orNull,
                 "COMPANY_POSTAL" → company.postalCode.orNull,
-                "COMPANY_META" → convertMeta(companyMeta)
+                "COMPANY_META" → companyMeta.orNull
             ).
                 filter(_._2 != null).
                 foreach(p ⇒ senMeta.put(p._1, 
p._2.asInstanceOf[java.io.Serializable]))
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/proclog/NCProcessLogManager.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/proclog/NCProcessLogManager.scala
index f7958b9..436a0e1 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/proclog/NCProcessLogManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/proclog/NCProcessLogManager.scala
@@ -91,6 +91,7 @@ object NCProcessLogManager extends NCService with 
NCIgniteInstance {
       * @param errMsg
       * @param resType
       * @param resBody
+      * @param resMeta
       * @param intentId
       * @param parent Optional parent span.
       */
@@ -101,6 +102,7 @@ object NCProcessLogManager extends NCService with 
NCIgniteInstance {
         errMsg: Option[String],
         resType: Option[String],
         resBody: Option[String],
+        resMeta: Option[JavaMeta],
         intentId: Option[String],
         parent: Span = null
     ): Unit =
@@ -115,6 +117,7 @@ object NCProcessLogManager extends NCService with 
NCIgniteInstance {
                     errMsg.orNull,
                     resType.orNull,
                     resBody.orNull,
+                    resMeta.orNull,
                     intentId.orNull,
                     tstamp,
                     span
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/query/NCQueryManager.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/query/NCQueryManager.scala
index ad6fdb1..2b762f3 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/query/NCQueryManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/query/NCQueryManager.scala
@@ -22,6 +22,7 @@ import org.apache.ignite.IgniteCache
 import org.apache.ignite.events.{CacheEvent, EventType}
 import org.apache.nlpcraft.common.ascii.NCAsciiTable
 import org.apache.nlpcraft.common.pool.NCThreadPoolManager
+import org.apache.nlpcraft.common.util.NCUtils.{jsonToJavaMap, uncompress}
 import org.apache.nlpcraft.common.{NCService, _}
 import org.apache.nlpcraft.server.apicodes.NCApiStatusCode._
 import org.apache.nlpcraft.server.company.NCCompanyManager
@@ -221,18 +222,6 @@ object NCQueryManager extends NCService with 
NCIgniteInstance with NCOpenCensusS
         val usr = NCUserManager.getUserById(usrId, parent).getOrElse(throw new 
NCE(s"Unknown user ID: $usrId"))
         val company = NCCompanyManager.getCompany(usr.companyId, 
parent).getOrElse(throw new NCE(s"Unknown company ID: ${usr.companyId}"))
 
-        val usrMeta = {
-            val m = NCUserManager.getUserProperties(usrId, parent)
-
-            if (m.isEmpty) None else Some(m.map(p ⇒ p.property → 
p.value).toMap)
-        }
-
-        val compMeta = {
-            val m = NCCompanyManager.getCompanyProperties(usr.companyId, 
parent)
-
-            if (m.isEmpty) None else Some(m.map(p ⇒ p.property → 
p.value).toMap)
-        }
-
         // Check input length.
         if (txt0.split(" ").length > MAX_WORDS)
             throw new NCE(s"User input is too long (max is $MAX_WORDS words).")
@@ -285,6 +274,13 @@ object NCQueryManager extends NCService with 
NCIgniteInstance with NCOpenCensusS
 
                 val enabledBuiltInToks = NCProbeManager.getModel(mdlId, 
span).enabledBuiltInTokens
 
+                @throws[NCE]
+                def unzipProperties(gzipOpt: Option[String]): Option[JavaMeta] 
=
+                    gzipOpt match {
+                        case Some(gzip) ⇒ Some(jsonToJavaMap(uncompress(gzip)))
+                        case None ⇒ None
+                    }
+
                 // Enrich the user input and send it to the probe.
                 NCProbeManager.askProbe(
                     srvReqId,
@@ -296,8 +292,8 @@ object NCQueryManager extends NCService with 
NCIgniteInstance with NCOpenCensusS
                     usrAgent,
                     rmtAddr,
                     data,
-                    usrMeta,
-                    compMeta,
+                    unzipProperties(usr.propertiesGzip),
+                    unzipProperties(company.propertiesGzip),
                     enableLog,
                     span
                 )
@@ -369,6 +365,7 @@ object NCQueryManager extends NCService with 
NCIgniteInstance with NCOpenCensusS
                     None,
                     None,
                     None,
+                    None,
                     span
                 )
         }
@@ -379,6 +376,7 @@ object NCQueryManager extends NCService with 
NCIgniteInstance with NCOpenCensusS
       * @param srvReqId Server request ID.
       * @param resType
       * @param resBody
+      * @param resMeta
       * @param logJson
       * @param intentId
       * @param parent Optional parent span.
@@ -388,6 +386,7 @@ object NCQueryManager extends NCService with 
NCIgniteInstance with NCOpenCensusS
         srvReqId: String,
         resType: String,
         resBody: String,
+        resMeta: Option[JavaMeta],
         logJson: Option[String],
         intentId: Option[String],
         parent: Span = null
@@ -407,6 +406,7 @@ object NCQueryManager extends NCService with 
NCIgniteInstance with NCOpenCensusS
                         copy.status = QRY_READY.toString
                         copy.resultType = Some(resType)
                         copy.resultBody = Some(resBody)
+                        copy.resultMeta = resMeta
                         copy.logJson = logJson
                         copy.intentId = intentId
     
@@ -430,6 +430,7 @@ object NCQueryManager extends NCService with 
NCIgniteInstance with NCOpenCensusS
                     None,
                     resType = Some(resType),
                     resBody = Some(resBody),
+                    resMeta = resMeta,
                     intentId = intentId,
                     span
                 )
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/rest/NCBasicRestApi.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/rest/NCBasicRestApi.scala
index a681a8f..c80c0ac 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/rest/NCBasicRestApi.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/rest/NCBasicRestApi.scala
@@ -24,14 +24,17 @@ import akka.http.scaladsl.model._
 import akka.http.scaladsl.model.headers._
 import akka.http.scaladsl.server.Directives.{entity, _}
 import akka.http.scaladsl.server._
-import com.google.gson.Gson
+import com.fasterxml.jackson.core.JsonProcessingException
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import com.typesafe.scalalogging.LazyLogging
 import io.opencensus.stats.Measure
 import io.opencensus.trace.{Span, Status}
 import org.apache.commons.validator.routines.UrlValidator
 import org.apache.nlpcraft.common.opencensus.NCOpenCensusTrace
 import org.apache.nlpcraft.common.pool.NCThreadPoolManager
-import org.apache.nlpcraft.common.{NCE, U}
+import org.apache.nlpcraft.common.util.NCUtils.{jsonToJavaMap, uncompress}
+import org.apache.nlpcraft.common.{JavaMeta, NCE, U}
 import org.apache.nlpcraft.model.NCModelView
 import org.apache.nlpcraft.server.apicodes.NCApiStatusCode.{API_OK, _}
 import org.apache.nlpcraft.server.company.NCCompanyManager
@@ -52,12 +55,10 @@ import scala.concurrent.{ExecutionContext, Future}
   * REST API default implementation.
   */
 class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace 
with NCOpenCensusServerStats {
-    protected final val GSON = new Gson()
     protected final val URL_VALIDATOR = new UrlValidator(Array("http", 
"https"), UrlValidator.ALLOW_LOCAL_URLS)
 
     final val API_VER = 1
     final val API = "api" / s"v$API_VER"
-
     /** */
     private final val CORS_HDRS = List(
         `Access-Control-Allow-Origin`.*,
@@ -65,6 +66,10 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with 
NCOpenCensusTrace w
         `Access-Control-Allow-Headers`("Authorization", "Content-Type", 
"X-Requested-With")
     )
 
+    private final val JS_MAPPER = new ObjectMapper()
+
+    JS_MAPPER.registerModule(DefaultScalaModule)
+
     /*
      * General control exception.
      * Note that these classes must be public because scala 2.11 internal 
errors (compilations problems).
@@ -132,6 +137,21 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
 
     /**
       *
+      * @param o
+      * @throws
+      * @return
+      */
+    @throws[NCE]
+    private def toJs(o: AnyRef): String =
+        try
+            JS_MAPPER.writeValueAsString(o)
+        catch {
+            // TODO: text
+            case e: JsonProcessingException ⇒ throw new NCE("Serialization 
error", e)
+        }
+
+    /**
+      *
       * @param acsTkn Access token to check.
       * @param shouldBeAdmin Admin flag.
       * @return
@@ -220,28 +240,38 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                 else
                     s.resultBody.orNull
                 ),
+            "resMeta" → s.resultMeta.orNull,
             "error" → s.error.orNull,
             "errorCode" → s.errorCode.map(Integer.valueOf).orNull,
             "logHolder" → (if (s.logJson.isDefined) 
U.jsonToObject(s.logJson.get) else null),
             "intentId" → s.intentId.orNull
         ).filter(_._2 != null).asJava
 
+
     /**
-      * Checks properties.
+      * Extracts and checks JSON.
       *
-      * @param propsOpt Optional properties.
+      * @param jsOpt JSON value. Optional.
+      * @param name Property name.
       */
     @throws[TooLargeField]
-    private def checkProperties(propsOpt: Option[Map[String, String]]): Unit =
-        propsOpt match {
-            case Some(props) ⇒
-                props.foreach { case (k, v) ⇒
-                    checkLength(k, k, 64)
-
-                    if (v != null && v.nonEmpty && v.length > 512)
-                        throw TooLargeField(v, 512)
+    @throws[InvalidField]
+    private def extractJson(jsOpt: Option[spray.json.JsValue], name: String): 
Option[String] =
+        jsOpt match {
+            case Some(js) ⇒
+                val s = js.compactPrint
+
+                checkLength(name, s, 512000)
+
+                // Validates.
+                try
+                    U.jsonToJavaMap(s)
+                catch {
+                    case _: NCE ⇒ throw InvalidField(name)
                 }
-            case None ⇒ // No-op.
+
+                Some(s)
+            case None ⇒ None
         }
 
     /**
@@ -526,9 +556,28 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
       *
       * @param fut
       */
-    private def successWithJs(fut: Future[String]): Route = onSuccess(fut) {
-        js ⇒ complete(HttpResponse(entity = 
HttpEntity(ContentTypes.`application/json`, js)))
-    }
+    private def successWithJs(fut: Future[String]): Route =
+        onSuccess(fut) {
+            js ⇒ complete(HttpResponse(entity = 
HttpEntity(ContentTypes.`application/json`, js)))
+        }
+
+    /**
+      *
+      * @param o
+      */
+    private def completeJs(o: Object): Route =
+        complete(HttpResponse(entity = 
HttpEntity(ContentTypes.`application/json`, toJs(o))))
+
+    /**
+      *
+      * @param gzipOpt
+      */
+    @throws[NCE]
+    private def unzipProperties(gzipOpt: Option[String]): Option[JavaMeta] =
+        gzipOpt match {
+            case Some(gzip) ⇒ Some(jsonToJavaMap(uncompress(gzip)))
+            case None ⇒ None
+        }
 
     /**
       *
@@ -559,14 +608,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     "acsTok" → req.acsTok, "usrExtId" → req.usrExtId, "mdlId" 
→ req.mdlId, "txt" → req.txt
                 )
 
-                val dataJsOpt =
-                    req.data match {
-                        case Some(data) ⇒ Some(data.compactPrint)
-                        case None ⇒ None
-                    }
-
-                checkLengthOpt("data", dataJsOpt,512000)
-
+                val dataJs = extractJson(req.data, "data")
                 val acsUsr = authenticate(req.acsTok)
 
                 checkModelId(req.mdlId, acsUsr.companyId)
@@ -579,7 +621,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                             mdlId = req.mdlId,
                             usrAgent = usrAgent,
                             rmtAddr = getAddress(rmtAddr),
-                            data = dataJsOpt,
+                            data = dataJs,
                             req.enableLog.getOrElse(false),
                             parent = span
                         ))
@@ -640,8 +682,8 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
 
                 successWithJs(
                     fut.collect {
-                        // We have to use GSON (not spray) here to serialize 
'resBody' field.
-                        case res ⇒ GSON.toJson(
+                        // We have to use Jackson (not spray) here to 
serialize 'resBody' field.
+                        case res ⇒ toJs(
                             Map(
                                 "status" → API_OK.toString,
                                 "state" → queryStateToMap(res)
@@ -724,8 +766,8 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                         toSeq.sortBy(-_.createTstamp.getTime).
                         take(req.maxRows.getOrElse(Integer.MAX_VALUE))
 
-                // We have to use GSON (not spray) here to serialize 'resBody' 
field.
-                val js = GSON.toJson(
+                // We have to use Jackson (not spray) here to serialize 
'resBody' field.
+                val js = toJs(
                     Map(
                         "status" → API_OK.toString,
                         "states" → states.map(queryStateToMap).asJava
@@ -772,8 +814,8 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
 
                 successWithJs(
                     fut.collect {
-                        // We have to use GSON (not spray) here to serialize 
'result' field.
-                        case res ⇒ GSON.toJson(Map("status" → API_OK.toString, 
"result" → res).asJava)
+                        // We have to use Jackson (not spray) here to 
serialize 'result' field.
+                        case res ⇒ toJs(Map("status" → API_OK.toString, 
"result" → res).asJava)
                     }
                 )
             }
@@ -881,7 +923,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
             adminFirstName: String,
             adminLastName: String,
             adminAvatarUrl: Option[String],
-            properties: Option[Map[String, String]]
+            properties: Option[spray.json.JsValue]
         )
         case class Res$Company$Add(
             status: String,
@@ -911,7 +953,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     "adminAvatarUrl" → req.adminAvatarUrl
                 )
 
-                checkProperties(req.properties)
+                val propsJs = extractJson(req.properties, "properties")
 
                 // Via REST only administrators of already created companies 
can create new companies.
                 authenticateAsAdmin(req.acsTok)
@@ -929,7 +971,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     req.adminFirstName,
                     req.adminLastName,
                     req.adminAvatarUrl,
-                    req.properties,
+                    propsJs,
                     span
                 )
 
@@ -958,11 +1000,10 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
             city: Option[String],
             address: Option[String],
             postalCode: Option[String],
-            properties: Option[Map[String, String]]
+            properties: Option[JavaMeta]
         )
 
         implicit val reqFmt: RootJsonFormat[Req$Company$Get] = 
jsonFormat1(Req$Company$Get)
-        implicit val resFmt: RootJsonFormat[Res$Company$Get] = 
jsonFormat10(Res$Company$Get)
 
         entity(as[Req$Company$Get]) { req ⇒
             startScopedSpan("company$get", "acsTok" → req.acsTok) { span ⇒
@@ -974,9 +1015,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     getCompany(acsUsr.companyId, span).
                     getOrElse(throw new NCE(s"Company not found: 
${acsUsr.companyId}"))
 
-                val props = 
NCCompanyManager.getCompanyProperties(acsUsr.companyId, span)
-
-                complete {
+                completeJs {
                     Res$Company$Get(API_OK,
                         company.id,
                         company.name,
@@ -986,7 +1025,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                         company.city,
                         company.address,
                         company.postalCode,
-                        if (props.isEmpty) None else Some(props.map(p ⇒ 
p.property → p.value).toMap)
+                        unzipProperties(company.propertiesGzip)
                     )
                 }
             }
@@ -1010,7 +1049,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
             city: Option[String],
             address: Option[String],
             postalCode: Option[String],
-            properties: Option[Map[String, String]]
+            properties: Option[spray.json.JsValue]
         )
         case class Res$Company$Update(
             status: String
@@ -1031,8 +1070,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     "postalCode" → req.postalCode
                 )
 
-                checkProperties(req.properties)
-
+                val propsJs = extractJson(req.properties, "properties")
                 val admUsr = authenticateAsAdmin(req.acsTok)
 
                 NCCompanyManager.updateCompany(
@@ -1044,7 +1082,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     req.city,
                     req.address,
                     req.postalCode,
-                    req.properties,
+                    propsJs,
                     span
                 )
 
@@ -1314,7 +1352,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
             lastName: String,
             avatarUrl: Option[String],
             isAdmin: Boolean,
-            properties: Option[Map[String, String]],
+            properties: Option[spray.json.JsValue],
             usrExtId: Option[String]
         )
         case class Res$User$Add(
@@ -1337,8 +1375,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     "usrExtId" → req.usrExtId
                 )
 
-                checkProperties(req.properties)
-
+                val propsJs = extractJson(req.properties, "properties")
                 val admUsr = authenticateAsAdmin(req.acsTok)
 
                 val id = NCUserManager.addUser(
@@ -1349,7 +1386,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     req.lastName,
                     req.avatarUrl,
                     req.isAdmin,
-                    req.properties,
+                    propsJs,
                     req.usrExtId,
                     span
                 )
@@ -1375,7 +1412,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
             firstName: String,
             lastName: String,
             avatarUrl: Option[String],
-            properties: Option[Map[String, String]]
+            properties: Option[spray.json.JsValue]
         )
         case class Res$User$Update(
             status: String
@@ -1393,8 +1430,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     "avatarUrl" → req.avatarUrl
                 )
 
-                checkProperties(req.properties)
-
+                val propsJs = extractJson(req.properties, "properties")
                 val acsUsr = authenticate(req.acsTok)
 
                 NCUserManager.updateUser(
@@ -1402,7 +1438,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     req.firstName,
                     req.lastName,
                     req.avatarUrl,
-                    req.properties,
+                    propsJs,
                     span
                 )
 
@@ -1454,7 +1490,6 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
 
                     NCUserManager.
                         getAllUsers(acsUsr.companyId, span).
-                        keys.
                         filter(_.id != acsUsr.id).
                         map(_.id).
                         foreach(delete)
@@ -1575,7 +1610,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
             avatarUrl: Option[String],
             isAdmin: Boolean,
             companyId: Long,
-            properties: Option[Map[String, String]]
+            properties: Option[JavaMeta]
         )
         case class Res$User$All(
             status: String,
@@ -1583,8 +1618,6 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
         )
 
         implicit val reqFmt: RootJsonFormat[Req$User$All] = 
jsonFormat1(Req$User$All)
-        implicit val usrFmt: RootJsonFormat[ResUser_User$All] = 
jsonFormat9(ResUser_User$All)
-        implicit val resFmt: RootJsonFormat[Res$User$All] = 
jsonFormat2(Res$User$All)
 
         entity(as[Req$User$All]) { req ⇒
             startScopedSpan("user$All", "acsTok" → req.acsTok) { span ⇒
@@ -1592,23 +1625,23 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
 
                 val admUSr = authenticateAsAdmin(req.acsTok)
 
-                val usrLst =
-                    NCUserManager.getAllUsers(admUSr.companyId, span).map { 
case (u, props) ⇒
-                        ResUser_User$All(
-                            u.id,
-                            u.email,
-                            u.extId,
-                            u.firstName,
-                            u.lastName,
-                            u.avatarUrl,
-                            u.isAdmin,
-                            u.companyId,
-                            if (props.isEmpty) None else Some(props.map(p ⇒ 
p.property → p.value).toMap)
+                completeJs {
+                    Res$User$All(
+                        API_OK,
+                        NCUserManager.getAllUsers(admUSr.companyId, 
span).map(u ⇒
+                            ResUser_User$All(
+                                u.id,
+                                u.email,
+                                u.extId,
+                                u.firstName,
+                                u.lastName,
+                                u.avatarUrl,
+                                u.isAdmin,
+                                u.companyId,
+                                unzipProperties(u.propertiesGzip)
+                            )
                         )
-                    }.toSeq
-
-                complete {
-                    Res$User$All(API_OK, usrLst)
+                    )
                 }
             }
         }
@@ -1634,11 +1667,10 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
             lastName: Option[String],
             avatarUrl: Option[String],
             isAdmin: Boolean,
-            properties: Option[Map[String, String]]
+            properties: Option[JavaMeta]
         )
 
         implicit val reqFmt: RootJsonFormat[Req$User$Get] = 
jsonFormat3(Req$User$Get)
-        implicit val resFmt: RootJsonFormat[Res$User$Get] = 
jsonFormat9(Res$User$Get)
 
         entity(as[Req$User$Get]) { req ⇒
             startScopedSpan(
@@ -1653,9 +1685,8 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     throw AdminRequired(acsUsr.email.get)
 
                 val usr = NCUserManager.getUserById(usrId, 
span).getOrElse(throw new NCE(s"User not found: $usrId"))
-                val props = NCUserManager.getUserProperties(usrId, span)
 
-                complete {
+                completeJs {
                     Res$User$Get(API_OK,
                         usr.id,
                         usr.email,
@@ -1664,7 +1695,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                         usr.lastName,
                         usr.avatarUrl,
                         usr.isAdmin,
-                        if (props.isEmpty) None else Some(props.map(p ⇒ 
p.property → p.value).toMap)
+                        unzipProperties(usr.propertiesGzip)
                     )
                 }
             }
@@ -1771,7 +1802,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging 
with NCOpenCensusTrace w
                     status = statusCode,
                     entity = HttpEntity(
                         ContentTypes.`application/json`,
-                        GSON.toJson(Map("code" → errCode, "msg" → 
errMsg).asJava)
+                        toJs(Map("code" → errCode, "msg" → errMsg).asJava)
                     )
                 )
             )
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/sql/NCSqlManager.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/sql/NCSqlManager.scala
index 00fe81b..3ef2551 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/sql/NCSqlManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/sql/NCSqlManager.scala
@@ -17,8 +17,10 @@
 
 package org.apache.nlpcraft.server.sql
 
+import com.fasterxml.jackson.core.JsonProcessingException
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import io.opencensus.trace.Span
-import org.apache.ignite.IgniteAtomicSequence
 import org.apache.nlpcraft.common.config.NCConfigurable
 import org.apache.nlpcraft.common.{NCService, _}
 import org.apache.nlpcraft.server.apicodes.NCApiStatusCode._
@@ -27,7 +29,6 @@ import org.apache.nlpcraft.server.mdo._
 import org.apache.nlpcraft.server.sql.NCSql.Implicits._
 
 import java.sql.Timestamp
-import scala.util.control.Exception.catching
 
 /**
   * Provides basic CRUD and often used operations on RDBMS.
@@ -35,7 +36,7 @@ import scala.util.control.Exception.catching
   */
 object NCSqlManager extends NCService with NCIgniteInstance {
     private final val DB_TABLES = Seq(
-        "nc_company", "nc_company_property", "nc_user", "nc_user_property", 
"passwd_pool", "proc_log", "feedback"
+        "nc_company", "nc_user", "passwd_pool", "proc_log", "feedback"
     )
     private final val CACHE_2_CLEAR = Seq(
         "user-token-signin-cache",
@@ -45,13 +46,46 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
         "stanford-cache",
         "opennlp-cache"
     )
-    
+
+    private final val JS_MAPPER = new ObjectMapper()
+
+    JS_MAPPER.registerModule(DefaultScalaModule)
+
     private object Config extends NCConfigurable {
         def init: Boolean = 
getBoolOpt("nlpcraft.server.database.igniteDbInitialize").getOrElse(false)
     }
 
-    @volatile private var usersPropsSeq: IgniteAtomicSequence = _
-    @volatile private var compPropsSeq: IgniteAtomicSequence = _
+    /**
+      *
+      * @param opt
+      */
+    @throws[NCE]
+    private def gzip(opt: Option[String]): String =
+        opt match {
+            case Some(s) ⇒ U.compress(s)
+            case None ⇒ null
+        }
+
+    /**
+      *
+      * @param nullable
+      */
+    @throws[NCE]
+    private def gzip(nullable: String): String = if (nullable != null) 
U.compress(nullable) else null
+
+    /**
+      *
+      * @param m
+      */
+    @throws[NCE]
+    private def gzip(m: JavaMeta): String = {
+        try
+            if (m != null) U.compress(JS_MAPPER.writeValueAsString(m)) else 
null
+        catch {
+            // TODO: text
+            case e: JsonProcessingException ⇒ throw new NCE("Serialization 
error", e)
+        }
+    }
 
     /**
      *
@@ -68,11 +102,6 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
         if (NCSql.isIgniteDb)
             prepareIgniteSchema()
      
-        catching(wrapIE) {
-            usersPropsSeq = NCSql.mkSeq(ignite, "usersPropsSeq", 
"nc_user_property", "id")
-            compPropsSeq = NCSql.mkSeq(ignite, "companiesPropsSeq", 
"nc_company_property", "id")
-        }
-     
         ackStarted()
     }
 
@@ -163,7 +192,6 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
     @throws[NCE]
     def deleteUser(id: Long, parent: Span): Int =
         startScopedSpan("deleteUser", parent, "usrId" → id) { _ ⇒
-            NCSql.delete("DELETE FROM nc_user_property WHERE user_id = ?", id)
             NCSql.delete("DELETE FROM nc_user WHERE id = ?", id)
         }
 
@@ -176,7 +204,6 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
     @throws[NCE]
     def deleteCompany(id: Long, parent: Span): Int =
         startScopedSpan("deleteCompany", parent, "compId" → id) { _ ⇒
-            NCSql.delete("DELETE FROM nc_user_property WHERE user_id IN 
(SELECT id FROM nc_user WHERE company_id = ?)", id)
             NCSql.delete("DELETE FROM nc_user WHERE company_id = ?", id)
             NCSql.delete("DELETE FROM nc_company WHERE id = ?", id)
         }
@@ -197,33 +224,28 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
         firstName: String,
         lastName: String,
         avatarUrl: Option[String],
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Int =
-        startScopedSpan("updateUser", parent, "usrId" → id) { span ⇒
-            val n =
-                NCSql.update(
-                    s"""
-                       |UPDATE nc_user
-                       |SET
-                       |    first_name = ?,
-                       |    last_name = ?,
-                       |    avatar_url = ?,
-                       |    last_modified_on = ?
-                       |WHERE id = ?
-                        """.stripMargin,
-                    firstName,
-                    lastName,
-                    avatarUrl.orNull,
-                    U.nowUtcTs(),
-                    id
-                )
-         
-            NCSql.delete("DELETE FROM nc_user_property WHERE user_id = ?", id)
-         
-            addUserProperties(id, propsOpt, span)
-         
-            n
+        startScopedSpan("updateUser", parent, "usrId" → id) { _ ⇒
+            NCSql.update(
+                s"""
+                   |UPDATE nc_user
+                   |SET
+                   |    first_name = ?,
+                   |    last_name = ?,
+                   |    avatar_url = ?,
+                   |    properties_gzip = ?,
+                   |    last_modified_on = ?
+                   |WHERE id = ?
+                    """.stripMargin,
+                firstName,
+                lastName,
+                avatarUrl.orNull,
+                gzip(propsOpt),
+                U.nowUtcTs(),
+                id
+            )
         }
 
     /**
@@ -246,97 +268,32 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
         firstName: String,
         lastName: String,
         avatarUrl: Option[String],
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Int =
         startScopedSpan("updateUser", parent, "usrId" → id) { span ⇒
-            val n =
-                NCSql.update(
-                    s"""
-                       |UPDATE nc_user
-                       |SET
-                       |    email = ?,
-                       |    passwd_salt = ?,
-                       |    first_name = ?,
-                       |    last_name = ?,
-                       |    avatar_url = ?,
-                       |    last_modified_on = ?
-                       |WHERE id = ?
-                        """.stripMargin,
-                    email,
-                    passwdSalt,
-                    firstName,
-                    lastName,
-                    avatarUrl.orNull,
-                    U.nowUtcTs(),
-                    id
-                )
-
-            NCSql.delete("DELETE FROM nc_user_property WHERE user_id = ?", id)
-
-            addUserProperties(id, propsOpt, span)
-
-            n
-        }
-
-    /**
-      *
-      * @param id
-      * @param propsOpt
-      * @param parent Optional parent span.
-      */
-    private def addUserProperties(id: Long, propsOpt: Option[Map[String, 
String]], parent: Span): Unit =
-        startScopedSpan("addUserProperties", parent, "usrId" → id) { _ ⇒
-            propsOpt match {
-                case Some(props) ⇒
-                    val now = U.nowUtcTs()
-         
-                    props.foreach { case (k, v) ⇒
-                        NCSql.insert(
-                            s"""
-                               |INSERT INTO nc_user_property (id, user_id, 
property, value, created_on, last_modified_on)
-                               |VALUES(?, ?, ?, ?, ?, ?)
-                            """.stripMargin,
-                            usersPropsSeq.getAndIncrement(),
-                            id,
-                            k,
-                            v,
-                            now,
-                            now
-                        )
-                    }
-                case None ⇒ // No-op.
-            }
-        }
-
-    /**
-      *
-      * @param id
-      * @param propsOpt
-      * @param parent Optional parent span.
-      */
-    private def addCompanyProperties(id: Long, propsOpt: Option[Map[String, 
String]], parent: Span): Unit =
-        startScopedSpan("addCompanyProperties", parent, "companyId" → id) { _ ⇒
-            propsOpt match {
-                case Some(props) ⇒
-                    val now = U.nowUtcTs()
-
-                    props.foreach { case (k, v) ⇒
-                        NCSql.insert(
-                            s"""
-                               |INSERT INTO nc_company_property (id, 
company_id, property, value, created_on, last_modified_on)
-                               |VALUES(?, ?, ?, ?, ?, ?)
-                            """.stripMargin,
-                            compPropsSeq.getAndIncrement(),
-                            id,
-                            k,
-                            v,
-                            now,
-                            now
-                        )
-                    }
-                case None ⇒ // No-op.
-            }
+            NCSql.update(
+                s"""
+                   |UPDATE nc_user
+                   |SET
+                   |    email = ?,
+                   |    passwd_salt = ?,
+                   |    first_name = ?,
+                   |    last_name = ?,
+                   |    avatar_url = ?,
+                   |    properties_gzip = ?,
+                   |    last_modified_on = ?
+                   |WHERE id = ?
+                    """.stripMargin,
+                email,
+                passwdSalt,
+                firstName,
+                lastName,
+                avatarUrl.orNull,
+                gzip(propsOpt),
+                U.nowUtcTs(),
+                id
+            )
         }
 
     /**
@@ -387,11 +344,11 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
         city: Option[String],
         address: Option[String],
         postalCode: Option[String],
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Int =
         startScopedSpan("updateCompany", parent, "compId" → id) { _ ⇒
-            val res = NCSql.update(
+            NCSql.update(
                 s"""
                    |UPDATE nc_company
                    |SET
@@ -402,6 +359,7 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
                    |    city = ?,
                    |    address = ?,
                    |    postal_code = ?,
+                   |    properties_gzip = ?,
                    |    last_modified_on = ?
                    |WHERE id = ?
                     """.stripMargin,
@@ -412,15 +370,10 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
                 city.orNull,
                 address.orNull,
                 postalCode.orNull,
+                gzip(propsOpt),
                 U.nowUtcTs(),
                 id
             )
-
-            NCSql.delete("DELETE FROM nc_company_property WHERE company_id = 
?", id)
-
-            addCompanyProperties(id, propsOpt, parent)
-
-            res
         }
 
     /**
@@ -530,34 +483,6 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
         }
 
     /**
-      * Gets user properties for given ID.
-      *
-      * @param id User ID.
-      * @param parent Optional parent span.
-      * @return User properties.
-      *
-      */
-    @throws[NCE]
-    def getUserProperties(id: Long, parent: Span): Seq[NCUserPropertyMdo] =
-        startScopedSpan("getUserProperties", parent, "usrId" → id) { _ ⇒
-            NCSql.select[NCUserPropertyMdo]("SELECT * FROM nc_user_property 
WHERE user_id = ?", id)
-        }
-
-    /**
-      * Gets company properties for given ID.
-      *
-      * @param id User ID.
-      * @param parent Optional parent span.
-      * @return Company properties.
-      *
-      */
-    @throws[NCE]
-    def getCompanyProperties(id: Long, parent: Span): 
Seq[NCCompanyPropertyMdo] =
-        startScopedSpan("getCompanyProperties", parent, "usrId" → id) { _ ⇒
-            NCSql.select[NCCompanyPropertyMdo]("SELECT * FROM 
nc_company_property WHERE company_id = ?", id)
-        }
-
-    /**
       * Gets user properties for given external ID.
       *
       * @param companyId Company ID.
@@ -593,15 +518,9 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
       * @param parent Optional parent span.
       */
     @throws[NCE]
-    def getAllUsers(compId: Long, parent: Span): Map[NCUserMdo, 
Seq[NCUserPropertyMdo]] =
+    def getAllUsers(compId: Long, parent: Span): Seq[NCUserMdo] =
         startScopedSpan("getAllUsers", parent, "compId" → compId) { _ ⇒
-            val props = NCSql.select[NCUserPropertyMdo](
-                "SELECT * FROM nc_user_property WHERE user_id IN (SELECT id 
FROM nc_user WHERE company_id = ?)",
-                compId
-            ).groupBy(_.userId)
-         
-            NCSql.select[NCUserMdo]("SELECT * FROM nc_user WHERE company_id = 
?", compId).
-                map(p ⇒ p → props.getOrElse(p.id, Nil)).toMap
+            NCSql.select[NCUserMdo]("SELECT * FROM nc_user WHERE company_id = 
?", compId)
         }
 
     /**
@@ -643,7 +562,7 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
         city: Option[String],
         address: Option[String],
         postalCode: Option[String],
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Unit =
         startScopedSpan("addCompany", parent, "compId" → id, "name" → name, 
"tkn" → tkn) { _ ⇒
@@ -663,10 +582,11 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
                   |    postal_code,
                   |    auth_token,
                   |    auth_token_hash,
+                  |    properties_gzip,
                   |    created_on,
                   |    last_modified_on
                   |)
-                  | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                  | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
               """.stripMargin,
                 id,
                 name,
@@ -678,11 +598,10 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
                 postalCode.orNull,
                 tkn,
                 U.mkSha256Hash(tkn),
+                gzip(propsOpt),
                 now,
                 now
             )
-
-            addCompanyProperties(id, propsOpt, parent)
         }
 
     /**
@@ -711,7 +630,7 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
         avatarUrl: Option[String],
         passwdSalt: Option[String],
         isAdmin: Boolean,
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Unit = {
         require(usrExtId.isDefined ^ email.isDefined)
@@ -739,11 +658,12 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
                   |    email,
                   |    passwd_salt,
                   |    avatar_url,
+                  |    properties_gzip,
                   |    is_admin,
                   |    created_on,
                   |    last_modified_on
                   | )
-                  | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                  | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                 """.stripMargin,
                 id,
                 compId,
@@ -753,12 +673,11 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
                 email.orNull,
                 passwdSalt.orNull,
                 avatarUrl.orNull,
+                gzip(propsOpt),
                 isAdmin,
                 now,
                 now
             )
-
-            addUserProperties(id, propsOpt, span)
         }
     }
     
@@ -852,6 +771,7 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
       * @param errMsg
       * @param resType
       * @param resBody
+      * @param resMeta
       * @param intentId
       * @param tstamp
       * @param parent Optional parent span.
@@ -862,9 +782,11 @@ object NCSqlManager extends NCService with 
NCIgniteInstance {
         errMsg: String,
         resType: String,
         resBody: String,
+        resMeta: JavaMeta,
         intentId: String,
         tstamp: Timestamp,
-        parent: Span): Unit =
+        parent: Span
+    ): Unit =
         startScopedSpan("updateReadyProcessingLog", parent, "srvReqId" → 
srvReqId) { _ ⇒
             NCSql.update(
                 """
@@ -874,6 +796,7 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
                   |    error = ?,
                   |    res_type = ?,
                   |    res_body_gzip = ?,
+                  |    res_meta_gzip = ?,
                   |    intent_id = ?,
                   |    resp_tstamp = ?
                   |WHERE srv_req_id = ?
@@ -881,7 +804,8 @@ object NCSqlManager extends NCService with NCIgniteInstance 
{
                 QRY_READY.toString,
                 errMsg,
                 resType,
-                if (resBody == null) null else U.compress(resBody),
+                gzip(resBody),
+                gzip(resMeta),
                 intentId,
                 tstamp,
                 srvReqId
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/user/NCUserManager.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/user/NCUserManager.scala
index 4471b31..7107085 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/user/NCUserManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/user/NCUserManager.scala
@@ -27,7 +27,7 @@ import org.apache.nlpcraft.common.config.NCConfigurable
 import org.apache.nlpcraft.common.{NCService, _}
 import org.apache.nlpcraft.server.ignite.NCIgniteHelpers._
 import org.apache.nlpcraft.server.ignite.NCIgniteInstance
-import org.apache.nlpcraft.server.mdo.{NCUserMdo, NCUserPropertyMdo}
+import org.apache.nlpcraft.server.mdo.NCUserMdo
 import org.apache.nlpcraft.server.sql.{NCSql, NCSqlManager}
 import org.apache.nlpcraft.server.tx.NCTxManager
 
@@ -190,7 +190,7 @@ object NCUserManager extends NCService with 
NCIgniteInstance {
       * @param parent Optional parent span.
       */
     @throws[NCE]
-    def getAllUsers(compId: Long, parent: Span = null): Map[NCUserMdo, 
Seq[NCUserPropertyMdo]] =
+    def getAllUsers(compId: Long, parent: Span = null): Seq[NCUserMdo] =
         NCSql.sql {
             NCSqlManager.getAllUsers(compId, parent)
         }
@@ -313,20 +313,6 @@ object NCUserManager extends NCService with 
NCIgniteInstance {
         }
 
     /**
-      * Gets user properties for given user ID.
-      *
-      * @param id User ID.
-      * @param parent Optional parent span.
-      */
-    @throws[NCE]
-    def getUserProperties(id: Long, parent: Span = null): 
Seq[NCUserPropertyMdo] =
-        startScopedSpan("getUserProperties", parent, "usrId" → id) { span ⇒
-            NCSql.sql {
-                NCSqlManager.getUserProperties(id, span)
-            }
-        }
-
-    /**
       *
       * @param email User email (as username).
       * @param passwd User password.
@@ -391,7 +377,7 @@ object NCUserManager extends NCService with 
NCIgniteInstance {
         firstName: String,
         lastName: String,
         avatarUrl: Option[String],
-        props: Option[Map[String, String]],
+        props: Option[String],
         parent: Span = null
     ): Unit =
         startScopedSpan("updateUser", parent, "usrId" → id) { span ⇒
@@ -488,7 +474,7 @@ object NCUserManager extends NCService with 
NCIgniteInstance {
         lastName: String,
         avatarUrl: Option[String],
         isAdmin: Boolean,
-        props: Option[Map[String, String]],
+        props: Option[String],
         usrExtIdOpt: Option[String],
         parent: Span = null
     ): Long =
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/server/rest/NCRestSpec.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/server/rest/NCRestSpec.scala
index 98bc498..098b582 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/server/rest/NCRestSpec.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/server/rest/NCRestSpec.scala
@@ -48,6 +48,9 @@ object NCRestSpec {
             val code = resp.getStatusLine.getStatusCode
             val js = mkJs(code, resp.getEntity)
 
+            if (js == null)
+                throw new RuntimeException(s"Unexpected response [code=$code, 
response=$js]")
+
             code match {
                 case 200 ⇒ GSON.fromJson(js, TYPE_RESP)
 
diff --git a/openapi/nlpcraft_swagger.yml b/openapi/nlpcraft_swagger.yml
index f8b0114..fbf0f1c 100644
--- a/openapi/nlpcraft_swagger.yml
+++ b/openapi/nlpcraft_swagger.yml
@@ -871,7 +871,7 @@ paths:
                 maxLength: 512000
               properties:
                 type: object
-                description: <em>Optional.</em> Additional user properties.
+                description: <em>Optional.</em> Additional user properties 
with maximum JSON length of 512000 bytes.
                 additionalProperties:
                   type: string
       responses:
@@ -1064,7 +1064,7 @@ paths:
                 description: Admin flag.
               properties:
                 type: object
-                description: <em>Optional.</em> Additional user properties.
+                description: <em>Optional.</em> Additional user properties 
with maximum JSON length of 512000 bytes.
                 additionalProperties:
                   type: string
               usrExtId:
@@ -1240,7 +1240,7 @@ paths:
                 maxLength: 512000
               properties:
                 type: object
-                description: <em>Optional.</em> Additional company properties.
+                description: <em>Optional.</em> Additional company properties 
with maximum JSON length of 512000 bytes.
                 additionalProperties:
                   type: string
       responses:
@@ -1323,7 +1323,7 @@ paths:
                 maxLength: 512
               properties:
                 type: object
-                description: <em>Optional.</em> Additional company properties.
+                description: <em>Optional.</em> Additional company properties 
with maximum JSON length of 512000 bytes.
                 additionalProperties:
                   type: string
       responses:
diff --git a/sql/mysql/drop_schema.sql b/sql/mysql/drop_schema.sql
index fba01aa..e1ead27 100644
--- a/sql/mysql/drop_schema.sql
+++ b/sql/mysql/drop_schema.sql
@@ -18,9 +18,7 @@
 USE nlpcraft;
 
 DROP TABLE IF EXISTS proc_log;
-DROP TABLE IF EXISTS nc_user_property;
 DROP TABLE IF EXISTS nc_user;
-DROP TABLE IF EXISTS nc_company_property;
 DROP TABLE IF EXISTS nc_company;
 DROP TABLE IF EXISTS passwd_pool;
 DROP TABLE IF EXISTS feedback;
diff --git a/sql/mysql/schema.sql b/sql/mysql/schema.sql
index cac0bf8..9022ab6 100644
--- a/sql/mysql/schema.sql
+++ b/sql/mysql/schema.sql
@@ -39,6 +39,7 @@ CREATE TABLE nc_company (
     postal_code VARCHAR(32),
     auth_token VARCHAR(64) NOT NULL,
     auth_token_hash VARCHAR(64) NOT NULL,
+    properties_gzip TEXT NULL,
     created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3)
 );
@@ -48,19 +49,6 @@ CREATE UNIQUE INDEX nc_company_idx_2 ON 
nc_company(auth_token);
 CREATE UNIQUE INDEX nc_company_idx_3 ON nc_company(auth_token_hash);
 
 --
--- Company properties table.
---
-CREATE TABLE nc_company_property (
-    id SERIAL PRIMARY KEY,
-    company_id BIGINT UNSIGNED NOT NULL,
-    property VARCHAR(64) NOT NULL,
-    value VARCHAR(512) NULL,
-    created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    FOREIGN KEY (company_id) REFERENCES nc_company(id)
-);
-
---
 -- User table.
 --
 CREATE TABLE nc_user (
@@ -73,6 +61,7 @@ CREATE TABLE nc_user (
     last_name VARCHAR(64) NULL,
     is_admin BOOLEAN NOT NULL, -- Whether or not created with admin token.
     passwd_salt VARCHAR(64) NULL,
+    properties_gzip TEXT NULL,
     created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     FOREIGN KEY (company_id) REFERENCES nc_company(id)
@@ -82,19 +71,6 @@ CREATE UNIQUE INDEX nc_user_idx_1 ON nc_user(email);
 CREATE UNIQUE INDEX nc_user_idx_2 ON nc_user(company_id, ext_id);
 
 --
--- User properties table.
---
-CREATE TABLE nc_user_property (
-    id SERIAL PRIMARY KEY,
-    user_id BIGINT UNSIGNED NOT NULL,
-    property VARCHAR(64) NOT NULL,
-    value VARCHAR(512) NULL,
-    created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    FOREIGN KEY (user_id) REFERENCES nc_user(id)
-);
-
---
 -- Pool of password hashes.
 --
 CREATE TABLE passwd_pool (
@@ -122,6 +98,7 @@ CREATE TABLE proc_log (
     -- Result parts.
     res_type VARCHAR(32) NULL,
     res_body_gzip TEXT NULL, -- GZIP-ed result body.
+    res_body_meta TEXT NULL, -- GZIP-ed result meta.
     intent_id VARCHAR(256) NULL,
     error TEXT NULL,
     -- Probe information for this request.
diff --git a/sql/oracle/drop_schema.sql b/sql/oracle/drop_schema.sql
index 652bdb8..78bd027 100644
--- a/sql/oracle/drop_schema.sql
+++ b/sql/oracle/drop_schema.sql
@@ -24,14 +24,6 @@ EXCEPTION WHEN OTHERS THEN
 END;
 
 BEGIN
-  EXECUTE IMMEDIATE 'DROP TABLE nc_user_property';
-EXCEPTION WHEN OTHERS THEN
-  IF SQLCODE != -942 THEN
-    RAISE;
-  END IF;
-END;
-
-BEGIN
   EXECUTE IMMEDIATE 'DROP TABLE nc_user';
 EXCEPTION WHEN OTHERS THEN
   IF SQLCODE != -942 THEN
@@ -40,14 +32,6 @@ EXCEPTION WHEN OTHERS THEN
 END;
 
 BEGIN
-  EXECUTE IMMEDIATE 'DROP TABLE nc_company_property';
-EXCEPTION WHEN OTHERS THEN
-  IF SQLCODE != -942 THEN
-    RAISE;
-END IF;
-END;
-
-BEGIN
   EXECUTE IMMEDIATE 'DROP TABLE nc_company';
 EXCEPTION WHEN OTHERS THEN
   IF SQLCODE != -942 THEN
diff --git a/sql/oracle/schema.sql b/sql/oracle/schema.sql
index 1ad495c..da243b9 100644
--- a/sql/oracle/schema.sql
+++ b/sql/oracle/schema.sql
@@ -37,6 +37,7 @@ CREATE TABLE nc_company (
     postal_code VARCHAR2(32),
     auth_token VARCHAR2(64) NOT NULL,
     auth_token_hash VARCHAR2(64) NOT NULL,
+    properties_gzip CLOB NULL,
     created_on DATE DEFAULT sysdate NOT NULL,
     last_modified_on DATE DEFAULT sysdate NOT NULL
 );
@@ -46,21 +47,6 @@ CREATE UNIQUE INDEX nc_company_idx_2 ON 
nc_company(auth_token);
 CREATE UNIQUE INDEX nc_company_idx_3 ON nc_company(auth_token_hash);
 
 --
--- Company properties table.
---
-CREATE TABLE nc_company_property (
-    id NUMBER PRIMARY KEY,
-    company_id NUMBER NOT NULL,
-    property VARCHAR2(64) NOT NULL,
-    value VARCHAR2(512) NULL,
-    created_on DATE DEFAULT sysdate NOT NULL,
-    last_modified_on DATE DEFAULT sysdate NOT NULL,
-    CONSTRAINT fk_company_id_company_property FOREIGN KEY (company_id) 
REFERENCES nc_company(id)
-);
-
-CREATE INDEX nc_company_property_idx1 ON nc_company_property(company_id);
-
---
 -- User table.
 --
 CREATE TABLE nc_user (
@@ -73,6 +59,7 @@ CREATE TABLE nc_user (
     last_name VARCHAR2(64) NULL,
     is_admin NUMBER(1) NOT NULL, -- Whether or not created with admin token.
     passwd_salt VARCHAR2(64) NULL,
+    properties_gzip CLOB NULL,
     created_on DATE DEFAULT sysdate NOT NULL,
     last_modified_on DATE DEFAULT sysdate NOT NULL,
     CONSTRAINT fk_company_id_user FOREIGN KEY (company_id) REFERENCES 
nc_company(id)
@@ -83,21 +70,6 @@ CREATE UNIQUE INDEX nc_user_idx_2 ON nc_user(company_id, 
ext_id);
 CREATE INDEX nc_user_idx_3 ON nc_user(company_id);
 
 --
--- User properties table.
---
-CREATE TABLE nc_user_property (
-    id NUMBER PRIMARY KEY,
-    user_id NUMBER NOT NULL,
-    property VARCHAR2(64) NOT NULL,
-    value VARCHAR2(512) NULL,
-    created_on DATE DEFAULT sysdate NOT NULL,
-    last_modified_on DATE DEFAULT sysdate NOT NULL,
-    CONSTRAINT fk_user_id_user_property FOREIGN KEY (user_id) REFERENCES 
nc_user(id)
-);
-
-CREATE INDEX nc_user_property_idx1 ON nc_user_property(user_id);
-
---
 -- Pool of password hashes.
 --
 CREATE TABLE passwd_pool (
@@ -125,6 +97,7 @@ CREATE TABLE proc_log (
     -- Result parts.
     res_type VARCHAR2(32) NULL,
     res_body_gzip CLOB NULL, -- GZIP-ed result body.
+    res_body_meta CLOB NULL, -- GZIP-ed result meta.
     intent_id VARCHAR2(256) NULL,
     error CLOB NULL,
     -- Probe information for this request.
diff --git a/sql/postgres/drop_schema.sql b/sql/postgres/drop_schema.sql
index b1ce0b9..f985bae 100644
--- a/sql/postgres/drop_schema.sql
+++ b/sql/postgres/drop_schema.sql
@@ -16,9 +16,7 @@
 --
 
 DROP TABLE IF EXISTS proc_log;
-DROP TABLE IF EXISTS nc_user_property;
 DROP TABLE IF EXISTS nc_user;
-DROP TABLE IF EXISTS nc_company_property;
 DROP TABLE IF EXISTS nc_company;
 DROP TABLE IF EXISTS passwd_pool;
 DROP TABLE IF EXISTS feedback;
\ No newline at end of file
diff --git a/sql/postgres/schema.sql b/sql/postgres/schema.sql
index 561418e..8329ed7 100644
--- a/sql/postgres/schema.sql
+++ b/sql/postgres/schema.sql
@@ -37,6 +37,7 @@ CREATE TABLE nc_company (
     postal_code VARCHAR(32),
     auth_token VARCHAR(64) NOT NULL,
     auth_token_hash VARCHAR(64) NOT NULL,
+    properties_gzip TEXT NULL,
     created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3)
 );
@@ -46,21 +47,6 @@ CREATE UNIQUE INDEX nc_company_idx_2 ON 
nc_company(auth_token);
 CREATE UNIQUE INDEX nc_company_idx_3 ON nc_company(auth_token_hash);
 
 --
--- Company properties table.
---
-CREATE TABLE nc_company_property (
-    id SERIAL PRIMARY KEY,
-    company_id BIGINT NOT NULL,
-    property VARCHAR(64) NOT NULL,
-    value VARCHAR(512) NULL,
-    created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    FOREIGN KEY (company_id) REFERENCES nc_company(id)
-);
-
-CREATE INDEX nc_company_property_idx1 ON nc_company_property(company_id);
-
---
 --
 -- User table.
 --
@@ -74,6 +60,7 @@ CREATE TABLE nc_user (
     last_name VARCHAR(64) NULL,
     is_admin BOOL NOT NULL, -- Whether or not created with admin token.
     passwd_salt VARCHAR(64) NULL,
+    properties_gzip TEXT NULL,
     created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     FOREIGN KEY (company_id) REFERENCES nc_company(id)
@@ -92,21 +79,6 @@ CREATE TABLE passwd_pool (
 );
 
 --
--- User properties table.
---
-CREATE TABLE nc_user_property (
-    id SERIAL PRIMARY KEY,
-    user_id BIGINT NOT NULL,
-    property VARCHAR(64) NOT NULL,
-    value VARCHAR(512) NULL,
-    created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    FOREIGN KEY (user_id) REFERENCES nc_user(id)
-);
-
-CREATE INDEX nc_user_property_idx1 ON nc_user_property(user_id);
-
---
 -- Processing log.
 --
 CREATE TABLE proc_log (
@@ -126,6 +98,7 @@ CREATE TABLE proc_log (
     -- Result parts.
     res_type VARCHAR(32) NULL,
     res_body_gzip TEXT NULL, -- GZIP-ed result body.
+    res_body_meta TEXT NULL, -- GZIP-ed result body.
     intent_id VARCHAR(256) NULL,
     error TEXT NULL,
     -- Probe information for this request.

Reply via email to