0001from metasqlobject.declarative import Declarative, counter
0002from metasqlobject.classinst import classinstancemethod
0003from sqlapi import sql
0004from sqlapi import exceptions
0005from classreg import class_registry
0006import events
0007
0008class Col(object):
0009
0010    _standard_sql_type = None
0011
0012    # These are overrides that can be used for force the exact SQL
0013    # type:
0014    sql_type = None
0015    sql_constraints = None
0016    not_null = False
0017    auto_increment = False
0018    primary_key = False
0019
0020    get = None
0021    set = None
0022
0023    db_name = None
0024    name = None
0025    soclass = None
0026
0027    def __init__(self, soclass=None, name=None,
0028                 copyfrom=None, **kw):
0029        if copyfrom is not None:
0030            for k, v in copyfrom.__dict__.items():
0031                if not k.startswith('_'):
0032                    setattr(self, k, v)
0033        self.declarative_count = counter.next()
0034        if name is not None:
0035            self.name = name
0036        for k, v in kw.items():
0037            setattr(self, k, v)
0038        if soclass is not None:
0039            self.soclass = soclass
0040            self.sqlexpr = sql.Column(
0041                sql.Table(soclass.sqlmeta.table),
0042                self.db_name or self.name)
0043        # @@: Warn about dbName
0044
0045    def __repr__(self):
0046        s = '<%s object %s' % (
0047            self.__class__.__name__, self.declarative_count)
0048        if self.soclass:
0049            s += ' for %s.%s' % (
0050                self.soclass.__module__,
0051                self.soclass.__name__)
0052        if self.name:
0053            s += ' name=%r' % self.name
0054        else:
0055            s += ' unnamed'
0056        if self.db_name:
0057            s += ' db_name=%r' % self.db_name
0058        s += '>'
0059        return s
0060
0061    def sql_type_parameters(self):
0062        cur = {}
0063        if self.not_null:
0064            cur['not_null'] = True
0065        if self.auto_increment:
0066            cur['auto_increment'] = True
0067        if self.primary_key:
0068            cur['primary_key'] = True
0069        return cur
0070
0071    def standard_sql_type(self, ops):
0072        return self._standard_sql_type
0073
0074    def __addtoclass__(self, cls, soclass, name):
0075        if self is None:
0076            col = cls(soclass=soclass, name=name)
0077        else:
0078            col = self.__class__(soclass=soclass, name=name,
0079                                 copyfrom=self)
0080        setattr(soclass, name, col)
0081        soclass.sqlmeta.add_column(col)
0082        soclass.__addtosubclass__.append(name)
0083
0084    __addtoclass__ = classinstancemethod(__addtoclass__)
0085
0086    def __set__(self, obj, value):
0087        if self.set is not None:
0088            self.set(obj, value, self.raw_set)
0089        else:
0090            self.raw_set(obj, value)
0091
0092    def raw_set(self, obj, value):
0093        from_python = self.from_python
0094        to_python = self.to_python
0095        if from_python is not Col.from_python:
0096            db_value = from_python(value)
0097        else:
0098            db_value = value
0099        if to_python is not Col.to_python:
0100            value = to_python(db_value)
0101        obj.sqlmeta._db_values[self.name] = db_value
0102        obj.sqlmeta._python_values[self.name] = value
0103        obj.sqlmeta._dirty_columns.append(self.name)
0104        if not obj.sqlmeta.lazy:
0105            obj.sqlmeta.write_updates()
0106
0107    def __get__(self, obj, type=None):
0108        if obj is None:
0109            return self.sqlexpr
0110        if self.get is not None:
0111            return self.get(obj, self.raw_get)
0112        else:
0113            return self.raw_get(obj)
0114
0115    def raw_get(self, obj):
0116        return obj.sqlmeta._python_values[self.name]
0117
0118    def to_python(self, value, state=None):
0119        return value
0120
0121    def from_python(self, value, state=None):
0122        return value
0123
0124    def create_sql(self, conn):
0125        ops = self.sql_type_parameters()
0126        return sql.ColumnDefinition(
0127            self.db_name or self.name,
0128            self.sql_type or self.standard_sql_type(ops),
0129            **ops), []
0130
0131class StringCol(Col):
0132
0133    _standard_sql_type = 'TEXT'
0134    length = None
0135
0136    def sql_type_parameters(self):
0137        cur = Col.sql_type_parameters(self)
0138        if self.length is not None:
0139            cur['length'] = self.length
0140        return cur
0141
0142    def standard_sql_type(self, ops):
0143        if 'length' in ops:
0144            return 'VARCHAR(%i)' % ops['length']
0145        else:
0146            return 'TEXT'
0147
0148class IntCol(Col):
0149
0150    _standard_sql_type = 'INT'
0151
0152# @@: Warn:
0153KeyCol = IntCol
0154
0155class FloatCol(Col):
0156
0157    _standard_sql_type = 'FLOAT'
0158
0159class DateCol(Col):
0160
0161    _standard_sql_type = 'DATE'
0162
0163class DateTimeCol(Col):
0164
0165    _standard_sql_type = 'TIMESTAMP'
0166
0167class BoolCol(Col):
0168
0169    _standard_sql_type = 'BOOLEAN'
0170
0171    def from_python(self, value, state=None):
0172        return bool(value)
0173
0174# @@: Warn:
0175
0176class ForeignKey(object):
0177
0178    cascade = None
0179
0180    def __init__(self, foreign_class=None,
0181                 soclass=None, name=None, copyfrom=None,
0182                 cascade=None, **args):
0183        if copyfrom is not None:
0184            self.__dict__.update(copyfrom.__dict__)
0185        if foreign_class is not None:
0186            self.foreign_class = foreign_class
0187        self.soclass = soclass
0188        self.name = name
0189        if cascade is not None:
0190            self.cascade = cascade
0191        if name:
0192            self.column_name = name + '_id'
0193            if getattr(self, 'column_args', None):
0194                self.column_args.update(args)
0195            else:
0196                self.column_args = args
0197        else:
0198            self.column_args = args
0199        self.declarative_count = counter.next()
0200
0201    def create_column(self):
0202        return IntCol(soclass=self.soclass, name=self.column_name,
0203                      **self.column_args)
0204
0205    def create_reference(self):
0206        return Reference(self.foreign_class, self.column_name,
0207                         soclass=self.soclass, name=self.name,
0208                         cascade=self.cascade)
0209
0210    def __addtoclass__(self, cls, soclass, name):
0211        if self is None:
0212            me = cls(soclass=soclass, name=name)
0213        else:
0214            me = self.__class__(soclass=soclass, name=name,
0215                                copyfrom=self)
0216
0217        ref = me.create_reference()
0218        col = me.create_column()
0219        setattr(soclass, col.name, col)
0220        col.__addtoclass__(soclass, col.name)
0221        setattr(soclass, ref.name, ref)
0222        ref.__addtoclass__(soclass, ref.name)
0223
0224    __addtoclass__ = classinstancemethod(__addtoclass__)
0225
0226class Reference(object):
0227
0228    get = None
0229    set = None
0230    cascade = None
0231
0232    def __init__(self, foreign_class=None, column_name=None,
0233                 soclass=None, name=None, copyfrom=None,
0234                 cascade=None,
0235                 **args):
0236        if copyfrom is not None:
0237            self.__dict__.update(copyfrom.__dict__)
0238        self.declarative_count = counter.next()
0239        if foreign_class is not None:
0240            self.foreign_class = foreign_class
0241        if column_name is not None:
0242            self.column_name = column_name
0243        if soclass is not None:
0244            self.soclass = soclass
0245            self.relative_module = self.soclass.__module__
0246        if name is not None:
0247            self.name = name
0248        if cascade is not None:
0249            assert cascade in (True, False, 'null')
0250            self.cascade = cascade
0251        if self.cascade is not None and self.soclass is not None:
0252            class_registry.register_callback(
0253                self.set_cascade,
0254                self.foreign_class, soclass.__module__)
0255
0256    def __repr__(self):
0257        if self.soclass:
0258            soclass = self.soclass.__name__
0259        else:
0260            soclass = '(no class set)'
0261        if self.column_name:
0262            soclass += '.'+self.column_name
0263        if self.foreign_class:
0264            foreign_class = self.foreign_class
0265        else:
0266            foreign_class = '(no dest set)'
0267        return '<%s %i for %s->%s as %s>' % (
0268            self.__class__.__name__,
0269            self.declarative_count,
0270            soclass, foreign_class,
0271            self.name or '(no attribute set)')
0272
0273    def set_cascade(self, joined_class):
0274        joined_class.event_hub.listen(
0275            events.RowDestroySignal, self, 'joined_destroyed')
0276        joined_class.event_hub.listen(
0277            events.DropTableSignal, self, 'joined_drop_table')
0278
0279    def joined_destroyed(self, instance, post_funcs):
0280        current = self.soclass.select_by(
0281            **{self.column_name: instance.id})
0282        current = list(current)
0283        if (self.cascade is not None and not self.cascade
0284            and current):
0285            raise exceptions.IntegrityError(
0286                "Cannot delete %r; instances depend on it: %r"
0287                % (instance, current))
0288        def do_cascade(soclass, del_id):
0289            for item in current:
0290                if self.cascade == 'null':
0291                    setattr(item, self.column_name, None)
0292                else:
0293                    item.destroy_self()
0294        post_funcs.append(do_cascade)
0295
0296    def joined_drop_table(self, soclass, connection,
0297                          cascade, post_funcs):
0298        if cascade and self.soclass.sqlmeta.table_exists(connection):
0299            self.soclass.sqlmeta.drop_table(cascade=cascade)
0300
0301    def __get__(self, obj, type=None):
0302        if obj is None:
0303            return SQLForeignKeyExpr(self)
0304        if self.get is not None:
0305            return self.get(obj, self.raw_get)
0306        else:
0307            return self.raw_get(obj)
0308
0309    def raw_get(self, obj):
0310        try:
0311            key = obj.sqlmeta._python_values[self.column_name]
0312        except KeyError:
0313            raise AttributeError(
0314                "Cannot retrieve .%s until .%s is set"
0315                % (self.name, self.column_name))
0316        if key is None:
0317            return None
0318        return class_registry.get_class(
0319            self.foreign_class, self.relative_module).get(key)
0320
0321    def __set__(self, obj, value):
0322        if self.set is not None:
0323            self.set(obj, value, self.raw_set)
0324        else:
0325            self.raw_set(obj, value)
0326
0327    def raw_set(self, obj, value):
0328        if isinstance(value, (int, long, str)):
0329            raise ValueError(
0330                "You can only set a reference to an object with an "
0331                ".id attribute, not the actual id (got: %r)"
0332                % value)
0333        key = value.id
0334        setattr(obj, self.column_name, key)
0335
0336    def __addtoclass__(self, cls, soclass, name):
0337        if self is None:
0338            me = cls(soclass=soclass, name=name)
0339        else:
0340            me = self.__class__(soclass=soclass, name=name,
0341                                copyfrom=self)
0342        setattr(soclass, name, me)
0343
0344    __addtoclass__ = classinstancemethod(__addtoclass__)
0345
0346class Compound(object):
0347
0348    def __init__(self, *columns, **kw):
0349        if 'copyfrom' in kw:
0350            self.__dict__.update(kw['copyfrom'].__dict__)
0351            del kw['copyfrom']
0352        actual_columns = []
0353        for name in columns:
0354            if isinstance(name, (list, tuple)):
0355                actual_columns.extend(name)
0356            else:
0357                actual_columns.append(name)
0358        self.columns = tuple(actual_columns)
0359        for name, value in kw.items():
0360            setattr(self, name, value)
0361
0362    def __addtoclass__(self, cls, soclass, name):
0363        if self is None:
0364            me = cls(soclass=soclass, name=name)
0365        else:
0366            me = self.__class__(soclass=soclass, name=name,
0367                                copyfrom=self)
0368        setattr(soclass, name, me)
0369
0370    __addtoclass__ = classinstancemethod(__addtoclass__)
0371
0372    def __get__(self, obj, type=None):
0373        if obj is None:
0374            return sql.CompoundExpression(
0375                *[getattr(type, name) for name in self.columns])
0376        return tuple(
0377            [getattr(obj, name) for name in self.columns])
0378
0379    def __set__(self, obj, value):
0380        if len(value) != len(self.columns):
0381            raise ValueError(
0382                "You must pass a tuple of length %i to %r (you gave: "
0383                "%r)" % (
0384                len(self.columns), self, value))
0385        obj.set(**dict(zip(self.columns, value)))
0386
0387class SQLForeignKeyExpr(object):
0388
0389    def __init__(self, reference):
0390        self.reference = reference
0391
0392    def __repr__(self):
0393        return '<%s %s>' % (
0394            self.__class__.__name__, sql.sqlrepr(self))
0395
0396    def __sql_components__(self, dbsql):
0397        return [getattr(self.reference.soclass, self.reference.column_name)]
0398
0399    def __eq__(self, other):
0400        if other is None:
0401            return sql.SQLPostfixOp(self, 'IS NULL')
0402        else:
0403            return sql.SQLOp(self, '=', other.id)
0404
0405    def __ne__(self, other):
0406        if other is None:
0407            return sql.SQLPostfixOp(self, 'IS NOT NULL')
0408        else:
0409            return sql.SQLOp(self, '<>', other.id)
0410
0411    # @@: Should these be available?
0412    def __and__(self, other):
0413        return SQLOp(self, 'AND', other)
0414    def __rand__(self, other):
0415        return SQLOp(other, 'AND', self)
0416    def __or__(self, other):
0417        return SQLOp(self, 'OR', other)
0418    def __ror__(self, other):
0419        return SQLOp(other, 'OR', self)
0420    def __invert__(self):
0421        return SQLUnaryOp('NOT', self)
0422
0423__all__ = ['ForeignKey']
0424for name, value in globals().items():
0425    if (isinstance(value, type)
0426        and issubclass(value, Col)):
0427        __all__.append(name)