MSSQL Failing deletes with subquery + schema

Issue #973 resolved
paj created an issue

SA currently generates a query like this:

DELETE FROM tsr.vulnres WHERE tsr.vulnres.tool != ? AND tsr.vulnres.targetid IN (SELECT tsr.target.id \nFROM tsr.target AS target_1, tsr.testing AS testing_1 \nWHERE tsr.target.testingid = tsr.testing.id AND tsr.testing.tsrid = ?)

In the subquery, the tables are aliased, but the columns don't use the aliased name.

A likely fix for this is in MSSQLCompiler.visit_column, to check if it's in a subquery inside a delete/update. However, is_subquery doesn't work at the moment, so that will need to be fixed first.

For SQL 2005 a workaround is to disable all the table aliasing.

Comments (6)

  1. Mike Bayer repo owner

    whats the desired SQL here? I can see that it has to do with schema-qualified table names needing to be alised. But, only in subqueries ?

    anyway, the way that MS-SQL is creating the "alias" for tables is a little weak - within visit_select(), clause adaption should be used to return an entirely new select() statement with the proper aliasing built in. The general technique is described below:

    from sqlalchemy import *
    
    m = MetaData()
    t = Table('t1', m, Column('somedata', Integer), schema="someschema")
    
    s = select([t.c.somedata](t.c.somedata)).where(t.c.somedata==5)
    d = t.delete().where(t.c.somedata.in_(s))
    
    from sqlalchemy.databases import mssql
    
    # this prints the "wrong" SQL
    print d.compile(dialect=mssql.dialect())
    
    print "--------------------"
    
    # if visit_select did this, would be better
    from sqlalchemy.sql.util import find_tables, ClauseAdapter
    tables = find_tables(s)
    
    for t in find_tables(s):
        s = ClauseAdapter(t.alias()).traverse(s, clone=True)
    
    d = t.delete().where(t.c.somedata.in_(s))
    print d.compile(dialect=mssql.dialect())
    
  2. paj reporter

    The desired SQL is this:

    DELETE FROM tsr.vulnres WHERE tsr.vulnres.tool != ? AND tsr.vulnres.targetid IN (SELECT tsr.target.id \nFROM tsr.target AS target_1, tsr.testing AS testing_1 \nWHERE target_1.testingid = testing_1.id AND testing_1.tsrid = ?)

    There are a few other options that would work, but that seems the closest to what's generated already.

  3. Mike Bayer repo owner

    to get is_subquery() working inside of delete/update/insert, this should work:

    Index: lib/sqlalchemy/sql/compiler.py
    ===================================================================
    --- lib/sqlalchemy/sql/compiler.py  (revision 5145)
    +++ lib/sqlalchemy/sql/compiler.py  (working copy)
    @@ -475,7 +475,7 @@
             stack_entry = {'select':select}
             prev_entry = self.stack and self.stack[-1](-1) or None
    
    -        if asfrom or (prev_entry and 'select' in prev_entry):
    +        if asfrom or (prev_entry and 'from' in prev_entry):
                 stack_entry['is_subquery']('is_subquery') = True
                 stack_entry['iswrapper']('iswrapper') = iswrapper
                 if not iswrapper and prev_entry and 'iswrapper' in prev_entry:
    

    then MSSQL visit_column() would have to use the label name for the column when is_subquery is detected (and maybe also self.isdelete=True ?). To get the label name look at the logic within compiler.label_select_column().

  4. Log in to comment