diff --git a/entity/AttributeRelationTree_entity/recordcontainers/jdito/contentProcess.js b/entity/AttributeRelationTree_entity/recordcontainers/jdito/contentProcess.js
index e6b4f8a5171e30c73818b120f6f4de53eabef89f..3bc714d701a23a938e5cca97131fd2ee59a579cb 100644
--- a/entity/AttributeRelationTree_entity/recordcontainers/jdito/contentProcess.js
+++ b/entity/AttributeRelationTree_entity/recordcontainers/jdito/contentProcess.js
@@ -1,44 +1,71 @@
-import("system.vars");
-import("system.result");
-import("system.db");
-import("Attribute_lib");
-import("Sql_lib");
-
-var objectType = vars.get("$param.ObjectType_param");
-var rowId = vars.get("$param.ObjectRowId_param");
-var attributeObj = {};
-var allAttributes = [];
-var sqlSelect = "select AB_ATTRIBUTEID, ATTRIBUTE_PARENT_ID, ATTRIBUTE_NAME, ATTRIBUTE_LEVEL from AB_ATTRIBUTE";
-
-var attributeValues = AttributeRelationUtils.getAllAttributes(rowId, objectType, false, true);
-
-_fetchAttributes(attributeValues.map(function (row) {return row[1]}));
-
-allAttributes = allAttributes
-    .sort(function (a, b) {return a[3] - b[3];}) //sort by level to make sure parents are added first
-    .map(function (row) {return [row[0], row[1], row[2]];}) //remove level from array
-    .concat(attributeValues);
-result.object(allAttributes);
-
-function _fetchAttributes (pAttributeIds)
-{
-    var condition = SqlCondition.begin();
-    var nextIds = [];
-    pAttributeIds.forEach(function (id)
-    {
-        if (!(id in this))
-            condition.orPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", id);
-    }, attributeObj);
-    db.table(condition.buildSql(sqlSelect, "1=2"))
-        .forEach(function (row)
-            {
-                this[row[0]] = true;
-                if (row[1])
-                    nextIds.push(row[1]);
-                else
-                    row[1] = null;
-                allAttributes.push(row);
-            }, attributeObj);
-    if (nextIds.length)
-        _fetchAttributes(nextIds);
+import("system.vars");
+import("system.result");
+import("system.db");
+import("Attribute_lib");
+import("Sql_lib");
+
+var objectType = vars.get("$param.ObjectType_param");
+var rowId = vars.get("$param.ObjectRowId_param");
+var attributeObj = {};
+var allAttributes = [];
+var sqlSelect = "select AB_ATTRIBUTEID, ATTRIBUTE_PARENT_ID, ATTRIBUTE_NAME from AB_ATTRIBUTE";
+
+var attributeValues = AttributeRelationUtils.getAllAttributes(rowId, objectType, false, true);
+
+_fetchAttributes(attributeValues.map(function (row) {return row[1]}));
+
+allAttributes = _sortArrayForTree(allAttributes).concat(attributeValues);
+result.object(allAttributes);
+
+function _fetchAttributes (pAttributeIds)
+{
+    var condition = SqlCondition.begin();
+    var nextIds = [];
+    pAttributeIds.forEach(function (id)
+    {
+        if (!(id in this))
+            condition.orPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", id);
+    }, attributeObj);
+    db.table(condition.buildSql(sqlSelect, "1=2"))
+        .forEach(function (row)
+            {
+                this[row[0]] = true;
+                if (row[1])
+                    nextIds.push(row[1]);
+                else
+                    row[1] = null;
+                allAttributes.push(row);
+            }, attributeObj);
+    if (nextIds.length)
+        _fetchAttributes(nextIds);
+}
+
+function _sortArrayForTree (pArray) 
+{
+    var rows = {};
+    var allIds = {};
+    var idIndex = 1;
+    var parentIdIndex = 4;
+    
+    pArray.forEach(function (row) {allIds[row[idIndex]] = true;});
+    
+    var index = 0;
+    
+    for (let itemsAdded = true; itemsAdded; itemsAdded = oldIndex != index)
+    {
+        var oldIndex = index;
+        pArray.forEach(function (row)
+        {
+            if (!(row[idIndex] in this) && (row[parentIdIndex] in this || !allIds[row[parentIdIndex]]))
+                this[row[idIndex]] = {
+                    data : row,
+                    index : index++
+                };
+        }, rows);
+    }
+    var sortedArray = new Array(Object.keys(rows).length);
+    for (let i in rows)
+        sortedArray[rows[i].index] = rows[i].data;
+    
+    return sortedArray;
 }
\ No newline at end of file
diff --git a/entity/Attribute_entity/Attribute_entity.aod b/entity/Attribute_entity/Attribute_entity.aod
index 27bd01462c6cfe52e580468756201a33b84bd200..a33899be37800f733596dee69847ff941dcd9efc 100644
--- a/entity/Attribute_entity/Attribute_entity.aod
+++ b/entity/Attribute_entity/Attribute_entity.aod
@@ -7,7 +7,7 @@
   <afterUiInit>%aditoprj%/entity/Attribute_entity/afterUiInit.js</afterUiInit>
   <iconId>VAADIN:TAG</iconId>
   <titleProcess>%aditoprj%/entity/Attribute_entity/titleProcess.js</titleProcess>
-  <recordContainer>db</recordContainer>
+  <recordContainer>jdito</recordContainer>
   <entityFields>
     <entityProvider>
       <name>#PROVIDER</name>
@@ -282,57 +282,28 @@
       <name>IMAGE</name>
       <contentType>IMAGE</contentType>
     </entityField>
+    <entityField>
+      <name>UID</name>
+    </entityField>
   </entityFields>
   <recordContainers>
-    <dbRecordContainer>
-      <name>db</name>
-      <alias>Data_alias</alias>
-      <conditionProcess>%aditoprj%/entity/Attribute_entity/recordcontainers/db/conditionProcess.js</conditionProcess>
-      <orderClauseProcess>%aditoprj%/entity/Attribute_entity/recordcontainers/db/orderClauseProcess.js</orderClauseProcess>
-      <onDBUpdate>%aditoprj%/entity/Attribute_entity/recordcontainers/db/onDBUpdate.js</onDBUpdate>
-      <onDBDelete>%aditoprj%/entity/Attribute_entity/recordcontainers/db/onDBDelete.js</onDBDelete>
-      <linkInformation>
-        <linkInformation>
-          <name>b5fd38e1-b315-4e66-bd68-569dfc04710c</name>
-          <tableName>AB_ATTRIBUTE</tableName>
-          <primaryKey>AB_ATTRIBUTEID</primaryKey>
-          <isUIDTable v="true" />
-        </linkInformation>
-      </linkInformation>
-      <recordFieldMappings>
-        <dbRecordFieldMapping>
-          <name>AB_ATTRIBUTEID.value</name>
-          <recordfield>AB_ATTRIBUTE.AB_ATTRIBUTEID</recordfield>
-        </dbRecordFieldMapping>
-        <dbRecordFieldMapping>
-          <name>ATTRIBUTE_PARENT_ID.value</name>
-          <recordfield>AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID</recordfield>
-        </dbRecordFieldMapping>
-        <dbRecordFieldMapping>
-          <name>ATTRIBUTE_TYPE.value</name>
-          <recordfield>AB_ATTRIBUTE.ATTRIBUTE_TYPE</recordfield>
-        </dbRecordFieldMapping>
-        <dbRecordFieldMapping>
-          <name>ATTRIBUTE_ACTIVE.value</name>
-          <recordfield>AB_ATTRIBUTE.ATTRIBUTE_ACTIVE</recordfield>
-        </dbRecordFieldMapping>
-        <dbRecordFieldMapping>
-          <name>ATTRIBUTE_TYPE.displayValue</name>
-          <expression>%aditoprj%/entity/Attribute_entity/recordcontainers/db/recordfieldmappings/attribute_type.displayvalue/expression.js</expression>
-        </dbRecordFieldMapping>
-        <dbRecordFieldMapping>
-          <name>ATTRIBUTE_NAME.value</name>
-          <recordfield>AB_ATTRIBUTE.ATTRIBUTE_NAME</recordfield>
-        </dbRecordFieldMapping>
-        <dbRecordFieldMapping>
-          <name>ATTRIBUTE_LEVEL.value</name>
-          <recordfield>AB_ATTRIBUTE.ATTRIBUTE_LEVEL</recordfield>
-        </dbRecordFieldMapping>
-        <dbRecordFieldMapping>
-          <name>KEYWORD_CONTAINER.value</name>
-          <recordfield>AB_ATTRIBUTE.KEYWORD_CONTAINER</recordfield>
-        </dbRecordFieldMapping>
-      </recordFieldMappings>
-    </dbRecordContainer>
+    <jDitoRecordContainer>
+      <name>jdito</name>
+      <jDitoRecordAlias>Data_alias</jDitoRecordAlias>
+      <contentProcess>%aditoprj%/entity/Attribute_entity/recordcontainers/jdito/contentProcess.js</contentProcess>
+      <onInsert>%aditoprj%/entity/Attribute_entity/recordcontainers/jdito/onInsert.js</onInsert>
+      <onUpdate>%aditoprj%/entity/Attribute_entity/recordcontainers/jdito/onUpdate.js</onUpdate>
+      <onDelete>%aditoprj%/entity/Attribute_entity/recordcontainers/jdito/onDelete.js</onDelete>
+      <recordFields>
+        <element>UID.value</element>
+        <element>AB_ATTRIBUTEID.value</element>
+        <element>ATTRIBUTE_ACTIVE.value</element>
+        <element>ATTRIBUTE_NAME.value</element>
+        <element>ATTRIBUTE_PARENT_ID.value</element>
+        <element>ATTRIBUTE_TYPE.value</element>
+        <element>ATTRIBUTE_TYPE.displayValue</element>
+        <element>KEYWORD_CONTAINER.value</element>
+      </recordFields>
+    </jDitoRecordContainer>
   </recordContainers>
 </entity>
diff --git a/entity/Attribute_entity/entityfields/attribute_level/valueProcess.js b/entity/Attribute_entity/entityfields/attribute_level/valueProcess.js
deleted file mode 100644
index e91a822698ae74d9b594ed31998920eb6f639c74..0000000000000000000000000000000000000000
--- a/entity/Attribute_entity/entityfields/attribute_level/valueProcess.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import("system.db");
-import("system.neon");
-import("system.result");
-import("system.vars");
-import("Sql_lib");
-
-if (vars.get("$sys.recordstate") == neon.OPERATINGSTATE_NEW || vars.get("$sys.recordstate") == neon.OPERATINGSTATE_EDIT)
-    if (vars.get("$field.ATTRIBUTE_PARENT_ID") != "")
-    {
-        var level = db.cell(SqlCondition.begin()
-            .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", vars.get("$field.ATTRIBUTE_PARENT_ID"))
-            .buildSql("select ATTRIBUTE_LEVEL from AB_ATTRIBUTE"));
-        result.string(parseInt(level) + 1);
-    }
-    else
-        result.string("0");
diff --git a/entity/Attribute_entity/recordcontainers/db/conditionProcess.js b/entity/Attribute_entity/recordcontainers/db/conditionProcess.js
deleted file mode 100644
index ed323a5c82da868f9b3ae4deafd54ad035520109..0000000000000000000000000000000000000000
--- a/entity/Attribute_entity/recordcontainers/db/conditionProcess.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import("system.db");
-import("system.vars");
-import("system.result");
-import("Sql_lib");
-import("Attribute_lib");
-
-var condition = "1 = 2";
-
-var getGroups = vars.exists("$param.GetGroups_param") && vars.get("$param.GetGroups_param");
-var objectType = vars.exists("$param.ObjectType_param") && vars.get("$param.ObjectType_param");
-if (getGroups)
-{
-    //this is for the selection of the superordinate attribute, this condition
-    //filters out the own id and the children to prevent loops
-    condition = db.translateCondition(SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.GROUP)
-        .andPrepareVars("AB_ATTRIBUTE.AB_ATTRIBUTEID", "$param.AttrParentId_param", "# != ?")
-        .and("AB_ATTRIBUTE.AB_ATTRIBUTEID not in ('" + AttributeUtil.getAllChildren(vars.getString("$param.AttrParentId_param")).join("','") + "')")
-        .build());
-}
-else if (objectType)  //if there's an objectType, it comes from the AttributeRelation entity
-{
-    var filteredAttributes = [];
-    if (vars.exists("$param.FilteredAttributeIds_param") && vars.get("$param.FilteredAttributeIds_param"))
-        filteredAttributes = JSON.parse(vars.get("$param.FilteredAttributeIds_param"));
-    
-    var ids = AttributeUtil.getPossibleAttributes(objectType, false, filteredAttributes);
-    condition = "AB_ATTRIBUTE.AB_ATTRIBUTEID in ('" + ids.join("','") + "')";
-} 
-else 
-{
-    var type = vars.exists("$param.AttrParentType_param") && vars.get("$param.AttrParentType_param");
-    if (type == $AttributeTypes.COMBO)
-        condition = SqlCondition.begin()
-            .andPrepareVars("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", "$param.AttrParentId_param")
-            .build();
-
-    var parentId = vars.exists("$param.AttrParentId_param") && vars.get("$param.AttrParentId_param");
-    if (parentId)
-        condition = "AB_ATTRIBUTE.AB_ATTRIBUTEID in ('" + AttributeUtil.getAllChildren(vars.getString("$param.AttrParentId_param")).join("','") + "')";
-    else if (!type)
-        condition = "";
-    
-}
-
-result.string(condition);
diff --git a/entity/Attribute_entity/recordcontainers/db/onDBDelete.js b/entity/Attribute_entity/recordcontainers/db/onDBDelete.js
deleted file mode 100644
index 7d1eb8b477436101b8cacb2fa7aa00241708adae..0000000000000000000000000000000000000000
--- a/entity/Attribute_entity/recordcontainers/db/onDBDelete.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import("system.vars");
-import("system.db");
-import("Sql_lib");
-import("Attribute_lib");
-
-var attributeId = vars.get("$field.AB_ATTRIBUTEID");
-
-var childIds = AttributeUtil.getAllChildren(attributeId).concat(attributeId);
-
-var condition = SqlCondition.begin()
-    .and("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID in ('" + childIds.join("','") + "')")
-    .build();
-
-//delete all entries in AB_ATTRIBUTEUSAGE belonging to the attribute to avoid unrelated entries
-db.deleteData("AB_ATTRIBUTEUSAGE", condition);
-
-condition = SqlCondition.begin()
-    .and("AB_ATTRIBUTERELATION.AB_ATTRIBUTE_ID in ('" + childIds.join("','") + "')")
-    .build();
-
-//delete all entries in AB_ATTRIBUTERELATION for the attributes
-db.deleteData("AB_ATTRIBUTERELATION", condition);
-
-condition = SqlCondition.begin()
-    .and("AB_ATTRIBUTE.AB_ATTRIBUTEID in ('" + childIds.join("','") + "')")
-    .build();
-
-//delete all attribute children
-db.deleteData("AB_ATTRIBUTE", condition);
diff --git a/entity/Attribute_entity/recordcontainers/db/onDBUpdate.js b/entity/Attribute_entity/recordcontainers/db/onDBUpdate.js
deleted file mode 100644
index 38704929f969ef673a7fafd41ac71a1fa2c05282..0000000000000000000000000000000000000000
--- a/entity/Attribute_entity/recordcontainers/db/onDBUpdate.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import("system.db");
-import("system.neon");
-import("system.vars");
-import("Attribute_lib");
-
-var level = parseInt(vars.get("$field.ATTRIBUTE_LEVEL"));
-
-var table = "AB_ATTRIBUTE";
-var columns = ["ATTRIBUTE_LEVEL"];
-var types = db.getColumnTypes(table, columns);
-var toUpdate = [];
-
-var attributes = [vars.get("$field.AB_ATTRIBUTEID")];
-while (attributes.length > 0)
-{
-    var condition = SqlCondition.begin()
-        .and("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID in ('" + attributes.join("','") + "')");
-
-    toUpdate.push([table, columns, types, [String(++level)], condition.build()]);
-
-    attributes = db.array(db.COLUMN, SqlCondition.begin()
-        .and("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID in ('" + attributes.join("','") + "')")
-        .buildSql("select AB_ATTRIBUTEID from AB_ATTRIBUTE")
-    );
-}
-
-db.updates(toUpdate);
diff --git a/entity/Attribute_entity/recordcontainers/db/orderClauseProcess.js b/entity/Attribute_entity/recordcontainers/db/orderClauseProcess.js
deleted file mode 100644
index 003688a8bb9231d8f68eada88a41012db8d2ac2c..0000000000000000000000000000000000000000
--- a/entity/Attribute_entity/recordcontainers/db/orderClauseProcess.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import("system.db");
-import("system.result");
-result.object({
-    "AB_ATTRIBUTE.ATTRIBUTE_LEVEL" : db.ASCENDING,
-    "AB_ATTRIBUTE.ATTRIBUTE_NAME" : db.ASCENDING
-});
\ No newline at end of file
diff --git a/entity/Attribute_entity/recordcontainers/db/recordfieldmappings/attribute_type.displayvalue/expression.js b/entity/Attribute_entity/recordcontainers/db/recordfieldmappings/attribute_type.displayvalue/expression.js
deleted file mode 100644
index 70d91a13284d4fe84c3ac11898f9818d944f46a2..0000000000000000000000000000000000000000
--- a/entity/Attribute_entity/recordcontainers/db/recordfieldmappings/attribute_type.displayvalue/expression.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import("system.result");
-import("Keyword_lib");
-import("KeywordRegistry_basic");
-
-var sql = KeywordUtils.getResolvedTitleSqlPart($KeywordRegistry.attributeType(), "AB_ATTRIBUTE.ATTRIBUTE_TYPE");
-result.string(sql);
\ No newline at end of file
diff --git a/others/db_changes/Data_alias/basic/2019.2/AttributeKeyword.xml b/others/db_changes/Data_alias/basic/2019.2/AttributeKeyword.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/process/Attribute_lib/process.js b/process/Attribute_lib/process.js
index af3d24d9585b86cd16d243fb69b02a0087e47aaf..4833062c4d4339456153be1f9e59a7978ce05498 100644
--- a/process/Attribute_lib/process.js
+++ b/process/Attribute_lib/process.js
@@ -50,8 +50,8 @@ AttributeUtil.getPossibleAttributes = function (pObjectType, pIncludeGroups, pFi
         attrCond.andSqlCondition(filteredIdsCondition);
     }
 
-//    if (!pIncludeGroups)
-//        attrCond.and("ATTRIBUTE_TYPE != '" + $AttributeTypes.GROUP + "'");
+    if (!pIncludeGroups)
+        attrCond.and("ATTRIBUTE_TYPE != '" + $AttributeTypes.GROUP + "'");
     
     var attributes = db.array(db.COLUMN, attrCond.buildSql(attrSql));
     
diff --git a/process/JditoFilter_lib/process.js b/process/JditoFilter_lib/process.js
index 64d5ea5a3107407acc64475b059ccb2a721858a7..47338a2ea2b09bb468ef588daa1784a150f5fad0 100644
--- a/process/JditoFilter_lib/process.js
+++ b/process/JditoFilter_lib/process.js
@@ -1,112 +1,199 @@
-
-/**
- * object for filtering records
- * 
- * @param {Array} pColumns the column names
- * @param {String|Object} pFilter the filter object
- */
-function JditoFilter (pColumns, pFilter) 
-{
-    var columnMap = {};
-    pColumns.forEach(function (row, i)
-    {
-        columnMap[row] = i;
-    });
-    this._columnMap = columnMap;
-    
-    if (pFilter.length) //check if pFilter is a string
-        pFilter = JSON.parse(pFilter);
-    
-    this._operator = pFilter.operator;
-    this._filters = pFilter.childs;
-}
-
-/**
- * tests the given row if it matches the filter
- * 
- * @param {Array} pRow one record
- * 
- * @return {boolean} true, if it matches the condition
- */
-JditoFilter.prototype.checkRecord = function (pRow)
-{
-    if (this._filters.length == 0)
-        return true;
-    
-    if (this._operator == "AND")
-        return this._filters.every(_testFn, this);
-    
-    return this._filters.some(_testFn, this);
-    
-    function _testFn (pFilter)
-    {
-        let value = pRow[this._columnMap[pFilter.name]];
-        return this._testValue(value, (pFilter.key || pFilter.value), pFilter.operator);
-    }
-}
-
-/**
- * compares two values with the given operator
- */
-JditoFilter.prototype._testValue = function (pRowValue, pFilterValue, pOperator)
-{
-    switch (pOperator)
-    {
-        case "CONTAINS":
-            return (new RegExp(pFilterValue)).test(pRowValue);
-        case "CONTAINSNOT":
-            return !(new RegExp(pFilterValue)).test(pRowValue);
-        case "STARTSWITH":
-            return (new RegExp("^" + pFilterValue)).test(pRowValue);
-        case "ENDSWITH":
-            return (new RegExp(pFilterValue + "$")).test(pRowValue);
-        case "EQUAL":
-            return (new RegExp("^" + pFilterValue + "$")).test(pRowValue);
-        case "NOT_EQUAL":
-            return !(new RegExp("^" + pFilterValue + "$")).test(pRowValue);
-        case "LESS":
-            return pRowValue < pFilterValue;
-        case "LESS_OR_EQUAL":
-            return pRowValue <= pFilterValue;
-        case "GREATER":
-            return pRowValue > pFilterValue;
-        case "GREATER_OR_EQUAL":
-            return pRowValue >= pFilterValue;
-        case "ISNULL":
-            return pRowValue == "";
-        case "ISNOTNULL":
-            return pRowValue != "";
-    }
-}
-
-/**
- * Provides functions for using the filter with jdito recordcontainers. You should only use this
- * if there is no other, faster way to filter the records
- * 
- * Do not instanciate this!
- * 
- * @class
- */
-function JditoFilterUtils () {}
-
-/**
- * Filters the given records
- * 
- * @param {Array} pColumns one dimensional array with all column names, the order has to match the columns of pRecords
- * @param {Array} pRecords two dimensional array with all records
- * @param {String|Object} pFilter the value of $local.filter
- * 
- * @return {Array} the filtered records
- */
-JditoFilterUtils.filterRecords = function (pColumns, pRecords, pFilter)
-{
-    if (!pFilter)
-        return pRecords;
-    
-    var filter = new JditoFilter(pColumns, pFilter);
-    
-    return pRecords.filter(function (row)
-        {
-            return this.checkRecord(row);
-        }, filter);
+import("Sql_lib");
+
+/**
+ * object for filtering records
+ * 
+ * @param {Array} pColumns the column names
+ * @param {String|Object} pFilter the filter object
+ */
+function JditoFilter (pColumns, pFilter) 
+{
+    var columnMap = {};
+    pColumns.forEach(function (row, i)
+    {
+        columnMap[row] = i;
+    });
+    this._columnMap = columnMap;
+    
+    if (pFilter.length) //check if pFilter is a string
+        pFilter = JSON.parse(pFilter);
+    
+    this._operator = pFilter.operator;
+    this._filters = pFilter.childs;
+}
+
+/**
+ * tests the given row if it matches the filter
+ * 
+ * @param {Array} pRow one record
+ * 
+ * @return {boolean} true, if it matches the condition
+ */
+JditoFilter.prototype.checkRecord = function (pRow)
+{
+    if (this._filters.length == 0)
+        return true;
+    
+    if (this._operator == "AND")
+        return this._filters.every(_testFn, this);
+    
+    return this._filters.some(_testFn, this);
+    
+    function _testFn (pFilter)
+    {
+        let value = pRow[this._columnMap[pFilter.name]];
+        return this._testValue(value, (pFilter.key || pFilter.value), pFilter.operator);
+    }
+}
+
+/**
+ * compares two values with the given operator
+ */
+JditoFilter.prototype._testValue = function (pRowValue, pFilterValue, pOperator)
+{
+    switch (pOperator)
+    {
+        case "CONTAINS":
+            return (new RegExp(pFilterValue)).test(pRowValue);
+        case "CONTAINSNOT":
+            return !(new RegExp(pFilterValue)).test(pRowValue);
+        case "STARTSWITH":
+            return (new RegExp("^" + pFilterValue)).test(pRowValue);
+        case "ENDSWITH":
+            return (new RegExp(pFilterValue + "$")).test(pRowValue);
+        case "EQUAL":
+            return (new RegExp("^" + pFilterValue + "$")).test(pRowValue);
+        case "NOT_EQUAL":
+            return !(new RegExp("^" + pFilterValue + "$")).test(pRowValue);
+        case "LESS":
+            return pRowValue < pFilterValue;
+        case "LESS_OR_EQUAL":
+            return pRowValue <= pFilterValue;
+        case "GREATER":
+            return pRowValue > pFilterValue;
+        case "GREATER_OR_EQUAL":
+            return pRowValue >= pFilterValue;
+        case "ISNULL":
+            return pRowValue == "";
+        case "ISNOTNULL":
+            return pRowValue != "";
+    }
+}
+
+/**
+ * Provides functions for using the filter with jdito recordcontainers. You should only use this
+ * if there is no other, faster way to filter the records
+ * 
+ * Do not instanciate this!
+ * 
+ * @class
+ */
+function JditoFilterUtils () {}
+
+/**
+ * Filters the given records
+ * 
+ * @param {Array} pColumns one dimensional array with all column names, the order has to match the columns of pRecords
+ * @param {Array} pRecords two dimensional array with all records
+ * @param {String|Object} pFilter the value of $local.filter
+ * 
+ * @return {Array} the filtered records
+ */
+JditoFilterUtils.filterRecords = function (pColumns, pRecords, pFilter)
+{
+    if (!pFilter)
+        return pRecords;
+    
+    var filter = new JditoFilter(pColumns, pFilter);
+    
+    return pRecords.filter(function (row)
+        {
+            return this.checkRecord(row);
+        }, filter);
+}
+
+JditoFilterUtils.getSqlCondition = function (pFilter, pTable, pColumnMap)
+{
+    var condition = new SqlCondition();
+    
+    if (!pFilter)
+        return condition;
+    if (pFilter.length) //check if pFilter is a string
+        pFilter = JSON.parse(pFilter);
+    if (!pColumnMap)
+        pColumnMap = {};
+    
+    _addCondition.call(condition, pFilter, pFilter.operator);
+    
+    return condition;
+    
+    //recursive function (for compatibility with a condition tree) that
+    //builds the SqlCondition
+    function _addCondition (pCondition, pOperator)
+    {
+        if (pCondition.type == "row")
+        {
+            if (pCondition.name in pColumnMap)
+                pCondition.name = pColumnMap[pCondition.name];
+            else if (pTable)
+                pCondition.name =  pTable + "." + pCondition.name;
+            
+            pCondition.value = (pCondition.key || pCondition.value);
+            var condition = _getCondition.call(pCondition);
+            if (pOperator == "AND")
+                this.andPrepare(pCondition.name, pCondition.value, condition);
+            else if (pOperator == "OR")
+                this.orPrepare(pCondition.name, pCondition.value, condition);
+        }
+        else if (pCondition.type == "group")
+        {
+            let subCondition = new SqlCondition();
+            let operator = pCondition.operator;
+            pCondition.childs.forEach(function (cond)
+            {
+                _addCondition.call(subCondition, cond, operator);
+            });
+            if (pOperator == "AND")
+                this.andSqlCondition(subCondition);
+            else if (pOperator == "OR")
+                this.orSqlCondition(subCondition);
+        }
+    }
+    
+    //returns the condition depending on the operator and 
+    //adds wildcards to the value if necessary
+    function _getCondition ()
+    {
+        switch (this.operator)
+        {
+            case "CONTAINS":
+                this.value = "%" + this.value + "%";
+                return "# like ?";
+            case "CONTAINSNOT":
+                this.value = "%" + this.value + "%";
+                return "# not like ?";
+            case "STARTSWITH":
+                this.value = this.value + "%";
+                return "# like ?";
+            case "ENDSWITH":
+                this.value = "%" + this.value;
+                return "# like ?";
+            case "EQUAL":
+                return "# = ?";
+            case "NOT_EQUAL":
+                return "# != ?";
+            case "LESS":
+                return "# < ?";
+            case "LESS_OR_EQUAL":
+                return "# <= ?";
+            case "GREATER":
+                return "# > ?";
+            case "GREATER_OR_EQUAL":
+                return "# >= ?";
+            case "ISNULL":
+                return "# is null";
+            case "ISNOTNULL":
+                return "# is not null";
+        }
+    }
 }
\ No newline at end of file