question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Query regarding Type Conversions for Model Columns

See original GitHub issue

Hi,

Request to please help on the below:

I am working to create a admin dashboard using flask admin with oracle database as back end. The Search box on one of the page gives me the below error:

sqlalchemy.exc.DatabaseError: (cx_Oracle.DatabaseError) ORA-00906: missing left parenthesis [SQL: 'SELECT count(:count_2) AS count_1 \nFROM measure \nWHERE lower(CAST(measure.measure_code AS N ARCHAR2)) LIKE lower(:param_1) OR lower(CAST(measure.measure_name AS NVARCHAR2)) LIKE lower(:param_2)'] [parameters: {'param_1': u'%da%', 'count_2': '*', 'param_2': u'%da%'}]

This is the SQL:

('SELECT count(:count_2) AS count_1 \nFROM measure \nWHERE lower(CAST(measure.measure_code AS NVARCHAR2)) LIKE lower(:param_1) OR lower(CAST(measure.measure_name AS NVARCHAR2)) LIKE lower(:param_2)' , {'param_1': u'%da%', 'count_2': '*', 'param_2': u'%da%'})

I tried to run the sql by removing the CAST function and it works fine.

Is there any possibility i can force the Flask Admin not to do type casting for the column in my sql.

Thank you for your help.

Issue Analytics

  • State:open
  • Created 6 years ago
  • Comments:5

github_iconTop GitHub Comments

1reaction
peteristhegreatcommented, Jul 8, 2018

FYI, this is what the error looks like and how I was searching for it in Google…

ORA-00906: missing left parenthesis in Flask-Admin, SqlAlchemy

The error

DatabaseError: (cx_Oracle.DatabaseError) ORA-00906: missing left parenthesis [SQL: u'SELECT count(:count_2) AS count_1 \nFROM "oracle_table" \nWHERE (lower(CAST("column_name" AS NVARCHAR2)) LIKE lower(:param_1) OR (/*... repeated with more parameters and column names ...*/))'] [parameters: { u'param_2': u'%SEARCH_STRING%', u'count_2': '*'}] (Background on this error at: http://sqlalche.me/e/4xp6)

Meaning

Oracle expects a count for the number of characters for some reason. And the count is inside a set of parenthesis. So after stating VARCHAR or NVARCHAR2 or CHAR, in a CAST call, it wants the character count. So VARCHAR should be VARCHAR(60) for example.

Work Around

I found a work around! Replace the calls to cast with _safe_cast that I threw together down below.

in flask-admin/flask_admin/contrib/sqla/view.py ModelView._apply_search near line 911

            filter_stmt.append(self._safe_cast(column, Unicode).ilike(stmt))

            if count_filter_stmt is not None:
                column = field if count_alias is None else getattr(count_alias, field.key)
                count_filter_stmt.append(self._safe_cast(column, Unicode).ilike(stmt))

And somewhere else nearby expose _safe_cast

from sqlalchemy.dialects.oracle import VARCHAR, VARCHAR2, CHAR
from sqlalchemy.types import Integer, String

@staticmethod
def _safe_cast(column, type):
    if isinstance(column.type, Integer): # TODO: also check to see if oracle...
        return cast(column, String(32)
    if not isinstance(column.type, VARCHAR) and not isinstance(column.type, VARCHAR2) and not isinstance(column.type, CHAR):
        return cast(column, type)
    else:
        return column

And here are some related threads that mention similar issues:

https://stackoverflow.com/questions/40342555/sqlalchemy-cast-to-binaryn

Also I tried this, but it did NOT seem to affect how CAST behaves:

from sqlalchemy.sql.elements import TypeClause
from sqlalchemy.dialects.oracle import VARCHAR2, VARCHAR, CHAR
from sqlalchemy.ext.compiler import compiles

@compiles(TypeClause, "oracle")
def _compile_typeclause(element, compiler, **kwargs):
    print '_compile_typeclause', str(element.type)
    if isinstance(element.type, VARCHAR):
        t = "VARCHAR"
        if element.type.length:
            t += "({})".format(element.type.length)
        return t
    elif isinstance(element.type, VARCHAR2):
        t = "VARCHAR2"
        if element.type.length:
            t += "({})".format(element.type.length)
        return t
    elif isinstance(element.type, CHAR):
        t = "CHAR"
        if element.type.length:
            t += "({})".format(element.type.length)
        return t
    return compiler.visit_typeclause(element, **kwargs)

https://github.com/Pegase745/sqlalchemy-datatables/issues/31 https://github.com/Pegase745/sqlalchemy-datatables/pull/46/commits/8479f59681c266e4c4dd596fb69631beab35b93e

Another workaround that did NOT work for me was to put the length of the column in the Unicode type parameter.

            filter_stmt.append(cast(column, Unicode(column.type.length)).ilike(stmt))

            if count_filter_stmt is not None:
                column = field if count_alias is None else getattr(count_alias, field.key)
                count_filter_stmt.append(cast(column, Unicode(column.type.length)).ilike(stmt))

Another place where casting could happen that might benefit from a similar fix.

https://github.com/alan-eu/flask-admin/commit/d36ebcf23344f01bd33beb961ab48c6af5903604 https://github.com/alan-eu/flask-admin/commit/725d4ba2b7b4485bd148faede7fc4cd1f71369a3

I think the true fix would be to have SqlAlchemy handle CAST for the oracle sql compiler properly.

0reactions
shellcomputecommented, Sep 16, 2021

In fact, @compiles filter on CAST-expression compiler for Oracle works well:

from sqlalchemy.sql.elements import Cast
from sqlalchemy.types import String
from sqlalchemy.ext.compiler import compiles

@compiles(Cast, "oracle")
def visit_cast(element, compiler, **kwargs):
    """
    oracle compiler filter
    """
    # print('element:', element.type, type(element.type), type(element.typeclause))
    # element: VARCHAR <class 'sqlalchemy.sql.sqltypes.String'> <class 'sqlalchemy.sql.elements.TypeClause'>
    if isinstance(element.type, String):
        return compiler.process(element.clause, **kwargs)
    return compiler.visit_cast(element, **kwargs)

The workaround and sqlalchemy-cast-to-binaryn given by by @peteristhegreat, helps a lot.

Read more comments on GitHub >

github_iconTop Results From Across the Web

SQL Server Data Type Conversion Methods and performance ...
This artcile explores the SQL Server Data type conversion method and their performance comparison.
Read more >
SQL CAST Function for Data Type Conversions
In this article we look at how to use the CAST function to change the data type for some data as well as...
Read more >
Data types in Power Query - Microsoft Learn
Changed type: Converts the values from the Any data type to a data type based on the inspection of the values from each...
Read more >
Data Type Conversions For SQL Server Sources And Query ...
I have set the (relatively new) UnsafeTypeConversions property on the Sql.Databases function to true · In the custom column I have used the ......
Read more >
Convert column data type | ThoughtSpot Software
Contents · Data type conversion behavior · Supported data type conversions · Date and time conversions · String to boolean conversions · String...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found