Issue 1:
Migrated issue, originally created by Raphaël Slinckx

Here is a test case yielding different results between sqlalchemy 0.9.9 and 1.0+

import json

from sqlalchemy import create_engine, Column
from sqlalchemy.types import TypeDecorator, UnicodeText, Integer
from sqlalchemy.ext.mutable import MutableDict
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session

class JSONDictType(TypeDecorator):
impl = UnicodeText

def process_bind_param(self, value, dialect):
if value is not None:
value = unicode(json.dumps(value))
return value

def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value

def JSON():
return MutableDict.as_mutable(JSONDictType)

Base = declarative_base()

class MyDataClass(Base):
__tablename__ = 'my_data'

id = Column(Integer, primary_key=True)
data = Column(JSON(), default={}, nullable=False)

session = Session(create_engine("sqlite:///:memory:", echo=True))
Base.metadata.create_all(session.bind)

m1 = MyDataClass()
session.add(m1)
session.flush() # Note: without this, the m1.data is None on sqlalchemy 1.0
m1.data["test"] = 1
session.commit()

m2 = MyDataClass()
session.add(m2)
session.commit()

assert m1.data == {'test': 1}
assert m2.data == {}
# With sqlalchemy 1.0 this results in
# m1.data == {}
# m2.data == {'test': 1}

As you can see the default={} seemed to work previously (also the m1.data['test'] = 1 worked without the session.flush() just above)

With the latest version, the dict provided as default seems to be used and re-used resulting in a process-wide globally shared default dict which can lead to catastrophic leaks in a webapp for example. Also the object needs to be flushed before m1.data is populated.

Maybe this usage was not expected or even discouraged, but I didn't find anything in the docs about this. The closest thing I identified is the change discussed here: http://docs.sqlalchemy.org/en/latest/changelog/migration_10.html#changes-to-attribute-events-and-other-operations-regarding-attributes-that-have-no-pre-existing-value and possibly the new default handling.

What is the expected behavior as of 1.0 ? And how do I get the old behavior using the latest version ?

Issue 2:
Migrated issue, originally created by Anonymous

If I create a join object and then use it as a parameter for relation's primaryjoin, the mapper will compile without complaining, but accessing the property that maps to such a relation generates illegal SQL and throws ProgrammingError.

Ie.:

class_mapper(Class).add_property('property', relation(OtherClass, primaryjoin=Class.other_id==OtherClass.id)))

works fine but:

myjoin=join(Class, OtherCalss, Class.other_id==OtherClass.id)
class_mapper(Class).add_property('property', relation(OtherClass, primaryjoin=myjoin))

Compiles but throws ProgrammingError on accessing Class.property.

If a join cannot be used to specify primaryjoin then the mapper should complain right away; otherwise, SQLA should somehow extract the join clause from the join when it's used in this way.

This issue may be related to changeset 5325.

I've attached a test case that's maybe a little more complicated than necessary but demonstrates the issue clearly.

Attachments: join_as_primaryjoin.py