Repository: OpenEnergyPlatform/oeplatform

Issue:
Advanced API: Condition with varchar value fails
I'm using the advanced API to retrieve some data.

Using a condition on a **numeric (serial) column** works:
```
import requests
data = { "query": {"fields": ['id', {'type': 'function', 'function': 'ST_AsText', 'operands':[{'type': 'column', 'column': 'geom'}]}],
                   "where": [{'type': 'operator', 'operator': '=', 'operands':[{'type': 'column', 'column': 'id'}, {'type': 'value', 'value': 1}]}],
                   "from":{'type': 'table', 'table': 'bkg_vg250_4_krs', 'schema':"boundaries"}}}
response = requests.post('http://oep.iks.cs.ovgu.de/api/v0/advanced/search', json=data)
data = response.json()['data']
print(data)
```

But not using a condition on a **non-numeric (varchar) column**:
```
import requests
data = { "query": {"fields": ['id', {'type': 'function', 'function': 'ST_AsText', 'operands':[{'type': 'column', 'column': 'geom'}]}],
                   "where": [{'type': 'operator', 'operator': '=', 'operands':[{'type': 'column', 'column': 'nuts'}, {'type': 'value', 'value': 'DEE05'}]}],
                   "from":{'type': 'table', 'table': 'bkg_vg250_4_krs', 'schema':"boundaries"}}}
response = requests.post('http://oep.iks.cs.ovgu.de/api/v0/advanced/search', json=data)
data = response.json()['data']
print(data)
```
(result is empty)

What's the matter here?

Update: Oh, there might be [some stuff left](https://github.com/openego/oeplatform/blob/cf9033206d2235339a0a299d84c1315260d28cf0/api/parser.py#L32) to be done..?!


Discussion:

Resolving patch:
diff --git a/api/actions.py b/api/actions.py
--- a/api/actions.py
+++ b/api/actions.py
@@ -24,6 +24,9 @@
 from sqlalchemy import exc
 pgsql_qualifier = re.compile(r"^[\w\d_\.]+$")
 
+import logging
+
+logger = logging.getLogger('oeplatform')
 
 def get_table_name(schema, table, restrict_schemas=True):
     if not has_schema(dict(schema=schema)):
@@ -253,7 +256,7 @@ def perform_sql(sql_statement, parameter=None):
     session = sessionmaker(bind=engine)()
 
     # Statement built and no changes required, so statement is empty.
-    print("SQL STATEMENT: |" + sql_statement + "|", parameter)
+    logger.debug("SQL STATEMENT: |" + sql_statement + "| \t " + str(parameter))
     if not sql_statement or sql_statement.isspace():
         return get_response_dict(success=True)
 
@@ -987,6 +990,7 @@ def _execute_sqla(query, cursor):
         raise APIError(repr(e))
     try:
         params = dict(compiled.params)
+        logger.debug('Executed %s with parameters %s'%(str(compiled), params))
         cursor.execute(str(compiled), params)
     except (psycopg2.DataError, exc.IdentifierError, psycopg2.IntegrityError) as e:
         raise APIError(repr(e))
@@ -1663,7 +1667,7 @@ def add_type(d, type):
 
 
 def apply_insert(session, table, row):
-    print("apply insert", row)
+    logger.info("apply insert", row)
     session.execute(table.insert(), row)
     session.execute('UPDATE {schema}.{table} SET _applied=TRUE WHERE id={id};'.format(
         schema=get_meta_schema_name(table.schema),
@@ -1673,7 +1677,7 @@ def apply_insert(session, table, row):
 
 
 def apply_update(session, table, row):
-    print("apply update", row)
+    logger.info("apply update", row)
     session.execute(table.update(table.c.id==row['id']), row)
     session.execute(
         'UPDATE {schema}.{table} SET _applied=TRUE WHERE _id={id};'.format(
@@ -1683,7 +1687,7 @@ def apply_update(session, table, row):
         ))
 
 def apply_deletion(session, table, row):
-    print("apply deletion", row)
+    logger.info("apply deletion", row)
     session.execute(table.delete(table.c.id==row['id']), row)
     session.execute(
         'UPDATE {schema}.{table} SET _applied=TRUE WHERE _id={id};'.format(
diff --git a/api/parser.py b/api/parser.py
--- a/api/parser.py
+++ b/api/parser.py
@@ -31,8 +31,6 @@ def quote(x):
 
 def read_pgvalue(x):
     # TODO: Implement check for valid values
-    if isinstance(x, str):
-        return "'" + x + "'"
     if x is None:
         return 'null'
     return x
@@ -270,7 +268,7 @@ def parse_condition(dl):
     if isinstance(dl, list):
         dl = {'type':'operator',
               'operator': 'AND',
-              'operands': list(dl)}
+              'operands': list(map(parse_expression, dl))}
     return parse_expression(dl)
 
 


Test patch:
diff --git a/api/tests/test_regression/__init__.py b/api/tests/test_regression/__init__.py
new file mode 100644
diff --git a/api/tests/test_regression/test_issue271.py b/api/tests/test_regression/test_issue271.py
new file mode 100644
--- /dev/null
+++ b/api/tests/test_regression/test_issue271.py
@@ -0,0 +1,80 @@
+import requests
+import json
+from api.tests import APITestCase
+
+class Test271(APITestCase):
+
+    def setUp(self):
+        self._structure_data = {
+            "constraints": [
+                {
+                    "constraint_type": "PRIMARY KEY",
+                    "constraint_parameter": "id",
+                    "reference_table": None,
+                    "reference_column": None
+                }
+            ],
+            "columns": [
+                {
+                    "name": "id",
+                    "data_type": "bigserial",
+                    "is_nullable": False,
+                    "character_maximum_length": None
+                }, {
+                    "name": "name",
+                    "data_type": 'character varying',
+                    "is_nullable": True,
+                    "character_maximum_length": 123
+                }
+            ]
+        }
+
+        resp = self.__class__.client.put(
+            '/api/v0/schema/{schema}/tables/{table}/'.format(
+                schema=self.test_schema, table=self.test_table),
+            data=json.dumps({'query': self._structure_data}),
+            HTTP_AUTHORIZATION='Token %s' % self.__class__.token,
+            content_type='application/json')
+
+        self.assertEqual(resp.status_code, 201,
+                         resp.json().get('reason', 'No reason returned'))
+
+        resp = self.__class__.client.post(
+            '/api/v0/schema/{schema}/tables/{table}/rows/new'.format(
+                schema=self.test_schema, table=self.test_table),
+            data=json.dumps({'query': [{'name': 'Hans'}, {'name': 'Petra'}]}),
+            HTTP_AUTHORIZATION='Token %s' % self.__class__.token,
+            content_type='application/json')
+
+        self.assertEqual(resp.status_code, 201,
+                         resp.json().get('reason', 'No reason returned'))
+
+    def test_271(self):
+        data = {"query": {"fields": ['name'],
+                          "where": [{'type': 'operator', 'operator': '=',
+                                     'operands': [
+                                         {'type': 'column', 'column': 'name'},
+                                         {'type': 'value', 'value': 'Hans'}]}],
+                          "from": {'type': 'table', 'table': self.test_table,
+                                   'schema': self.test_schema}}}
+        resp = self.__class__.client.post('/api/v0/advanced/search',
+                                          data=json.dumps(data),
+                                          HTTP_AUTHORIZATION='Token %s' % self.__class__.token,
+                                          content_type='application/json')
+
+        self.assertEqual(resp.status_code, 200,
+                         resp.json().get('reason', 'No reason returned'))
+
+        json_resp = resp.json()
+
+        self.assertListEqual(json_resp['data'], [['Hans']])
+
+    def tearDown(self):
+        resp = self.__class__.client.delete(
+            '/api/v0/schema/{schema}/tables/{table}/'.format(
+                schema=self.test_schema, table=self.test_table),
+            HTTP_AUTHORIZATION='Token %s' % self.__class__.token,
+            content_type='application/json')
+
+        self.assertEqual(resp.status_code, 200,
+                         resp.json().get('reason', 'No reason returned'))
\ No newline at end of file